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); } }
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++; } }
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}"); } }