private void OnTriggerEnter(Collider other)
 {
     if (other.gameObject.tag == "EncounterEnemy")
     {
         statsE          = other.gameObject.GetComponent <EncounterStats>();
         placeholder     = other.gameObject.GetComponent <EnemyTypeOnEncounter>();
         encounter.typeE = other.gameObject.GetComponent <EnemyTypeOnEncounter>();
         encounter.OnEncounterEnter(placeholder.SummonEnemy, false, difficultyReceived);
     }
 }
Пример #2
0
        public static async Task ParseAsync(SessionLogInfo logInfo, string logFile)
        {
            // Check the filesystem first
            var sessionPath = Path.Combine(ParentPath, logInfo.SessionId.ToString());

            if (CheckFolderExists(sessionPath) == false)
            {
                Console.WriteLine("Can't continue as something went wrong while checking that the session folder exists.");
                Console.ReadLine();
                Environment.Exit(1);
            }

            Console.WriteLine("Folder structure checked OK.");

            Console.WriteLine($"Beginning to parse {logFile}");
            await using var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true);
            using var sr       = new StreamReader(fs);
            #region Calculate when the session starts in local and UTC time
            // Session date is stored in UTC already, so instead of subtracting offset for UTC, add offset for local
            TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById(logInfo.UploaderTimezone);
            DateTime     localSessionDate = logInfo.SessionDate.Add(tzi.GetUtcOffset(logInfo.SessionDate));
            Console.WriteLine($"Session date (local time): {localSessionDate}, UTC time: {logInfo.SessionDate}");
            #endregion
            var logType = LogType.Unknown;

            // Main loop
            var    line                    = "";
            var    lineNumber              = 1;
            var    downtimeSeconds         = DefaultEncounterDowntime;
            int    encounterNumber         = 0;
            bool   inCombat                = false;
            var    currentCombatStarted    = new DateTime();
            var    currentCombatLastDamage = new DateTime();
            double daysToAdd               = 0;
            var    lastTimeStamp           = new DateTime();
            //var calculatedTimestamp = new DateTime();
            var encounterLength = new TimeSpan(0, 0, 0, 0);
            var unwrittenLines  = new List <string>();

            // Per encounter counters
            long totalDamage     = 0;
            long totalHealing    = 0;
            long totalShielding  = 0;
            int  damageEvents    = 0;
            int  healingEvents   = 0;
            int  shieldingEvents = 0;
            int  buffEvents      = 0;
            Dictionary <string, int>  playerDeaths   = new Dictionary <string, int>();
            Dictionary <string, int>  npcDeaths      = new Dictionary <string, int>();
            Dictionary <string, long> npcDamageTaken = new Dictionary <string, long>();

            // Globals
            var players   = new HashSet <string>();
            var npcs      = new HashSet <string>();
            var pets      = new HashSet <string>();
            var abilities = new HashSet <string>();
            Dictionary <string, int>  globalPlayerDeaths   = new Dictionary <string, int>();
            Dictionary <string, int>  globalNpcDeaths      = new Dictionary <string, int>();
            Dictionary <string, long> globalNpcDamageTaken = new Dictionary <string, long>();
            long globalTotalDamage      = 0;
            long globalTotalHealing     = 0;
            long globalTotalShielding   = 0;
            int  globalDamageEvents     = 0;
            int  globalHealingEvents    = 0;
            int  globalShieldingEvents  = 0;
            int  globalBuffEvents       = 0;
            var  totalEncounterDuration = new TimeSpan();

            // Testing
            Stopwatch encWriter = new Stopwatch();

            while ((line = sr.ReadLine()) != null)
            {
                // If we don't know what type of log this is yet, then figure that out now
                if (logType == LogType.Unknown)
                {
                    if (line.Length > 8)
                    {
                        if (line.Substring(2, 1) == ":" && line.Substring(5, 1) == ":")
                        {
                            logType = LogType.Standard;
                        }
                        else if (line.Substring(2, 1) == "/" && line.Substring(5, 1) == "/")
                        {
                            logType = LogType.Expanded;
                        }
                    }

                    // If we haven't figured out the log type by this point, skip processing this line and check the next one
                    if (logType == LogType.Unknown)
                    {
                        continue;
                    }
                }

                var entry = await ParseLine(line, logType);

                if (entry.ValidEntry == false)
                {
                    if (!line.Contains("Combat Begin") && !line.Contains("Combat End"))
                    {
                        Console.WriteLine($"Line {lineNumber} is not valid ({entry.InvalidReason})");
                        Console.WriteLine(line);
                        Console.WriteLine();
                    }
                    // Skip the rest of this loop, but increment the line number before we do
                    lineNumber++;
                    continue;
                }

                if (!inCombat)
                {
                    // Not in combat yet. Check to see whether this particular log entry should 'start' combat
                    if (entry.ShouldStartCombat())
                    {
                        // Blank line just to be nice
                        Console.WriteLine();

                        // Begin combat.
                        encounterNumber++;
                        encounterLength = new TimeSpan(0, 0, 0, 0);
                        Console.WriteLine($"Encounter {encounterNumber} started at {entry.ParsedTimeStamp.AddDays(daysToAdd)}");
                        // Default the encounter time unless it needs to be overridden
                        downtimeSeconds = entry.GetDowntimeValueForEncounter();
                        if (downtimeSeconds != DefaultEncounterDowntime)
                        {
                            Console.WriteLine($"Detected an overridden encounter downtime. The value is now {downtimeSeconds}");
                        }
                        inCombat       = true;
                        unwrittenLines = new List <string>();

                        // Set the variables that we'll use to determine when the encounter should end
                        currentCombatStarted    = entry.ParsedTimeStamp.AddDays(daysToAdd);
                        lastTimeStamp           = entry.ParsedTimeStamp.AddDays(daysToAdd);
                        currentCombatLastDamage = entry.ParsedTimeStamp.AddDays(daysToAdd);

                        // Update the time elapsed for this event
                        entry.SetTimeElapsed(currentCombatStarted, true);

                        // Create the files that we'll use for this encounter
                        var encounterContainersCreated = CreateEncounterContainers(sessionPath, encounterNumber);
                        if (!encounterContainersCreated)
                        {
                            Console.WriteLine($"Unable to create containers for encounter {encounterNumber}.");
                        }

                        // Reset counters
                        totalDamage     = 0;
                        totalHealing    = 0;
                        totalShielding  = 0;
                        damageEvents    = 0;
                        healingEvents   = 0;
                        shieldingEvents = 0;
                        buffEvents      = 0;
                        playerDeaths    = new Dictionary <string, int>();
                        npcDeaths       = new Dictionary <string, int>();
                        npcDamageTaken  = new Dictionary <string, long>();

                        #region Single line append
                        encWriter.Reset();
                        encWriter.Start();
                        // NB: This is only used to append lines one at a time.
                        // Add this entry to the file that it belongs to
                        await AppendLine(sessionPath, encounterNumber, encounterContainers[entry.ContainerType], line);

                        // Testing: Write to the encounter file
                        await AppendLine_SingleFileForEncounter(sessionPath, encounterNumber, line);

                        //Console.WriteLine($"{entry.SecondsElapsed}: {line}");

                        #endregion
                    }
                }
                else
                {
                    var secondDifference =
                        (int)(entry.ParsedTimeStamp.AddDays(daysToAdd) - lastTimeStamp).TotalSeconds;
                    if (secondDifference == 0 || secondDifference > 0)
                    {
                        // Timestamp hasn't changed, or it's later in the same day
                        entry.CalculatedTimeStamp = entry.ParsedTimeStamp.AddDays(daysToAdd);
                    }
                    else
                    {
                        // We have just rolled over midnight
                        daysToAdd++;
                        entry.CalculatedTimeStamp = entry.ParsedTimeStamp.AddDays(daysToAdd);
                        //currentCombatLastDamage = calculatedTimestamp;
                    }

                    // Update the time elapsed for this event
                    entry.SetTimeElapsed(currentCombatStarted);

                    if ((entry.CalculatedTimeStamp - currentCombatLastDamage).TotalSeconds > downtimeSeconds)
                    {
                        encWriter.Stop();
                        inCombat        = false;
                        encounterLength = currentCombatLastDamage - currentCombatStarted;
                        // Update global encounter totals
                        totalEncounterDuration += encounterLength;
                        Console.WriteLine($"Combat for encounter {encounterNumber} ended at {entry.ParsedTimeStamp.AddDays(daysToAdd)} ({entry.SecondsElapsed} seconds elapsed). Time elapsed for reading and writing: {encWriter.Elapsed}");
                        Console.WriteLine($"The last damage was detected at {currentCombatLastDamage}");
                        var encInfo = new List <string>
                        {
                            $"Encounter {encounterNumber}",
                            $"Started: {currentCombatStarted}",
                            $"Ended: {currentCombatLastDamage}",
                            $"Duration: {encounterLength}",
                            "===================",
                            $"Total damage done: {totalDamage}. Events: {damageEvents}",
                            $"Total healing done: {totalHealing}. Events: {healingEvents}",
                            $"Total shielding done: {totalShielding}. Events: {shieldingEvents}",
                            "-------------------",
                            $"Deaths: {playerDeaths.Sum(kvp => kvp.Value) + npcDeaths.Sum(kvp => kvp.Value)}",
                            "-------------------",
                        };

                        encInfo.AddRange(playerDeaths.OrderByDescending(kvp => kvp.Value).Select(death => $"{death.Key}: {death.Value}"));
                        encInfo.Add("-------------------");
                        encInfo.AddRange(npcDeaths.OrderByDescending(kvp => kvp.Value).Select(death => $"{death.Key}: {death.Value}"));

                        var encStats = new EncounterStats(
                            encounterNumber, encounterLength, totalDamage, damageEvents, totalHealing, healingEvents,
                            totalShielding, shieldingEvents, playerDeaths, npcDeaths, npcDamageTaken);

                        // Old pre-JSON encounter stats
                        //await WriteEncounterInfo(sessionPath, encounterNumber, encInfo);
                        // JSON
                        //await WriteEncounterStats(sessionPath, encounterNumber, encStats);

                        // Remove the encounter folder if it's not long enough to warrant saving
                        encounterLength = currentCombatLastDamage - currentCombatStarted;
                        if (encounterLength.TotalSeconds < 5)
                        {
                            // Remove the encounter (not long enough)
                            var encRemoved = RemoveEncounterFolder(sessionPath, encounterNumber);
                            if (encRemoved)
                            {
                                Console.WriteLine($"Encounter {encounterNumber} removed (<5s)");
                            }
                        }

                        // Update the session text file
                        var globalInfo = new List <string>
                        {
                            $"Total encounters: {encounterNumber}",
                            $"Total encounter time: <Not calculated>",
                            "===================",
                            $"Total damage done: {globalTotalDamage}. Events: {globalDamageEvents}",
                            $"Total healing done: {globalTotalHealing}. Events: {globalHealingEvents}",
                            $"Total shielding done: {globalTotalShielding}. Events: {globalShieldingEvents}",
                            "-------------------",
                            $"Deaths: {globalPlayerDeaths.Sum(kvp => kvp.Value) + globalNpcDeaths.Sum(kvp => kvp.Value)}",
                            "-------------------",
                        };

                        globalInfo.AddRange(globalPlayerDeaths.OrderByDescending(kvp => kvp.Value).Select(death => $"{death.Key}: {death.Value}"));
                        globalInfo.Add("-------------------");
                        globalInfo.AddRange(globalNpcDeaths.OrderByDescending(kvp => kvp.Value).Select(death => $"{death.Key}: {death.Value}"));

                        // Old pre-JSON encounter stats
                        //await WriteSessionInfo(sessionPath, globalInfo);
                        // JSON
                        //await WriteSessionStats(sessionPath, new SessionStats
                        //(encounterNumber, totalEncounterDuration, globalTotalDamage, globalDamageEvents,
                        //    globalTotalHealing, globalHealingEvents, globalTotalShielding, globalShieldingEvents,
                        //    globalPlayerDeaths, globalNpcDeaths, globalNpcDamageTaken));
                    }
                    // Still in combat but not outside our downtime period. Write the event if we need to
                    else if (!entry.IgnoreThisEvent)
                    {
                        // Update the last combat timestamp if it has changed
                        if (entry.CalculatedTimeStamp != currentCombatLastDamage)
                        {
                            if (entry.TargetType == CharacterType.Npc && entry.IsDamageType)
                            {
                                currentCombatLastDamage = entry.ParsedTimeStamp.AddDays(daysToAdd);
                            }
                        }

                        // Write damage/death records immediately, but collect all other logged entries and write them if we find another damage
                        // record within the permitted downtime. If nothing happens for X seconds, then discard unwritten lines.
                        switch (entry.ContainerType)
                        {
                        case EncounterContainerType.Damage:
                        case EncounterContainerType.Death:
                            // Write any previously unwritten lines to the log
                            if (unwrittenLines.Any())
                            {
                                await AppendLine_SingleFileForEncounter(sessionPath, encounterNumber, unwrittenLines);

                                unwrittenLines = new List <string>();
                            }
                            await AppendLine_SingleFileForEncounter(sessionPath, encounterNumber, line);

                            break;

                        case EncounterContainerType.NotLogged:
                        case EncounterContainerType.Unknown:
                            // Do nothing with these
                            break;

                        default:
                            // Not a death or damage record so add this to the list of unwritten lines
                            unwrittenLines.Add(line);
                            break;
                        }

                        // This switch works well, but still logs entries after it needs to (when combat is finished)
                        //switch (entry.ContainerType)
                        //{
                        //    case EncounterContainerType.Unknown:
                        //        Console.WriteLine($"  Unknown container type. Line: {line}");
                        //        break;
                        //    case EncounterContainerType.NotLogged:
                        //        break;
                        //    default:
                        //        //Console.WriteLine($"{entry.SecondsElapsed}: {line}");
                        //        await AppendLine(sessionPath, encounterNumber, encounterContainers[entry.ContainerType], line);
                        //        await AppendLine_SingleFileForEncounter(sessionPath, encounterNumber, line);
                        //        switch (entry.AttackerType)
                        //        {
                        //            case CharacterType.Player:
                        //                players.Add(entry.AttackerName);
                        //                break;
                        //            case CharacterType.Npc:
                        //                npcs.Add(entry.AttackerName);
                        //                break;
                        //            case CharacterType.Pet:
                        //                pets.Add(entry.AttackerName);
                        //                break;
                        //        }
                        //        switch (entry.TargetType)
                        //        {
                        //            case CharacterType.Player:
                        //                players.Add(entry.TargetName);
                        //                break;
                        //            case CharacterType.Npc:
                        //                npcs.Add(entry.TargetName);
                        //                break;
                        //            case CharacterType.Pet:
                        //                pets.Add(entry.TargetName);
                        //                break;
                        //        }

                        //        abilities.Add(entry.AbilityName);
                        //        break;
                        //}

                        // Testing / encounter stats

                        //// Switch the container type again to add to the correct counter
                        //switch (entry.ContainerType)
                        //{
                        //    case EncounterContainerType.Buff:
                        //        buffEvents += 1;
                        //        globalBuffEvents += 1;
                        //        break;
                        //    case EncounterContainerType.Damage:
                        //        damageEvents += 1;
                        //        totalDamage += entry.TotalDamage;
                        //        globalDamageEvents += 1;
                        //        globalTotalDamage += entry.TotalDamage;
                        //        if (entry.TargetType == CharacterType.Npc && entry.TargetTakingDamage)
                        //        {
                        //            npcDamageTaken.AddDamageTaken(entry.TargetName, entry.ActionValue);
                        //            globalNpcDamageTaken.AddDamageTaken(entry.TargetName, entry.ActionValue);
                        //        }
                        //        break;
                        //    case EncounterContainerType.Heal:
                        //        healingEvents += 1;
                        //        totalHealing += entry.ActionValue;
                        //        globalHealingEvents += 1;
                        //        globalTotalHealing += entry.ActionValue;
                        //        break;
                        //    case EncounterContainerType.Shield:
                        //        shieldingEvents += 1;
                        //        totalShielding += entry.ActionValue;
                        //        globalShieldingEvents += 1;
                        //        globalTotalShielding += entry.ActionValue;
                        //        break;
                        //    case EncounterContainerType.Death:
                        //        switch (entry.TargetType)
                        //        {
                        //            case CharacterType.Player:
                        //                playerDeaths.AddDeath(entry.TargetName);
                        //                globalPlayerDeaths.AddDeath(entry.TargetName);
                        //                break;
                        //            case CharacterType.Npc:
                        //                npcDeaths.AddDeath(entry.TargetName);
                        //                globalNpcDeaths.AddDeath(entry.TargetName);
                        //                break;
                        //        }
                        //        break;
                        //}
                    }
                }

                lineNumber++;
            }
        }
Пример #3
0
 static async Task WriteEncounterStats(string sessionPath, int encounterNumber, EncounterStats stats)
 {
     try
     {
         await File.WriteAllTextAsync(
             Path.Combine(sessionPath, $"{encounterNumber}.txt"),
             JsonConvert.SerializeObject(stats));
     }
     catch (Exception ex)
     {
         Console.WriteLine($"Error writing to encounter {encounterNumber} stats: {ex.Message}");
     }
 }