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 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); } } }
private static DamageRecord ParsePointsOf(string[] data, bool isNonMelee, int forIndex, int byIndex, int hitIndex, StringBuilder builder, List <string> nameList) { DamageRecord record = null; uint damage = StatsUtil.ParseUInt(data[forIndex + 1]); string type = null; string subType = null; string attacker = null; if (byIndex > 1) { // possible spell subType = ReadStringToPeriod(data, byIndex, builder); } // before hit nameList.Clear(); for (int i = hitIndex - 1; i >= 0; i--) { if (data[hitIndex].EndsWith(".", StringComparison.Ordinal)) { break; } else { nameList.Insert(0, data[i]); } } if (nameList.Count > 0) { attacker = string.Join(" ", nameList); } if (!isNonMelee) { subType = GetTypeFromHit(data[hitIndex], out bool additional); if (subType != null) { type = Labels.MELEE; if (additional) { hitIndex++; // multi-word hit value } } } if (!string.IsNullOrEmpty(subType) && isNonMelee) { type = GetTypeFromSpell(subType, Labels.DD); } string defender = string.Join(" ", data, hitIndex + 1, forIndex - hitIndex - 1); // check for pets HasOwner(attacker, out string attackerOwner); HasOwner(defender, out string defenderOwner); // some new special cases if (!string.IsNullOrEmpty(subType) && subType.StartsWith("Elemental Conversion", StringComparison.Ordinal)) { PlayerManager.Instance.AddVerifiedPet(defender); } else if (!string.IsNullOrEmpty(attacker) && !string.IsNullOrEmpty(defender)) { record = BuildRecord(attacker, defender, damage, attackerOwner, defenderOwner, subType, type); } return(record); }
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 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)); } }); } }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (DamageGroupIds) { try { FireNewStatsEvent(options); Reset(); Selected = options.Npcs; Title = options.Name; var damageBlocks = new List <ActionBlock>(); Selected.ForEach(fight => { damageBlocks.AddRange(fight.DamageBlocks); if (fight.GroupId > -1) { DamageGroupIds[fight.GroupId] = 1; } RaidTotals.Ranges.Add(new TimeSegment(fight.BeginDamageTime, fight.LastDamageTime)); StatsUtil.UpdateRaidTimeRanges(fight, PlayerTimeRanges, PlayerSubTimeRanges); }); damageBlocks.Sort((a, b) => a.BeginTime.CompareTo(b.BeginTime)); if (damageBlocks.Count > 0) { RaidTotals.TotalSeconds = RaidTotals.Ranges.GetTotal(); RaidTotals.MaxTime = RaidTotals.TotalSeconds; int rangeIndex = 0; double lastTime = 0; var newBlock = new List <ActionBlock>(); damageBlocks.ForEach(block => { if (RaidTotals.Ranges.TimeSegments.Count > rangeIndex && block.BeginTime > RaidTotals.Ranges.TimeSegments[rangeIndex].EndTime) { rangeIndex++; if (newBlock.Count > 0) { DamageGroups.Add(newBlock); } newBlock = new List <ActionBlock>(); } if (lastTime != block.BeginTime) { var copy = new ActionBlock(); copy.Actions.AddRange(block.Actions); copy.BeginTime = block.BeginTime; newBlock.Add(copy); } else { newBlock.Last().Actions.AddRange(block.Actions); } // update pet mapping block.Actions.ForEach(action => UpdatePetMapping(action as DamageRecord)); lastTime = block.BeginTime; }); DamageGroups.Add(newBlock); RaidTotals.Ranges.TimeSegments.ForEach(segment => DataManager.Instance.GetResistsDuring(segment.BeginTime, segment.EndTime).ForEach(block => Resists.AddRange(block.Actions))); ComputeDamageStats(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); } } } }
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); } } }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (HealingGroups) { Selected = options.Npcs; Title = options.Name; try { FireNewStatsEvent(options); RaidTotals = StatsUtil.CreatePlayerStats(Labels.RAID); HealingGroups.Clear(); Selected.ForEach(npc => StatsUtil.UpdateTimeDiffs(RaidTotals, npc, HEAL_OFFSET)); RaidTotals.TotalSeconds = RaidTotals.TimeDiffs.Sum(); if (RaidTotals.BeginTimes.Count > 0 && RaidTotals.BeginTimes.Count == RaidTotals.LastTimes.Count) { for (int i = 0; i < RaidTotals.BeginTimes.Count; i++) { var updatedHeals = new List <ActionBlock>(); var heals = DataManager.Instance.GetHealsDuring(RaidTotals.BeginTimes[i], RaidTotals.LastTimes[i]); heals.ForEach(heal => { var updatedHeal = new ActionBlock() { BeginTime = heal.BeginTime }; updatedHeal.Actions.AddRange(heal.Actions.Where(item => item is HealRecord record && IsValidHeal(record))); if (updatedHeal.Actions.Count > 0) { updatedHeals.Add(updatedHeal); } }); if (updatedHeals.Count > 0) { HealingGroups.Add(updatedHeals); } } ComputeHealingStats(options); } else if (Selected == null || Selected.Count == 0) { FireNoDataEvent(options, "NONPC"); } else { FireNoDataEvent(options, "NODATA"); } } catch (ArgumentNullException ne) { LOG.Error(ne); } catch (NullReferenceException nr) { LOG.Error(nr); } catch (ArgumentOutOfRangeException aor) { LOG.Error(aor); } catch (ArgumentException ae) { LOG.Error(ae); } catch (OutOfMemoryException oem) { LOG.Error(oem); } } }
public static void Process(LineData lineData) { try { string[] split = lineData.Action.Split(' '); if (split != null && split.Length > 2) { // [Sun Mar 01 22:20:36 2020] A shaded torch has been awakened by Drogbaa. // [Sun Mar 01 22:34:58 2020] You have entered The Eastern Wastes. // [Sun Mar 01 20:35:55 2020] The master looter, Qulas, looted 32426 platinum from the corpse. // [Sun Mar 01 23:51:02 2020] You receive 129 platinum, 2 gold and 1 copper as your split (with a lucky bonus). // [Sun Feb 02 22:43:51 2020] You receive 28 platinum, 7 gold, 2 silver and 5 copper as your split. // [Sun Feb 02 23:31:23 2020] You receive 57 platinum as your split. // [Fri Feb 07 22:01:20 2020] --Kizant has looted a Lesser Engraved Velium Rune from Velden Dragonbane's corpse.-- // [Sat Feb 08 01:20:26 2020] --Proximoe has looted a Velium Infused Spider Silk from a restless devourer's corpse.-- // [Sat Feb 08 21:21:36 2020] --You have looted a Cold-Forged Cudgel from Queen Dracnia's corpse.-- string looter = null; int awakenedIndex = -1; int lootedIndex = -1; int masterLootIndex = -1; int receiveIndex = -1; bool handled = false; for (int i = 0; i < split.Length && !handled; i++) { if (i == 0 && split[0].StartsWith("--")) { looter = split[0] == "--You" ? ConfigUtil.PlayerName : split[0].TrimStart('-'); } else { switch (split[i]) { case "awakened": awakenedIndex = i; break; case "looted": lootedIndex = i; break; case "looter,": masterLootIndex = (i == 2 && split[1] == "master" && split[0] == "The") ? masterLootIndex = i + 1 : -1; break; case "receive": receiveIndex = (i == 1 && split[0] == "You") ? i : -1; break; case "by": if (awakenedIndex > -1 && awakenedIndex == (i - 1) && split.Length > 5 && split[i - 2] == "been" && split[i - 3] == "has") { string awakened = string.Join(" ", split, 0, i - 3); string breaker = string.Join(" ", split, i + 1, split.Length - i - 1).TrimEnd('.'); DataManager.Instance.AddMiscRecord(new MezBreakRecord() { Breaker = breaker, Awakened = awakened }, DateUtil.ParseLogDate(lineData.Line)); handled = true; } break; case "entered": if (i == 2 && split[1] == "have" && split[0] == "You" && split.Length > 2) { string zone = string.Join(" ", split, 3, split.Length - 3).TrimEnd('.'); DataManager.Instance.AddMiscRecord(new ZoneRecord() { Zone = zone }, DateUtil.ParseLogDate(lineData.Line)); handled = true; } break; case "from": if (masterLootIndex > -1 && lootedIndex > masterLootIndex && split.Length > lootedIndex + 1 && split.Length > 3) { string name = split[3].TrimEnd(','); if (ParseCurrency(split, lootedIndex + 1, i, out string item, out uint count)) { PlayerManager.Instance.AddVerifiedPlayer(name); LootRecord record = new LootRecord() { Item = item, Player = name, Quantity = count, IsCurrency = true }; DataManager.Instance.AddLootRecord(record, DateUtil.ParseLogDate(lineData.Line)); handled = true; } } else if (!string.IsNullOrEmpty(looter) && lootedIndex == 2 && split.Length > 4) { // covers "a" or "an" uint count = split[3][0] == 'a' ? 1 : StatsUtil.ParseUInt(split[3]); string item = string.Join(" ", split, 4, i - 4); string npc = string.Join(" ", split, i + 1, split.Length - i - 1).TrimEnd(LootedFromTrim).Replace("'s corpse", ""); if (count > 0 && count != ushort.MaxValue) { PlayerManager.Instance.AddVerifiedPlayer(looter); LootRecord record = new LootRecord() { Item = item, Player = looter, Quantity = count, IsCurrency = false, Npc = npc }; DataManager.Instance.AddLootRecord(record, DateUtil.ParseLogDate(lineData.Line)); handled = true; } } break; case "split": if (receiveIndex > -1 && split[i - 1] == "your" && split[i - 2] == "as") { if (ParseCurrency(split, 2, i - 2, out string item, out uint count)) { LootRecord record = new LootRecord() { Item = item, Player = ConfigUtil.PlayerName, Quantity = count, IsCurrency = true }; DataManager.Instance.AddLootRecord(record, DateUtil.ParseLogDate(lineData.Line)); handled = true; } } break; } } } } } catch (ArgumentNullException ne) { LOG.Error(ne); } catch (NullReferenceException nr) { LOG.Error(nr); } catch (ArgumentOutOfRangeException aor) { LOG.Error(aor); } catch (ArgumentException ae) { LOG.Error(ae); } }
private void HandleDamageProcessed(object sender, DamageProcessedEvent processed) { if (LastFightProcessTime != processed.BeginTime) { DataManager.Instance.CheckExpireFights(processed.BeginTime); ValidCombo.Clear(); if (processed.BeginTime - LastFightProcessTime > RECENTSPELLTIME) { RecentSpellCache.Clear(); } } // cache recent player spells to help determine who the caster was var isAttackerPlayer = PlayerManager.Instance.IsPetOrPlayer(processed.Record.Attacker) || processed.Record.Attacker == Labels.RS; if (isAttackerPlayer && (processed.Record.Type == Labels.DD || processed.Record.Type == Labels.DOT || processed.Record.Type == Labels.PROC)) { RecentSpellCache[processed.Record.SubType] = true; } string comboKey = processed.Record.Attacker + "=" + processed.Record.Defender; if (ValidCombo.TryGetValue(comboKey, out bool defender) || IsValidAttack(processed.Record, isAttackerPlayer, out defender)) { ValidCombo[comboKey] = defender; bool isNonTankingFight = false; string origTimeString = processed.OrigTimeString.Substring(4, 15); // fix for unknown spells having a good name to work from if (processed.Record.AttackerIsSpell && defender) { processed.Record.Attacker = Labels.UNK; } Fight fight = Get(processed.Record, processed.BeginTime, origTimeString, defender); if (defender) { Helpers.AddAction(fight.DamageBlocks, processed.Record, processed.BeginTime); AddPlayerTime(fight, processed.Record, processed.Record.Attacker, processed.BeginTime); fight.BeginDamageTime = double.IsNaN(fight.BeginDamageTime) ? processed.BeginTime : fight.BeginDamageTime; fight.LastDamageTime = processed.BeginTime; if (StatsUtil.IsHitType(processed.Record.Type)) { fight.DamageHits++; fight.Total += processed.Record.Total; isNonTankingFight = fight.DamageHits == 1; var attacker = processed.Record.AttackerOwner ?? processed.Record.Attacker; if (fight.PlayerTotals.TryGetValue(attacker, out FightTotalDamage total)) { total.Damage += (processed.Record.Type == Labels.BANE) ? 0 : processed.Record.Total; total.DamageWithBane += processed.Record.Total; total.Name = processed.Record.Attacker; total.PetOwner = total.PetOwner ?? processed.Record.AttackerOwner; total.UpdateTime = processed.BeginTime; } else { fight.PlayerTotals[attacker] = new FightTotalDamage { Damage = (processed.Record.Type == Labels.BANE) ? 0 : processed.Record.Total, DamageWithBane = processed.Record.Total, Name = processed.Record.Attacker, PetOwner = processed.Record.AttackerOwner, UpdateTime = processed.BeginTime, BeginTime = processed.BeginTime }; } SpellDamageStats stats = null; var spellKey = processed.Record.Attacker + "++" + processed.Record.SubType; if (processed.Record.Type == Labels.DD) { if (!fight.DDDamage.TryGetValue(spellKey, out stats)) { stats = new SpellDamageStats { Caster = processed.Record.Attacker, Spell = processed.Record.SubType }; fight.DDDamage[spellKey] = stats; } } else if (processed.Record.Type == Labels.DOT) { if (!fight.DoTDamage.TryGetValue(spellKey, out stats)) { stats = new SpellDamageStats { Caster = processed.Record.Attacker, Spell = processed.Record.SubType }; fight.DoTDamage[spellKey] = stats; } } if (stats != null) { stats.Count += 1; stats.Max = Math.Max(processed.Record.Total, stats.Max); stats.Total += processed.Record.Total; } // only a pet can 'hit' with a Flurry since players only crush/slash/punch/pierce with main hand weapons if (processed.Record.AttackerOwner == null && processed.Record.Type == Labels.MELEE && processed.Record.SubType == "Hits" && LineModifiersParser.IsFlurry(processed.Record.ModifiersMask)) { PlayerManager.Instance.AddVerifiedPet(processed.Record.Attacker); } } } else { Helpers.AddAction(fight.TankingBlocks, processed.Record, processed.BeginTime); AddPlayerTime(fight, processed.Record, processed.Record.Defender, processed.BeginTime); fight.BeginTankingTime = double.IsNaN(fight.BeginTankingTime) ? processed.BeginTime : fight.BeginTankingTime; fight.LastTankingTime = processed.BeginTime; if (StatsUtil.IsHitType(processed.Record.Type)) { fight.TankHits++; } } fight.LastTime = processed.BeginTime; LastFightProcessTime = processed.BeginTime; var ttl = fight.LastTime - fight.BeginTime + 1; fight.TooltipText = string.Format(CultureInfo.CurrentCulture, "#Hits To Players: {0}, #Hits From Players: {1}, Time Alive: {2}s", fight.TankHits, fight.DamageHits, ttl); DataManager.Instance.UpdateIfNewFightMap(fight.CorrectMapKey, fight, isNonTankingFight); } }
public static void Process(LineData lineData) { bool handled = false; try { string[] split = lineData.Action.Split(' '); if (split != null && split.Length >= 2) { int stop = split.Length - 1; if (!string.IsNullOrEmpty(split[stop]) && split[stop][split[stop].Length - 1] == ')') { for (int i = stop; i >= 0 && stop > 2; i--) { if (!string.IsNullOrEmpty(split[i]) && split[i][0] == '(') { stop = i - 1; break; } } } // see if it's a died message right away if (split.Length > 1 && stop >= 1 && split[stop] == "died.") { var test = string.Join(" ", split, 0, stop); if (!string.IsNullOrEmpty(test)) { UpdateSlain(test, "", lineData); handled = true; } } if (!handled) { int byIndex = -1, forIndex = -1, pointsOfIndex = -1, endDamage = -1, byDamage = -1, extraIndex = -1; int fromDamage = -1, hasIndex = -1, haveIndex = -1, hitType = -1, hitTypeAdd = -1, slainIndex = -1; int takenIndex = -1, tryIndex = -1, yourIndex = -1, isIndex = -1, dsIndex = -1, butIndex = -1; int missType = -1, nonMeleeIndex = -1; string subType = null; bool found = false; for (int i = 0; i <= stop && !found; i++) { if (!string.IsNullOrEmpty(split[i])) { switch (split[i]) { case "healed": found = true; // short circuit break; case "but": butIndex = i; break; case "is": case "was": isIndex = i; break; case "has": hasIndex = i; break; case "have": haveIndex = i; break; case "by": byIndex = i; if (slainIndex > -1) { found = true; // short circut } else if (i > 4 && split[i - 1] == "damage") { byDamage = i - 1; } break; case "from": if (i > 3 && split[i - 1] == "damage") { fromDamage = i - 1; if (pointsOfIndex > -1 && extraIndex > -1) { found = true; // short circut } else if (stop > (i + 1) && split[i + 1] == "your") { yourIndex = i + 1; } } break; case "damage.": if (i == stop) { endDamage = i; } break; case "non-melee": nonMeleeIndex = i; if (i > 9 && stop == (i + 1) && split[i + 1] == "damage." && pointsOfIndex == (i - 2) && forIndex == (i - 4)) { dsIndex = i - 5; found = true; // short circut } break; case "point": case "points": if (stop >= (i + 1) && split[i + 1] == "of") { pointsOfIndex = i; if (i > 2 && split[i - 2] == "for") { forIndex = i - 2; } } break; case "blocks!": missType = (stop == i && butIndex > -1 && i > tryIndex) ? 0 : missType; break; case "shield!": case "staff!": missType = (i > 5 && stop == i && butIndex > -1 && i > tryIndex && split[i - 2] == "with" && split[i - 3].StartsWith("block", StringComparison.OrdinalIgnoreCase)) ? 0 : missType; break; case "dodge!": case "dodges!": missType = (stop == i && butIndex > -1 && i > tryIndex) ? 1 : missType; break; case "miss!": case "misses!": missType = (stop == i && butIndex > -1 && i > tryIndex) ? 2 : missType; break; case "parries!": missType = (stop == i && butIndex > -1 && i > tryIndex) ? 3 : missType; break; case "INVULNERABLE!": missType = (stop == i && butIndex > -1 && i > tryIndex) ? 4 : missType; break; case "slain": slainIndex = i; break; case "taken": if (i > 1 && (hasIndex == (i - 1) || haveIndex == (i - 1))) { takenIndex = i - 1; if (stop > (i + 2) && split[i + 1] == "an" && split[i + 2] == "extra") { extraIndex = i + 2; } } break; // Old (EQEMU) crit and crippling blow handling case "hit!": if (stop == i && split.Length > 4 && split[i - 1] == "critical" && split[i - 3] == "scores") { LastCrit = new OldCritData { Attacker = split[0], LineData = lineData }; } break; case "Crippling": if (stop == (i + 1) && split.Length > 4 && split[i + 1].StartsWith("Blow!") && split[i - 2] == "lands") { LastCrit = new OldCritData { Attacker = split[0], LineData = lineData }; } break; default: if (slainIndex == -1 && i > 0 && string.IsNullOrEmpty(subType) && HitMap.TryGetValue(split[i], out subType)) { hitType = i; if (i < stop && HitAdditionalMap.ContainsKey(split[i])) { hitTypeAdd = i + i; } if (i > 2 && split[i - 1] == "to" && (split[i - 2] == "tries" || split[i - 2] == "try")) { tryIndex = i - 2; } } break; } } } // [Sun Apr 18 19:36:39 2021] Tantor is pierced by Tolzol's thorns for 6718 points of non-melee damage. // [Mon Apr 19 22:02:52 2021] Honvar is tormented by Reisil's frost for 7809 points of non-melee damage. // [Sun Apr 25 13:47:12 2021] Test One Hundred Three is burned by YOUR flames for 5224 points of non-melee damage. // [Sun Apr 18 14:16:13 2021] A failed reclaimer is pierced by YOUR thorns for 193 points of non-melee damage. if (dsIndex > -1 && pointsOfIndex > dsIndex && isIndex > -1 && isIndex < dsIndex && byIndex > isIndex) { string attacker = string.Join(" ", split, byIndex + 1, dsIndex - byIndex - 1); if (!string.IsNullOrEmpty(attacker)) { var valid = attacker == "YOUR"; if (!valid && attacker.EndsWith("'s", StringComparison.OrdinalIgnoreCase)) { attacker = attacker.Substring(0, attacker.Length - 2); valid = true; } if (valid) { string defender = string.Join(" ", split, 0, isIndex); uint damage = StatsUtil.ParseUInt(split[pointsOfIndex - 1]); handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, Labels.DS, Labels.DS); } } } // [Mon May 10 22:18:46 2021] A dendridic shard was chilled to the bone for 410 points of non-melee damage. else if (dsIndex > -1 && pointsOfIndex > dsIndex && isIndex > -1 && isIndex < dsIndex && byIndex == -1) { string defender = string.Join(" ", split, 0, isIndex); uint damage = StatsUtil.ParseUInt(split[pointsOfIndex - 1]); handled = CreateDamageRecord(lineData, split, stop, Labels.RS, defender, damage, Labels.DS, Labels.DS); } // [Tue Mar 26 22:43:47 2019] a wave sentinel has taken an extra 6250000 points of non-melee damage from Kazint's Greater Fetter spell. else if (extraIndex > -1 && pointsOfIndex == (extraIndex + 2) && fromDamage == (pointsOfIndex + 3) && split[stop] == "spell.") { if (split[fromDamage + 2].EndsWith("'s", StringComparison.OrdinalIgnoreCase)) { string attacker = split[fromDamage + 2].Substring(0, split[fromDamage + 2].Length - 2); string defender = string.Join(" ", split, 0, takenIndex); uint damage = StatsUtil.ParseUInt(split[extraIndex + 1]); string spell = string.Join(" ", split, fromDamage + 3, stop - fromDamage - 3); var spellData = DataManager.Instance.GetDamagingSpellByName(spell); SpellResist resist = spellData != null ? spellData.Resist : SpellResist.UNDEFINED; handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, Labels.BANE, spell, resist); } } // [Sun Apr 18 21:26:15 2021] Astralx crushes Sontalak for 126225 points of damage. (Strikethrough Critical) // [Sun Apr 18 20:20:32 2021] Susarrak the Crusader claws Villette for 27699 points of damage. (Strikethrough Wild Rampage) else if (!string.IsNullOrEmpty(subType) && endDamage > -1 && pointsOfIndex == (endDamage - 2) && forIndex > -1 && hitType < forIndex) { int hitTypeMod = hitTypeAdd > 0 ? 1 : 0; string attacker = string.Join(" ", split, 0, hitType); string defender = string.Join(" ", split, hitType + hitTypeMod + 1, forIndex - hitType - hitTypeMod - 1); subType = TextFormatUtils.ToUpper(subType); uint damage = StatsUtil.ParseUInt(split[pointsOfIndex - 1]); handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, Labels.MELEE, subType); } // [Sun Apr 18 20:24:56 2021] Sonozen hit Jortreva the Crusader for 38948 points of fire damage by Burst of Flames. (Lucky Critical Twincast) else if (byDamage > 3 && pointsOfIndex == (byDamage - 3) && byIndex == (byDamage + 1) && forIndex > -1 && subType == "hits" && hitType < forIndex && split[stop].Length > 0 && split[stop][split[stop].Length - 1] == '.') { string spell = string.Join(" ", split, byIndex + 1, stop - byIndex); if (!string.IsNullOrEmpty(spell) && spell[spell.Length - 1] == '.') { spell = spell.Substring(0, spell.Length - 1); string attacker = string.Join(" ", split, 0, hitType); string defender = string.Join(" ", split, hitType + 1, forIndex - hitType - 1); string type = GetTypeFromSpell(spell, Labels.DD); uint damage = StatsUtil.ParseUInt(split[pointsOfIndex - 1]); SpellResist resist = SpellResist.UNDEFINED; SpellResistMap.TryGetValue(split[byDamage - 1], out resist); // extra way to check for pets if (spell.StartsWith("Elemental Conversion", StringComparison.Ordinal)) { PlayerManager.Instance.AddVerifiedPet(defender); } handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, type, spell, resist); } } // [Sun Apr 18 20:32:39 2021] Dovhesi has taken 173674 damage from Curse of the Shrine by Grendish the Crusader. // [Sun Apr 18 20:32:42 2021] Grendish the Crusader has taken 1003231 damage from Pyre of Klraggek Rk. III by Atvar. (Lucky Critical) // [Thu Mar 18 18:48:10 2021] You have taken 4852 damage from Nectar of Misery by Commander Gartik. // [Thu Mar 18 01:05:46 2021] A gnoll has taken 108790 damage from your Mind Coil Rk. II. // Old (eqemu) [Sat Jan 15 21:09:10 2022] Pixtt Invi Mal has taken 189 damage from Goanna by Tuyen`s Chant of Fire. else if (fromDamage > 3 && takenIndex == (fromDamage - 3) && (byIndex > fromDamage || yourIndex > fromDamage)) { string attacker = null; string spell = null; if (byIndex > -1) { attacker = string.Join(" ", split, byIndex + 1, stop - byIndex); attacker = (!string.IsNullOrEmpty(attacker) && attacker[attacker.Length - 1] == '.') ? attacker.Substring(0, attacker.Length - 1) : null; spell = string.Join(" ", split, fromDamage + 2, byIndex - fromDamage - 2); } else if (yourIndex > -1) { attacker = split[yourIndex]; spell = string.Join(" ", split, yourIndex + 1, stop - yourIndex); spell = (!string.IsNullOrEmpty(spell) && spell[spell.Length - 1] == '.') ? spell.Substring(0, spell.Length - 1) : Labels.DOT; } if (!string.IsNullOrEmpty(attacker) && !string.IsNullOrEmpty(spell)) { string type; SpellData spellData = DataManager.Instance.GetDamagingSpellByName(spell); // Old (eqemu) if attacker is actually a spell then swap attacker and spell // Spells dont change on eqemu servers so this should always be a spell even with old spell data if (spellData == null && DataManager.Instance.IsOldSpell(attacker)) { // check that we can't find a spell where the player name is var temp = attacker; attacker = spell; spell = temp; type = Labels.DOT; } else { type = GetTypeFromSpell(spell, Labels.DOT); } string defender = string.Join(" ", split, 0, takenIndex); uint damage = StatsUtil.ParseUInt(split[fromDamage - 1]); SpellResist resist = spellData != null ? spellData.Resist : SpellResist.UNDEFINED; handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, type, spell, resist); } } // [Mon Apr 26 21:07:21 2021] Lawlstryke has taken 216717 damage by Wisp Explosion. else if (byDamage > -1 && takenIndex == (byDamage - 3)) { string defender = string.Join(" ", split, 0, takenIndex); uint damage = StatsUtil.ParseUInt(split[byDamage - 1]); string spell = string.Join(" ", split, byDamage + 2, stop - byDamage - 1); if (!string.IsNullOrEmpty(spell) && spell[spell.Length - 1] == '.') { spell = spell.Substring(0, spell.Length - 1); } SpellResist resist = SpellResist.UNDEFINED; if (DataManager.Instance.GetDamagingSpellByName(spell) is SpellData spellData && spellData != null) { resist = spellData.Resist; } handled = CreateDamageRecord(lineData, split, stop, "", defender, damage, Labels.DOT, spell, resist, true); } // Old (eqemu direct damage) [Sat Jan 15 21:08:54 2022] Jaun hit Pixtt Invi Mal for 150 points of non-melee damage. else if (hitType > -1 && forIndex > -1 && forIndex < pointsOfIndex && nonMeleeIndex > pointsOfIndex) { int hitTypeMod = hitTypeAdd > 0 ? 1 : 0; string attacker = string.Join(" ", split, 0, hitType); string defender = string.Join(" ", split, hitType + hitTypeMod + 1, forIndex - hitType - hitTypeMod - 1); uint damage = StatsUtil.ParseUInt(split[pointsOfIndex - 1]); handled = CreateDamageRecord(lineData, split, stop, attacker, defender, damage, Labels.DD, Labels.DD); } // [Mon Aug 05 02:05:12 2019] An enchanted Syldon stalker tries to crush YOU, but misses! (Strikethrough) // [Sat Aug 03 00:20:57 2019] You try to crush a Kar`Zok soldier, but miss! (Riposte Strikethrough) // [Sat Apr 24 01:08:49 2021] Test One Hundred Three tries to punch Kazint, but misses! // [Sat Apr 24 01:08:49 2021] Test One Hundred Three tries to punch Kazint, but Kazint dodges! // [Sat Apr 24 01:10:17 2021] Test One Hundred Three tries to punch YOU, but YOU dodge! // [Sat Apr 24 01:10:17 2021] Kazint tries to crush Test One Hundred Three, but Test One Hundred Three dodges! // [Sun Apr 18 19:45:21 2021] You try to crush a primal guardian, but a primal guardian parries! // [Mon May 31 20:29:49 2021] A bloodthirsty gnawer tries to bite Vandil, but Vandil parries! // [Sun Apr 25 22:56:22 2021] Romance tries to bash Vulak`Aerr, but Vulak`Aerr parries! // [Sun Jul 28 20:12:46 2019] Drogbaa tries to slash Whirlrender Scout, but misses! (Strikethrough) // [Tue Mar 30 16:43:54 2021] You try to crush a desert madman, but a desert madman blocks! // [Mon Apr 26 22:40:10 2021] An ancient warden tries to hit Reisil, but Reisil blocks with his shield! // [Sun Mar 21 00:11:31 2021] A carrion bat tries to bite YOU, but YOU block with your shield! // [Mon Apr 26 14:51:01 2021] A windchill sprite tries to smash YOU, but YOU block with your staff! // [Mon May 10 22:18:46 2021] Tolzol tries to crush Dendritic Golem, but Dendritic Golem is INVULNERABLE! else if (tryIndex > -1 && butIndex > tryIndex && missType > -1) { string label = null; switch (missType) { case 0: label = Labels.BLOCK; break; case 1: label = Labels.DODGE; break; case 2: label = Labels.MISS; break; case 3: label = Labels.PARRY; break; case 4: label = Labels.INVULNERABLE; break; } if (!string.IsNullOrEmpty(label)) { int hitTypeMod = hitTypeAdd > 0 ? 1 : 0; string defender = string.Join(" ", split, hitType + hitTypeMod + 1, butIndex - hitType - hitTypeMod - 1); if (!string.IsNullOrEmpty(defender) && defender[defender.Length - 1] == ',') { defender = defender.Substring(0, defender.Length - 1); string attacker = string.Join(" ", split, 0, tryIndex); subType = TextFormatUtils.ToUpper(subType); handled = CreateDamageRecord(lineData, split, stop, attacker, defender, 0, label, subType); } } } // [Sun Apr 18 21:26:20 2021] Strangle`s pet has been slain by Kzerk! else if (slainIndex > -1 && byIndex == (slainIndex + 1) && hasIndex > 0 && stop > (slainIndex + 1) && split[hasIndex + 1] == "been") { string killer = string.Join(" ", split, byIndex + 1, stop - byIndex); killer = killer.Length > 1 && killer[killer.Length - 1] == '!' ? killer.Substring(0, killer.Length - 1) : killer; string slain = string.Join(" ", split, 0, hasIndex); handled = UpdateSlain(slain, killer, lineData); HasOwner(slain, out string t1); HasOwner(killer, out string t2); } // [Mon Apr 19 02:22:09 2021] You have been slain by an armed flyer! else if (stop > 4 && slainIndex == 3 && byIndex == 4 && split[0] == "You" && split[1] == "have" && split[2] == "been") { string killer = string.Join(" ", split, 5, stop - 4); killer = killer.Length > 1 && killer[killer.Length - 1] == '!' ? killer.Substring(0, killer.Length - 1) : killer; string slain = ConfigUtil.PlayerName; handled = UpdateSlain(slain, killer, lineData); } // [Mon Apr 19 02:22:09 2021] You have slain a failed bodyguard! else if (slainIndex == 2 && split[0] == "You" && split[1] == "have") { string killer = ConfigUtil.PlayerName; string slain = string.Join(" ", split, 3, stop - 2); slain = slain.Length > 1 && slain[slain.Length - 1] == '!' ? slain.Substring(0, slain.Length - 1) : slain; handled = UpdateSlain(slain, killer, lineData); } } } } catch (Exception e) { LOG.Error(e); } DebugUtil.UnregisterLine(lineData.LineNumber, handled); }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (TankingGroups) { Selected = options.Npcs; Title = options.Name; try { FireNewStatsEvent(options); RaidTotals = StatsUtil.CreatePlayerStats(Labels.RAID); TankingGroups.Clear(); var damageBlocks = new List <ActionBlock>(); Selected.ForEach(fight => { StatsUtil.UpdateTimeDiffs(RaidTotals, fight); damageBlocks.AddRange(fight.TankingBlocks); }); damageBlocks.Sort((a, b) => a.BeginTime.CompareTo(b.BeginTime)); if (damageBlocks.Count > 0) { RaidTotals.TotalSeconds = RaidTotals.TimeDiffs.Sum(); var newBlock = new List <ActionBlock>(); var timeIndex = 0; damageBlocks.ForEach(block => { if (block.BeginTime > RaidTotals.LastTimes[timeIndex]) { timeIndex++; if (newBlock.Count > 0) { TankingGroups.Add(newBlock); } newBlock = new List <ActionBlock>(); } newBlock.Add(block); }); TankingGroups.Add(newBlock); ComputeTankingStats(options); } else if (Selected == null || Selected.Count == 0) { FireNoDataEvent(options, "NONPC"); } else { FireNoDataEvent(options, "NODATA"); } } catch (ArgumentNullException ne) { LOG.Error(ne); } catch (NullReferenceException nr) { LOG.Error(nr); } catch (ArgumentOutOfRangeException aor) { LOG.Error(aor); } catch (ArgumentException ae) { LOG.Error(ae); } catch (OutOfMemoryException oem) { LOG.Error(oem); } } }
private static void UpdateSubStats(PlayerSubStats subStats, DamageRecord record, double beginTime) { StatsUtil.UpdateStats(subStats, record, beginTime); }
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)); } }); } }
private static HealRecord HandleHealed(ProcessLine pline) { // [Sun Feb 24 21:00:58 2019] Foob's promised interposition is fulfilled Foob healed himself for 44238 hit points by Promised Interposition Heal V. (Lucky Critical) // [Sun Feb 24 21:01:01 2019] Rowanoak is soothed by Brell's Soothing Wave. Farzi healed Rowanoak for 524 hit points by Brell's Sacred Soothing Wave. // [Sun Feb 24 21:00:52 2019] Kuvani healed Tolzol over time for 11000 hit points by Spirit of the Wood XXXIV. // [Sun Feb 24 21:00:52 2019] Kuvani healed Foob over time for 9409 (11000) hit points by Spirit of the Wood XXXIV. // [Sun Feb 24 21:00:58 2019] Fllint healed Foob for 11820 hit points by Blessing of the Ancients III. // [Sun Feb 24 21:01:00 2019] Tolzol healed itself for 548 hit points. // [Sun Feb 24 21:01:01 2019] Piemastaj`s pet has been healed for 15000 hit points by Enhanced Theft of Essence Effect X. // [Sun Feb 24 23:30:51 2019] Piemastaj`s pet glows with holy light. Findawenye healed Piemastaj`s pet for 2823 (78079) hit points by Mending Splash Rk. III. (Critical) // [Mon Feb 18 21:21:12 2019] Nylenne has been healed over time for 8211 hit points by Roar of the Lion 6. // [Mon Feb 18 21:20:39 2019] You have been healed over time for 1063 (8211) hit points by Roar of the Lion 6. // [Mon Feb 18 21:17:35 2019] Snowzz healed Malkatar over time for 8211 hit points by Roar of the Lion 6. // [Wed Nov 06 14:19:54 2019] Your ward heals you as it breaks! You healed Niktaza for 8970 (86306) hit points by Healing Ward. (Critical) HealRecord record = null; string part = pline.ActionPart; int optional = pline.OptionalIndex; string test = part.Substring(0, optional); bool done = false; string healer = ""; string healed = ""; string spell = null; string type = Labels.HEAL; uint heal = 0; uint overHeal = 0; int previous = test.Length >= 2 ? test.LastIndexOf(" ", test.Length - 2, StringComparison.Ordinal) : -1; if (previous > -1) { if (test.IndexOf("are ", previous + 1, StringComparison.Ordinal) > -1) { done = true; } else if (previous - 1 >= 0 && (test[previous - 1] == '.' || test[previous - 1] == '!') || previous - 9 > 0 && test.IndexOf("fulfilled", previous - 9, StringComparison.Ordinal) > -1) { healer = test.Substring(previous + 1); } else if (previous - 4 >= 0 && test.IndexOf("has been", previous - 3, StringComparison.Ordinal) > -1) { healed = test.Substring(0, previous - 4); if (part.Length > optional + 17 && part.IndexOf("over time", optional + 8, 9, StringComparison.Ordinal) > -1) { type = Labels.HOT; } } else if (previous - 5 >= 0 && test.IndexOf("have been", previous - 4, StringComparison.Ordinal) > -1) { healed = test.Substring(0, previous - 5); if (part.Length > optional + 17 && part.IndexOf("over time", optional + 8, 9, StringComparison.Ordinal) > -1) { type = Labels.HOT; } } } else { healer = test.Substring(0, optional); } if (!done) { int amountIndex = -1; if (healed.Length == 0) { int afterHealed = optional + 8; int forIndex = part.IndexOf(" for ", afterHealed, StringComparison.Ordinal); if (forIndex > 1) { if (forIndex - 9 >= 0 && part.IndexOf("over time", forIndex - 9, StringComparison.Ordinal) > -1) { type = Labels.HOT; healed = part.Substring(afterHealed, forIndex - afterHealed - 10); } else { healed = part.Substring(afterHealed, forIndex - afterHealed); } amountIndex = forIndex + 5; } } else { if (type == Labels.HEAL) { amountIndex = optional + 12; } else if (type == Labels.HOT) { amountIndex = optional + 22; } } if (amountIndex > -1) { int amountEnd = part.IndexOf(" ", amountIndex, StringComparison.Ordinal); if (amountEnd > -1) { uint value = StatsUtil.ParseUInt(part.Substring(amountIndex, amountEnd - amountIndex)); if (value != uint.MaxValue) { heal = value; } int overEnd = -1; if (part.Length > amountEnd + 1 && part[amountEnd + 1] == '(') { overEnd = part.IndexOf(")", amountEnd + 2, StringComparison.Ordinal); if (overEnd > -1) { uint value2 = StatsUtil.ParseUInt(part.Substring(amountEnd + 2, overEnd - amountEnd - 2)); if (value2 != uint.MaxValue) { overHeal = value2; } } } int rest = overEnd > -1 ? overEnd : amountEnd; int byIndex = part.IndexOf(" by ", rest, StringComparison.Ordinal); if (byIndex > -1) { int periodIndex = part.LastIndexOf(".", StringComparison.Ordinal); if (periodIndex > -1 && periodIndex - byIndex - 4 > 0) { spell = part.Substring(byIndex + 4, periodIndex - byIndex - 4); } } } } if (healed.Length > 0) { // check for pets int possessive = healed.IndexOf("`s ", StringComparison.Ordinal); if (possessive > -1) { if (PlayerManager.Instance.IsVerifiedPlayer(healed.Substring(0, possessive))) { PlayerManager.Instance.AddVerifiedPet(healed); } // dont count swarm pets healer = ""; heal = 0; } if (healer.Length > 0 && heal != 0) { record = new HealRecord() { Total = heal, OverTotal = overHeal, Healer = string.Intern(healer), Healed = string.Intern(healed), Type = string.Intern(type), ModifiersMask = -1 }; record.SubType = string.IsNullOrEmpty(spell) ? Labels.SELFHEAL : string.Intern(spell); if (part[part.Length - 1] == ')') { // using 4 here since the shortest modifier should at least be 3 even in the future. probably. int firstParen = part.LastIndexOf('(', part.Length - 4); if (firstParen > -1) { record.ModifiersMask = LineModifiersParser.Parse(part.Substring(firstParen + 1, part.Length - 1 - firstParen - 1)); if (LineModifiersParser.IsTwincast(record.ModifiersMask)) { PlayerManager.Instance.AddVerifiedPlayer(record.Healer); } } } } } } return(record); }
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 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); }
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, }); }
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); } } }
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> >(); AllDamageGroups.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); } } } }); }); });
internal void BuildTotalStats(GenerateStatsOptions options) { lock (DamageGroups) { Selected = options.Npcs; Title = options.Name; try { FireNewStatsEvent(options); RaidTotals = StatsUtil.CreatePlayerStats(Labels.RAID); DamageGroups.Clear(); DamageGroupIds.Clear(); PlayerHasPet.Clear(); PetToPlayer.Clear(); Resists.Clear(); var damageBlocks = new List <ActionBlock>(); Selected.ForEach(fight => { StatsUtil.UpdateTimeDiffs(RaidTotals, fight); damageBlocks.AddRange(fight.DamageBlocks); if (fight.GroupId > -1) { DamageGroupIds[fight.GroupId] = 1; } }); damageBlocks.Sort((a, b) => a.BeginTime.CompareTo(b.BeginTime)); if (damageBlocks.Count > 0) { RaidTotals.TotalSeconds = RaidTotals.TimeDiffs.Sum(); var newBlock = new List <ActionBlock>(); var timeIndex = 0; damageBlocks.ForEach(block => { if (block.BeginTime > RaidTotals.LastTimes[timeIndex]) { timeIndex++; if (newBlock.Count > 0) { DamageGroups.Add(newBlock); } newBlock = new List <ActionBlock>(); } newBlock.Add(block); block.Actions.ForEach(action => { DamageRecord damage = action as DamageRecord; // see if there's a pet mapping, check this first string pname = PlayerManager.Instance.GetPlayerFromPet(damage.Attacker); if (!string.IsNullOrEmpty(pname) || !string.IsNullOrEmpty(pname = damage.AttackerOwner)) { PlayerHasPet[pname] = 1; PetToPlayer[damage.Attacker] = pname; } }); }); DamageGroups.Add(newBlock); for (int i = 0; i < RaidTotals.BeginTimes.Count && i < RaidTotals.LastTimes.Count; i++) { var group = DataManager.Instance.GetResistsDuring(RaidTotals.BeginTimes[i], RaidTotals.LastTimes[i]); group.ForEach(block => Resists.AddRange(block.Actions)); } ComputeDamageStats(options); } else if (Selected == null || Selected.Count == 0) { FireNoDataEvent(options, "NONPC"); } else { FireNoDataEvent(options, "NODATA"); } } catch (ArgumentNullException ne) { LOG.Error(ne); } catch (NullReferenceException nr) { LOG.Error(nr); } catch (ArgumentOutOfRangeException aor) { LOG.Error(aor); } catch (ArgumentException ae) { LOG.Error(ae); } catch (OutOfMemoryException oem) { LOG.Error(oem); } } }
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); } } }
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 BuildGroups(PlayerStats playerStats, List <PlayerSubStats> all) { List <PlayerSubStats> list = new List <PlayerSubStats>(); PlayerSubStats dots = new PlayerSubStats() { Name = Labels.DOT, Type = Labels.DOT }; PlayerSubStats dds = new PlayerSubStats() { Name = Labels.DD, Type = Labels.DD }; PlayerSubStats procs = new PlayerSubStats() { Name = Labels.PROC, Type = Labels.PROC }; PlayerSubStats resisted = new PlayerSubStats() { Name = Labels.RESIST, Type = Labels.RESIST, ResistRate = 100 }; List <PlayerSubStats> allDots = new List <PlayerSubStats>(); List <PlayerSubStats> allDds = new List <PlayerSubStats>(); List <PlayerSubStats> allProcs = new List <PlayerSubStats>(); List <PlayerSubStats> allResisted = new List <PlayerSubStats>(); all.ForEach(sub => { PlayerSubStats stats = null; switch (sub.Type) { case Labels.DOT: stats = dots; allDots.Add(sub); break; case Labels.DD: case Labels.BANE: stats = dds; allDds.Add(sub); break; case Labels.PROC: stats = procs; allProcs.Add(sub); break; case Labels.RESIST: stats = resisted; allResisted.Add(sub); break; default: list.Add(sub); break; } StatsUtil.MergeStats(stats, sub); }); foreach (var stats in new PlayerSubStats[] { dots, dds, procs, resisted }) { StatsUtil.CalculateRates(stats, RaidStats, playerStats); } UnGroupedDD[playerStats.Name] = allDds; UnGroupedDoT[playerStats.Name] = allDots; UnGroupedProcs[playerStats.Name] = allProcs; UnGroupedResisted[playerStats.Name] = allResisted; GroupedDD[playerStats.Name] = dds; GroupedDoT[playerStats.Name] = dots; GroupedProcs[playerStats.Name] = procs; GroupedResisted[playerStats.Name] = resisted; OtherDamage[playerStats.Name] = list; Dispatcher.InvokeAsync(() => { if (allDds.Count > 0 && !groupDirectDamage.IsEnabled) { groupDirectDamage.IsEnabled = true; } if (allProcs.Count > 0 && !groupProcs.IsEnabled) { groupProcs.IsEnabled = true; } if (allDots.Count > 0 && !groupDoT.IsEnabled) { groupDoT.IsEnabled = true; } if (allResisted.Count > 0 && !groupResisted.IsEnabled) { groupResisted.IsEnabled = true; } }); }
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, }); }
internal void BuildTotalStats(GenerateStatsOptions options) { lock (TankingGroups) { try { FireNewStatsEvent(options); Reset(); Selected = options.Npcs; Title = options.Name; var damageBlocks = new List <ActionBlock>(); Selected.ForEach(fight => { damageBlocks.AddRange(fight.TankingBlocks); RaidTotals.Ranges.Add(new TimeSegment(fight.BeginTankingTime, fight.LastTankingTime)); StatsUtil.UpdateRaidTimeRanges(fight, PlayerTimeRanges, PlayerSubTimeRanges, true); }); damageBlocks.Sort((a, b) => a.BeginTime.CompareTo(b.BeginTime)); if (damageBlocks.Count > 0) { RaidTotals.TotalSeconds = RaidTotals.Ranges.GetTotal(); int rangeIndex = 0; var newBlock = new List <ActionBlock>(); damageBlocks.ForEach(block => { if (RaidTotals.Ranges.TimeSegments.Count > rangeIndex && block.BeginTime > RaidTotals.Ranges.TimeSegments[rangeIndex].EndTime) { rangeIndex++; if (newBlock.Count > 0) { TankingGroups.Add(newBlock); } newBlock = new List <ActionBlock>(); } newBlock.Add(block); }); TankingGroups.Add(newBlock); ComputeTankingStats(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); } } } }
public static void Process(LineData lineData) { bool handled = false; try { string[] split = lineData.Action.Split(' '); if (split != null && split.Length >= 2) { // [Sun Mar 01 22:20:36 2020] A shaded torch has been awakened by Drogbaa. // [Sun Mar 01 20:35:55 2020] The master looter, Qulas, looted 32426 platinum from the corpse. // [Sun Mar 01 23:51:02 2020] You receive 129 platinum, 2 gold and 1 copper as your split (with a lucky bonus). // [Sun Feb 02 22:43:51 2020] You receive 28 platinum, 7 gold, 2 silver and 5 copper as your split. // [Sun Feb 02 23:31:23 2020] You receive 57 platinum as your split. // [Fri Feb 07 22:01:20 2020] --Kizant has looted a Lesser Engraved Velium Rune from Velden Dragonbane's corpse.-- // [Sat Feb 08 01:20:26 2020] --Proximoe has looted a Velium Infused Spider Silk from a restless devourer's corpse.-- // [Sat Feb 08 21:21:36 2020] --You have looted a Cold-Forged Cudgel from Queen Dracnia's corpse.-- // [Mon Apr 27 22:32:04 2020] Restless Tijoely resisted your Stormjolt Vortex Effect! // [Mon Apr 27 20:51:22 2020] Kazint's Scorching Beam Rk. III spell has been reflected by a shadow reflection. // [Sun Mar 28 19:42:46 2021] A Draconic Lava Chain Feet Ornament was given to Aldryn. // [Mon Apr 05 19:42:24 2021] Hacket won the need roll on 1 item(s): Restless Velium Tainted Pelt with a roll of 996. string looter = null; int awakenedIndex = -1; int lootedIndex = -1; int masterLootIndex = -1; int receiveIndex = -1; int resistedIndex = -1; int isIndex = -1; int itemsIndex = -1; for (int i = 0; i < split.Length && !handled; i++) { if (i == 0 && split[0].StartsWith("--", StringComparison.OrdinalIgnoreCase)) { looter = split[0] == "--You" ? ConfigUtil.PlayerName : split[0].TrimStart('-'); } else { switch (split[i]) { case "awakened": awakenedIndex = i; break; case "is": isIndex = i; break; case "looted": lootedIndex = i; break; case "resisted": resistedIndex = i; break; case "item(s):": if (split.Length > 9 && split[1] == "won" && split[4] == "roll") { itemsIndex = i; } break; case "looter,": masterLootIndex = (i == 2 && split[1] == "master" && split[0] == "The") ? masterLootIndex = i + 1 : -1; break; case "receive": receiveIndex = (i == 1 && split[0] == "You") ? i : -1; break; case "with": if (itemsIndex > -1 && split.Length > i + 2 && split[i + 2] == "roll") { looter = split[0].Equals("you", StringComparison.OrdinalIgnoreCase) ? ConfigUtil.PlayerName : split[0]; string item = string.Join(" ", split, itemsIndex + 1, i - itemsIndex - 1); var currentTime = DateUtil.ParseLogDate(lineData.Line, out _); PlayerManager.Instance.AddVerifiedPlayer(looter, currentTime); LootRecord record = new LootRecord() { Item = item, Player = looter, Quantity = 0, IsCurrency = false, Npc = "Won Roll (Not Looted)" }; DataManager.Instance.AddLootRecord(record, currentTime); handled = true; } break; case "reflected": if (split.Length > 6 && i >= 6 && i + 2 < split.Length && split[0].StartsWith(ConfigUtil.PlayerName, StringComparison.Ordinal) && split[i - 1] == "been" && split[i - 2] == "has" && split[i - 3] == "spell" && split[i + 1] == "by") { // var spell = string.Join(" ", split, 1, i - 4); var npc = string.Join(" ", split, i + 2, split.Length - i - 2).TrimEnd('.'); DataManager.Instance.UpdateNpcSpellReflectStats(npc); handled = true; } break; case "your": if (resistedIndex > 0 && resistedIndex + 1 == i && split.Length > i + 1 && split[split.Length - 1].EndsWith("!", StringComparison.Ordinal)) { string npc = string.Join(" ", split, 0, resistedIndex); string spell = string.Join(" ", split, i + 1, split.Length - i - 1).TrimEnd('!'); DataManager.Instance.AddResistRecord(new ResistRecord { Defender = npc, Spell = spell }, DateUtil.ParseLogDate(lineData.Line, out _)); handled = true; } break; case "by": if (awakenedIndex > -1 && awakenedIndex == (i - 1) && split.Length > 5 && split[i - 2] == "been" && split[i - 3] == "has") { string awakened = string.Join(" ", split, 0, i - 3); string breaker = string.Join(" ", split, i + 1, split.Length - i - 1).TrimEnd('.'); DataManager.Instance.AddMiscRecord(new MezBreakRecord { Breaker = breaker, Awakened = awakened }, DateUtil.ParseLogDate(lineData.Line, out _)); handled = true; } else if (isIndex > 0 && StruckByTypes.ContainsKey(split[i - 1])) { // ignore common lines like: is struck by handled = true; } break; case "from": if (masterLootIndex > -1 && lootedIndex > masterLootIndex && split.Length > lootedIndex + 1 && split.Length > 3) { string name = split[3].TrimEnd(','); if (ParseCurrency(split, lootedIndex + 1, i, out string item, out uint count)) { var currentTime = DateUtil.ParseLogDate(lineData.Line, out _); PlayerManager.Instance.AddVerifiedPlayer(name, currentTime); LootRecord record = new LootRecord() { Item = item, Player = name, Quantity = count, IsCurrency = true }; DataManager.Instance.AddLootRecord(record, currentTime); handled = true; } } else if (!string.IsNullOrEmpty(looter) && lootedIndex == 2 && split.Length > 4) { // covers "a" or "an" uint count = split[3][0] == 'a' ? 1 : StatsUtil.ParseUInt(split[3]); string item = string.Join(" ", split, 4, i - 4); string npc = string.Join(" ", split, i + 1, split.Length - i - 1).TrimEnd(LootedFromTrim).Replace("'s corpse", ""); if (count > 0 && count != ushort.MaxValue) { var currentTime = DateUtil.ParseLogDate(lineData.Line, out _); PlayerManager.Instance.AddVerifiedPlayer(looter, currentTime); LootRecord record = new LootRecord() { Item = item, Player = looter, Quantity = count, IsCurrency = false, Npc = npc }; DataManager.Instance.AddLootRecord(record, currentTime); handled = true; } } break; case "given": if (split[i - 1] == "was" && split.Length == (i + 3) && split[i + 1] == "to") { string player = split[i + 2]; if (player.Length > 3) { looter = player.Substring(0, player.Length - 1); looter = looter.Equals("you", StringComparison.OrdinalIgnoreCase) ? ConfigUtil.PlayerName : looter; var currentTime = DateUtil.ParseLogDate(lineData.Line, out _); PlayerManager.Instance.AddVerifiedPlayer(looter, currentTime); string item = string.Join(" ", split, 1, i - 2); LootRecord record = new LootRecord() { Item = item, Player = looter, Quantity = 0, IsCurrency = false, Npc = "Given (Not Looted)" }; DataManager.Instance.AddLootRecord(record, currentTime); handled = true; } } break; case "split.": case "split": if (receiveIndex > -1 && split[i - 1] == "your" && split[i - 2] == "as") { if (ParseCurrency(split, 2, i - 2, out string item, out uint count)) { LootRecord record = new LootRecord() { Item = item, Player = ConfigUtil.PlayerName, Quantity = count, IsCurrency = true }; DataManager.Instance.AddLootRecord(record, DateUtil.ParseLogDate(lineData.Line, out _)); handled = true; } } break; } } } } } catch (ArgumentNullException ne) { LOG.Error(ne); } catch (NullReferenceException nr) { LOG.Error(nr); } catch (ArgumentOutOfRangeException aor) { LOG.Error(aor); } catch (ArgumentException ae) { LOG.Error(ae); } DebugUtil.UnregisterLine(lineData.LineNumber, handled); }
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); }