private static void Aggregate(Dictionary <string, DataPoint> playerData, Dictionary <string, ChartValues <DataPoint> > theValues, Dictionary <string, DataPoint> needAccounting, DataPoint dataPoint, DataPoint aggregate, double firstTime, double lastTime, double diff) { if (diff > DataManager.FIGHTTIMEOUT) { UpdateRemaining(theValues, needAccounting, firstTime, lastTime); foreach (var value in playerData.Values) { value.RollingTotal = 0; value.RollingCritHits = 0; value.RollingHits = 0; value.CurrentTime = lastTime + 6; Insert(value, theValues); value.CurrentTime = firstTime - 6; Insert(value, theValues); } } aggregate.Total += dataPoint.Total; aggregate.RollingTotal += dataPoint.Total; aggregate.RollingHits += 1; aggregate.RollingCritHits += LineModifiersParser.IsCrit(dataPoint.ModifiersMask) ? (uint)1 : 0; aggregate.BeginTime = firstTime; aggregate.CurrentTime = dataPoint.CurrentTime; if (diff >= 1) { Insert(aggregate, theValues); UpdateRemaining(theValues, needAccounting, firstTime, dataPoint.CurrentTime, aggregate.Name); } else { needAccounting[aggregate.Name] = aggregate; } }
private static DamageRecord ValidateDamage(DamageRecord record, SpellResist resist = SpellResist.UNDEFINED) { if (record != null) { // handle riposte separately if (LineModifiersParser.IsRiposte(record.ModifiersMask)) { record.SubType = Labels.RIPOSTE; } if (InIgnoreList(record.Defender)) { record = null; } else { // Needed to replace 'You' and 'you', etc record.Attacker = PlayerManager.Instance.ReplacePlayer(record.Attacker, record.Defender); record.Defender = PlayerManager.Instance.ReplacePlayer(record.Defender, record.Attacker); if (string.IsNullOrEmpty(record.Attacker)) { record.Attacker = Labels.ENVDAMAGE; } if (resist != SpellResist.UNDEFINED && ConfigUtil.PlayerName == record.Attacker && record.Defender != record.Attacker) { DataManager.Instance.UpdateNpcSpellResistStats(record.Defender, resist); } } } return(record); }
internal static void UpdateStats(PlayerSubStats stats, HitRecord record, double beginTime) { switch (record.Type) { case Labels.BANE: stats.BaneHits++; stats.Hits += 1; break; case Labels.MISS: stats.Misses++; stats.MeleeAttempts += 1; break; case Labels.PROC: case Labels.DOT: case Labels.DD: case Labels.HOT: case Labels.HEAL: case Labels.DS: stats.Hits += 1; break; default: stats.Hits += 1; stats.MeleeHits++; stats.MeleeAttempts += 1; break; } if (record.Total > 0) { stats.Total += record.Total; stats.Max = Math.Max(stats.Max, record.Total); } if (record.Total > 0 && record.OverTotal > 0) { stats.Extra += (record.OverTotal - record.Total); } LineModifiersParser.Parse(record, stats); stats.BeginTime = double.IsNaN(stats.BeginTime) ? beginTime : stats.BeginTime; stats.LastTime = beginTime; }
private static DamageRecord ValidateDamage(DamageRecord record) { if (record != null) { // handle riposte separately if (LineModifiersParser.IsRiposte(record.ModifiersMask)) { record.SubType = Labels.RIPOSTE; } if (InIgnoreList(record.Defender)) { record = null; } else { // Needed to replace 'You' and 'you', etc record.Attacker = PlayerManager.Instance.ReplacePlayer(record.Attacker, record.Defender); record.Defender = PlayerManager.Instance.ReplacePlayer(record.Defender, record.Attacker); } } return(record); }
private static DamageRecord ParseDamage(string actionPart) { DamageRecord record = null; ParseType parseType = ParseType.UNKNOWN; string withoutMods = actionPart; int modifiersIndex = -1; if (actionPart[actionPart.Length - 1] == ')') { // using 4 here since the shortest modifier should at least be 3 even in the future. probably. modifiersIndex = actionPart.LastIndexOf('(', actionPart.Length - 4); if (modifiersIndex > -1) { withoutMods = actionPart.Substring(0, modifiersIndex); } } int pointsIndex = -1; int forIndex = -1; int fromIndex = -1; int byIndex = -1; int takenIndex = -1; int hitIndex = -1; int extraIndex = -1; int isAreIndex = -1; bool nonMelee = false; List <string> nameList = new List <string>(); StringBuilder builder = new StringBuilder(); var data = withoutMods.Split(' '); SpellResist resist = SpellResist.UNDEFINED; for (int i = 0; i < data.Length; i++) { switch (data[i]) { case "taken": takenIndex = i; int test1 = i - 1; if (test1 > 0 && data[test1] == "has") { parseType = ParseType.HASTAKEN; int test2 = i + 2; if (data.Length > test2 && data[test2] == "extra" && data[test2 - 1] == "an") { extraIndex = test2; } } else if (test1 >= 1 && data[test1] == "have" && data[test1 - 1] == "You") { parseType = ParseType.YOUHAVETAKEN; } break; case "by": byIndex = i; break; case "non-melee": nonMelee = true; break; case "is": case "are": isAreIndex = i; break; case "for": int next = i + 1; if (data.Length > next && data[next].Length > 0 && char.IsNumber(data[next][0])) { forIndex = i; } break; case "from": fromIndex = i; break; case "points": int ofIndex = i + 1; if (ofIndex < data.Length && data[ofIndex] == "of") { parseType = ParseType.POINTSOF; pointsIndex = i; int resistIndex = ofIndex + 1; if (resistIndex < data.Length && SpellResistMap.TryGetValue(data[resistIndex], out SpellResist value)) { resist = value; nonMelee = true; } } break; default: if (HitMap.ContainsKey(data[i])) { hitIndex = i; } break; } } if (parseType == ParseType.POINTSOF && forIndex > -1 && forIndex < pointsIndex && hitIndex > -1) { record = ParsePointsOf(data, nonMelee, forIndex, byIndex, hitIndex, builder, nameList); } else if (parseType == ParseType.HASTAKEN && takenIndex < fromIndex && fromIndex > -1) { record = ParseHasTaken(data, takenIndex, fromIndex, byIndex, builder); } else if (parseType == ParseType.POINTSOF && extraIndex > -1 && takenIndex > -1 && takenIndex < fromIndex) { record = ParseExtra(data, takenIndex, extraIndex, fromIndex, nameList); } // there are more messages without a specificied attacker or spell but do these first else if (parseType == ParseType.YOUHAVETAKEN && takenIndex > -1 && fromIndex > -1 && byIndex > fromIndex) { record = ParseYouHaveTaken(data, takenIndex, fromIndex, byIndex, builder); } else if (parseType == ParseType.POINTSOF && isAreIndex > -1 && byIndex > isAreIndex && forIndex > byIndex) { record = ParseDS(data, isAreIndex, byIndex, forIndex); } if (record != null && modifiersIndex > -1) { record.ModifiersMask = LineModifiersParser.Parse(actionPart.Substring(modifiersIndex + 1, actionPart.Length - 1 - modifiersIndex - 1)); } return(ValidateDamage(record, resist)); }
private static DamageRecord ParseMiss(string actionPart, int index) { // [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) DamageRecord record = null; string withoutMods = actionPart; int modifiersIndex = -1; if (actionPart[actionPart.Length - 1] == ')') { // using 4 here since the shortest modifier should at least be 3 even in the future. probably. modifiersIndex = actionPart.LastIndexOf('(', actionPart.Length - 4); if (modifiersIndex > -1) { withoutMods = actionPart.Substring(0, modifiersIndex); } } int tryStartIndex; int hitStartIndex = -1; int missesIndex = index - LineParsing.ACTIONINDEX; if (withoutMods[0] == 'Y' && withoutMods[1] == 'o' && withoutMods[2] == 'u') { tryStartIndex = 3; hitStartIndex = 11; } else { tryStartIndex = withoutMods.IndexOf(" tries to ", StringComparison.Ordinal); if (tryStartIndex > -1) { hitStartIndex = tryStartIndex + 10; } } if (tryStartIndex > -1 && hitStartIndex > -1 && tryStartIndex < missesIndex) { int hitEndIndex = withoutMods.IndexOf(" ", hitStartIndex, StringComparison.Ordinal); if (hitEndIndex > -1) { string hit = withoutMods.Substring(hitStartIndex, hitEndIndex - hitStartIndex); string subType = GetTypeFromHit(hit, out bool additional); if (subType != null) { int hitLength = hit.Length + (additional ? 3 : 0); // check for pets string attacker = withoutMods.Substring(0, tryStartIndex); int defStart = hitStartIndex + hitLength + 1; int missesEnd = missesIndex - defStart; if (missesEnd > 0) { string defender = withoutMods.Substring(defStart, missesEnd); HasOwner(attacker, out string attackerOwner); HasOwner(defender, out string defenderOwner); if (attacker != null && defender != null) { record = BuildRecord(attacker, defender, 0, attackerOwner, defenderOwner, subType, Labels.MISS); } if (record != null && modifiersIndex > -1) { record.ModifiersMask = LineModifiersParser.Parse(actionPart.Substring(modifiersIndex + 1, actionPart.Length - 1 - modifiersIndex - 1)); } } } } } return(ValidateDamage(record)); }
internal static void UpdateStats(PlayerSubStats stats, HitRecord record, bool isPet = false) { var newMeleeHit = false; switch (record.Type) { case Labels.BANE: stats.BaneHits++; stats.Hits += 1; break; case Labels.BLOCK: stats.Blocks++; stats.MeleeAttempts++; break; case Labels.DODGE: stats.Dodges++; stats.MeleeAttempts++; break; case Labels.MISS: stats.Misses++; stats.MeleeAttempts++; break; case Labels.PARRY: stats.Parries++; stats.MeleeAttempts++; break; case Labels.INVULNERABLE: stats.Invulnerable++; stats.MeleeAttempts++; break; case Labels.PROC: case Labels.DOT: case Labels.DD: stats.SpellHits++; stats.Hits++; break; case Labels.HOT: case Labels.HEAL: case Labels.DS: case Labels.RS: stats.Hits += 1; break; default: stats.Hits += 1; stats.MeleeAttempts++; newMeleeHit = true; break; } if (newMeleeHit) { // regular hit from a player OR Hits from a pet can do things like Flurry if (record.Type == Labels.MELEE) { if (RegularMeleeTypes.ContainsKey(record.SubType) || (record.SubType == "Hits" && isPet)) { stats.RegularMeleeHits++; } else if (record.SubType == "Shoots") { stats.BowHits++; } } stats.MeleeHits++; } if (record.Total > 0) { stats.Total += record.Total; stats.Max = Math.Max(stats.Max, record.Total); } if (record.OverTotal > 0) { stats.Extra += (record.OverTotal - record.Total); } LineModifiersParser.UpdateStats(record, stats); }
private void AddDataPoints(RecordGroupCollection recordIterator, List <PlayerStats> selected = null, Predicate <object> filter = null) { DateTime newTaskTime = DateTime.Now; Task.Run(() => { double lastTime = double.NaN; double firstTime = double.NaN; Dictionary <string, DataPoint> playerData = new Dictionary <string, DataPoint>(); Dictionary <string, DataPoint> needAccounting = new Dictionary <string, DataPoint>(); Dictionary <string, ChartValues <DataPoint> > theValues = new Dictionary <string, ChartValues <DataPoint> >(); foreach (var dataPoint in recordIterator) { double diff = double.IsNaN(lastTime) ? 1 : dataPoint.CurrentTime - lastTime; if (!playerData.TryGetValue(dataPoint.Name, out DataPoint aggregate)) { aggregate = new DataPoint() { Name = dataPoint.Name }; playerData[dataPoint.Name] = aggregate; } if (double.IsNaN(firstTime) || diff > DataManager.FIGHT_TIMEOUT) { firstTime = dataPoint.CurrentTime; if (diff > DataManager.FIGHT_TIMEOUT) { UpdateRemaining(theValues, needAccounting, firstTime, lastTime); foreach (var value in playerData.Values) { value.RollingTotal = 0; value.RollingCritHits = 0; value.RollingHits = 0; value.CurrentTime = lastTime + 6; Insert(value, theValues); value.CurrentTime = firstTime - 6; Insert(value, theValues); } } } aggregate.Total += dataPoint.Total; aggregate.RollingTotal += dataPoint.Total; aggregate.RollingHits += 1; aggregate.RollingCritHits += LineModifiersParser.IsCrit(dataPoint.ModifiersMask) ? (uint)1 : 0; aggregate.BeginTime = firstTime; aggregate.CurrentTime = dataPoint.CurrentTime; lastTime = dataPoint.CurrentTime; if (diff >= 1) { Insert(aggregate, theValues); UpdateRemaining(theValues, needAccounting, firstTime, lastTime, aggregate.Name); } else { needAccounting[aggregate.Name] = aggregate; } } UpdateRemaining(theValues, needAccounting, firstTime, lastTime); Plot(theValues, newTaskTime, selected, filter); }); }
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 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); } }
private static bool CreateDamageRecord(LineData lineData, string[] split, int stop, string attacker, string defender, uint damage, string type, string subType, SpellResist resist = SpellResist.UNDEFINED, bool attackerIsSpell = false) { bool success = false; if (damage != uint.MaxValue && !string.IsNullOrEmpty(type) && !string.IsNullOrEmpty(subType) && !InIgnoreList(defender)) { // Needed to replace 'You' and 'you', etc defender = PlayerManager.Instance.ReplacePlayer(defender, defender); if (string.IsNullOrEmpty(attacker)) { attacker = subType; } else if (attacker.EndsWith("'s corpse", StringComparison.Ordinal)) { attacker = attacker.Substring(0, attacker.Length - 9); } else { // Needed to replace 'You' and 'you', etc attacker = PlayerManager.Instance.ReplacePlayer(attacker, attacker); } if (resist != SpellResist.UNDEFINED && ConfigUtil.PlayerName == attacker && defender != attacker) { DataManager.Instance.UpdateNpcSpellResistStats(defender, resist); } // check for pets HasOwner(attacker, out string attackerOwner); HasOwner(defender, out string defenderOwner); DamageRecord record = new DamageRecord { Attacker = string.Intern(FixName(attacker)), Defender = string.Intern(FixName(defender)), Type = string.Intern(type), SubType = string.Intern(subType), Total = damage, AttackerOwner = attackerOwner != null?string.Intern(attackerOwner) : null, DefenderOwner = defenderOwner != null?string.Intern(defenderOwner) : null, ModifiersMask = -1, AttackerIsSpell = attackerIsSpell }; var currentTime = DateUtil.ParseLogDate(lineData.Line, out string timeString); if (split.Length > stop + 1) { // improve this later so maybe the string doesn't have to be re-joined string modifiers = string.Join(" ", split, stop + 1, split.Length - stop - 1); record.ModifiersMask = LineModifiersParser.Parse(record.Attacker, modifiers.Substring(1, modifiers.Length - 2), currentTime); } if (!double.IsNaN(currentTime)) { // handle old style crits for eqemu if (LastCrit != null && LastCrit.Attacker == record.Attacker && LastCrit.LineData.LineNumber == (lineData.LineNumber - 1)) { var critTime = DateUtil.ParseLogDate(LastCrit.LineData.Line, out string _); if (!double.IsNaN(critTime) && (currentTime - critTime) <= 1) { record.ModifiersMask = (record.ModifiersMask == -1) ? LineModifiersParser.CRIT : record.ModifiersMask | LineModifiersParser.CRIT; } LastCrit = null; } CheckSlainQueue(currentTime); DamageProcessedEvent e = new DamageProcessedEvent() { Record = record, OrigTimeString = timeString, BeginTime = currentTime }; EventsDamageProcessed?.Invoke(record, e); success = true; if (record.Type == Labels.DD && SpecialCodes.Keys.FirstOrDefault(special => !string.IsNullOrEmpty(record.SubType) && record.SubType.Contains(special)) is string key && !string.IsNullOrEmpty(key)) { DataManager.Instance.AddSpecial(new SpecialSpell() { Code = SpecialCodes[key], Player = record.Attacker, BeginTime = currentTime }); } } } return(success); }