Beispiel #1
0
        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;
            }
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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;
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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));
        }
Beispiel #6
0
        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));
        }
Beispiel #7
0
        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);
            });
        }
Beispiel #9
0
        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);
        }
Beispiel #10
0
        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);
        }