Skip to content

Instantly share code, notes, and snippets.

@pjmagee
Last active June 27, 2023 21:44
Show Gist options
  • Select an option

  • Save pjmagee/dbdaf311790e28b90f7590f992ce6e88 to your computer and use it in GitHub Desktop.

Select an option

Save pjmagee/dbdaf311790e28b90f7590f992ce6e88 to your computer and use it in GitHub Desktop.
SWTOR Parser v2 Memory Spans
public static readonly string Settings = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, System.Environment.SpecialFolderOption.None), "Star Wars - The Old Republic", "CombatLogs");
public static DirectoryInfo CombatLogsDirectory => new(Settings);
void Main()
{
foreach (var file in EnumerateCombatLogs())
{
foreach (var line in EnumerateCombatLogItems(file))
{
if (line.Value is not null)
if (line.Value.IsParry || line.Value.IsDodge || line.Value.IsMiss || line.Value.IsAbsorbed)
new
{
line.Ability,
line.Value,
line.Threat
}.Dump();
}
}
}
public class CombatLog
{
public FileInfo FileInfo { get; set; }
public CombatLog()
{
}
}
public class GameObject
{
public ReadOnlyMemory<char> Rom { get; }
public GameObject(ReadOnlyMemory<char> rom)
{
Rom = rom;
}
public bool IsNested => Rom.Span.IndexOf('/') > -1;
public virtual string? Name
{
get
{
if (IsNested) return Rom.Span.Slice(Rom.Span.IndexOf('/') + 1, Rom.Span.LastIndexOf('{') - Rom.Span.IndexOf('/') - 1).Trim().ToString();
if (Rom.Length > 0) return Rom.Span.Slice(0, Rom.Span.IndexOf('{') - 1).Trim().ToString();
return null;
}
}
public virtual long? Id
{
get
{
if (IsNested)
{
var startIndex = Rom.Span.LastIndexOf('{');
var endIndex = Rom.Span.LastIndexOf('}');
return long.Parse(Rom.Span.Slice(startIndex + 1, endIndex - startIndex - 1));
}
if (Rom.Length > 0)
{
var startIndex = Rom.Span.IndexOf('{');
var endIndex = Rom.Span.IndexOf('}');
return long.Parse(Rom.Span.Slice(startIndex + 1, endIndex - startIndex - 1));
}
return null;
}
}
}
public class Action
{
public ReadOnlyMemory<char> Rom { get; }
public Action(ReadOnlyMemory<char> rom)
{
Rom = rom;
}
public static Action? Parse(ReadOnlyMemory<char> memory)
{
if(memory.Span.IndexOf(':') != -1)
return new Action(memory);
return null;
}
private int Splitter => Rom.Span.IndexOf(':');
public GameObject Event => new GameObject(Rom.Slice(0, Splitter));
public GameObject Effect => new GameObject(Rom.Slice(Splitter + 1, Rom.Span.Length - Splitter - 1));
}
public class Actor
{
public List<ReadOnlyMemory<char>> Roms { get; }
public string? Name
{
get
{
try
{
if (IsPlayer)
return Roms[0].Span.Slice(1, Roms[0].Span.IndexOf('#') - 1).ToString();
return Roms[0].Span.Slice(0, Roms[0].Span.IndexOf('{')).ToString();
}
catch
{
return null;
}
}
}
public bool IsEmpty => Roms.Count == 0 || (Roms.Count == 1 && Roms[0].Length == 1 && Roms[0].Span[0] == '=');
public bool IsNpc => !IsEmpty && Roms[0].Span[0] != '@';
public bool IsPlayer => Roms.Count > 0 && Roms[0].Span.Length > 0 && Roms[0].Span[0] == '@';
public bool IsCompanion => IsPlayer && Roms[0].Span.IndexOf('/') > 0;
public int? HealthNow
{
get
{
if (Roms.Count == 3)
{
return int.Parse(Roms[2].Slice(1, Roms[2].Span.IndexOf('/') - 1).ToString());
}
return null;
}
}
public int? HealthMax
{
get
{
if (!IsEmpty)
{
var health = Roms[2].Span;
int maxStart = health.IndexOf('/') + 1;
int maxLength = health.Length - maxStart - 1;
return int.Parse(Roms[2].Slice(maxStart, maxLength).ToString());
}
return null;
}
}
public (float X, float Y, float Z)? Position
{
get
{
if (!IsEmpty)
{
var coordinates = ExtractCoordinates(Roms[1].Span);
return (coordinates[0], coordinates[1], coordinates[2]);
}
return null;
}
}
public long? Id
{
get
{
try
{
if (IsPlayer)
{
var hash = Roms[0].Span.IndexOf('#');
var slash = Roms[0].Span.IndexOf('/');
if (IsCompanion)
return long.Parse(Roms[0].Span.Slice(hash + 1, slash - hash - 1));
return long.Parse(Roms[0].Span.Slice(hash + 1, Roms[0].Span.Length - 1 - hash));
}
if (IsNpc)
{
int openIndex = Roms[0].Span.IndexOf('{');
int closeIndex = Roms[0].Span.IndexOf('}');
return long.Parse(Roms[0].Span.Slice(openIndex + 1, closeIndex - openIndex - 1));
}
}
catch
{
}
return null;
}
}
public Actor(ReadOnlyMemory<char> rom)
{
Roms = GetSubSections(rom);
}
public static Actor? Parse(ReadOnlyMemory<char> rom)
{
if (rom.IsEmpty || (rom.Length == 1 && rom.Span[0] == '=')) return null;
return new Actor(rom);
}
}
public class ActorSection
{
public ReadOnlyMemory<char> Section { get; }
public ActorSection(ReadOnlyMemory<char> section)
{
Section = section;
}
}
public class Ability : GameObject
{
public Ability(ReadOnlyMemory<char> rom) : base(rom)
{
}
public static Ability? Parse(ReadOnlyMemory<char> rom)
{
if(rom.Length == 0 || rom.IsEmpty) return null;
return new Ability(rom);
}
}
public class CombatLogItem
{
public ReadOnlyMemory<char> Rom { get; set; }
public List<ReadOnlyMemory<char>> Roms { get; }
public DateTime TimeStamp { get; }
public Actor? Source { get; }
public Actor? Target { get; }
public Ability? Ability { get; }
public Action? Action { get; }
public Value? Value { get; }
public Threat? Threat { get; }
public CombatLogItem(ReadOnlyMemory<char> line)
{
Rom = line;
Roms = GetSections(Rom);
TimeStamp = DateTime.Parse(Roms[0].Span);
Source = Actor.Parse(Roms[1]);
Target = Actor.Parse(Roms[2]);
Ability = Ability.Parse(Roms[3]);
Action = Action.Parse(Roms[4]);
Value = Value.Parse(Rom);
Threat = Threat.Parse(Rom);
}
}
public static class Constants
{
public static ReadOnlyMemory<char> Energy = new("energy".ToCharArray());
public static ReadOnlyMemory<char> Kinetic = new("kinetic".ToCharArray());
public static ReadOnlyMemory<char> Internal = new("internal".ToCharArray());
public static ReadOnlyMemory<char> Elemental = new("elemental".ToCharArray());
public static ReadOnlyMemory<char> Absorbed = new("absorbed".ToCharArray());
public static ReadOnlyMemory<char> Critical = new(new[] {'*'});
public static ReadOnlyMemory<char> Parry = new("-parry".ToArray());
public static ReadOnlyMemory<char> Miss = new("-miss".ToArray());
public static ReadOnlyMemory<char> Dodge = new("-dodge".ToArray());
public static ReadOnlyMemory<char> Tilde = new("~".ToArray());
public static ReadOnlyMemory<char> HeroEnginePrefix = new("he".ToArray());
}
public class Value
{
public ReadOnlyMemory<char> Memory { get; }
public Value(ReadOnlyMemory<char> memory)
{
this.Memory = memory;
}
public bool IsCritical => Memory.Span.Contains(Constants.Critical.Span, StringComparison.OrdinalIgnoreCase);
private bool IsEnergy => Memory.Span.Contains(Constants.Energy.Span, StringComparison.OrdinalIgnoreCase);
private bool IsKinetic => Memory.Span.Contains(Constants.Kinetic.Span, StringComparison.OrdinalIgnoreCase);
private bool IsElemental => Memory.Span.Contains(Constants.Elemental.Span, StringComparison.OrdinalIgnoreCase);
private bool IsInternal => Memory.Span.Contains(Constants.Internal.Span, StringComparison.OrdinalIgnoreCase);
public bool IsAbsorbed => Memory.Span.Contains(Constants.Absorbed.Span, StringComparison.OrdinalIgnoreCase);
public bool IsParry => Memory.Span.Contains(Constants.Parry.Span, StringComparison.OrdinalIgnoreCase);
public bool IsMiss => Memory.Span.Contains(Constants.Miss.Span, StringComparison.OrdinalIgnoreCase);
public bool IsDodge => Memory.Span.Contains(Constants.Dodge.Span, StringComparison.OrdinalIgnoreCase);
private bool IsTilde => Memory.Span.Contains(Constants.Tilde.Span, StringComparison.OrdinalIgnoreCase);
public SpecialValue? Special
{
get
{
var value = ExtractTildeValue(Memory);
if(value is null) return null;
if (IsElemental) return new SpecialValue(value.Value, SpecialType.Elemental);
if (IsEnergy) return new SpecialValue(value.Value, SpecialType.Energy);
if (IsInternal) return new SpecialValue(value.Value, SpecialType.Internal);
if (IsKinetic) return new SpecialValue(value.Value, SpecialType.Kinetic);
return null;
}
}
public int? Initial
{
get
{
return ExtractFirstValue(Memory);
}
}
public long? Id
{
get
{
var start = Memory.Span.IndexOf('{');
var end = Memory.Span.IndexOf('}');
if (start != -1)
return long.Parse(Memory.Span.Slice(start + 1, end - start - 1));
return null;
}
}
public static Value? Parse(ReadOnlyMemory<char> memory)
{
var start = memory.Span.LastIndexOf('(');
var end = memory.Span.LastIndexOf(')');
var exists = start > memory.Span.LastIndexOf(']');
if (exists)
{
var scope = memory.Slice(start + 1, end - start - 1);
if (!scope.Span.StartsWith(Constants.HeroEnginePrefix.Span, StringComparison.OrdinalIgnoreCase))
return new Value(scope);
}
return null;
}
public struct SpecialValue
{
public int Value { get; }
public SpecialType Type { get; }
public SpecialValue(int value, SpecialType type)
{
Value = value;
Type = type;
}
}
public enum SpecialType
{
Elemental,
Internal,
Energy,
Kinetic
}
}
public class Threat
{
public ReadOnlyMemory<char> Rom { get; }
public bool IsPositive => Value >= 0;
public bool IsNegative => Value < 0;
public int Value => int.Parse(Rom.Span);
public Threat(ReadOnlyMemory<char> rom)
{
this.Rom = rom;
}
public static Threat? Parse(ReadOnlyMemory<char> rom)
{
var start = rom.Span.LastIndexOf('<');
var end = rom.Span.LastIndexOf('>');
var exists = start > rom.Span.LastIndexOf(']');
if (exists)
{
var scope = rom.Slice(start + 1, end - start - 1);
if (scope.Span[0] != 'v')
return new Threat(scope);
}
return null;
}
}
public static int? ExtractFirstValue(ReadOnlyMemory<char> rom)
{
int? value = null;
int index = 0;
// Ignore any leading whitespace
while (index < rom.Length && char.IsWhiteSpace(rom.Span[index]))
{
index++;
}
// Extract the digits until a non-digit character or '~' is encountered
while (index < rom.Length && (char.IsDigit(rom.Span[index]) || rom.Span[index] == '~'))
{
if (rom.Span[index] == '~')
{
break; // Stop extracting when '~' is encountered
}
value = ((value ?? 0) * 10) + (rom.Span[index] - '0');
index++;
}
return value;
}
public static int? ExtractTildeValue(ReadOnlyMemory<char> rom)
{
int? value = null;
int index = 0;
// Find the '~' character, if present
while (index < rom.Length && rom.Span[index] != '~')
{
index++;
}
// Ignore any characters until a non-digit character is encountered
while (index < rom.Length && !char.IsDigit(rom.Span[index]))
{
index++;
}
// Extract the digits until a non-digit character is encountered
while (index < rom.Length && char.IsDigit(rom.Span[index]))
{
value = ((value?? 0) * 10) + (rom.Span[index] - '0');
index++;
}
return value;
}
public static List<float> ExtractCoordinates(ReadOnlySpan<char> span)
{
List<float> coordinates = new List<float>();
int start = 0;
int end = 0;
// Skip the opening parenthesis
if (span[0] == '(')
{
start = 1;
}
// Exclude the closing parenthesis
if (span[span.Length - 1] == ')')
{
end = span.Length - 1;
}
else
{
end = span.Length;
}
ReadOnlySpan<char> content = span.Slice(start, end - start);
// Split the content by comma
start = 0;
for (int i = 0; i < content.Length; i++)
{
if (content[i] == ',')
{
float number;
if (float.TryParse(content.Slice(start, i - start), NumberStyles.Float, CultureInfo.InvariantCulture, out number))
{
coordinates.Add(number);
}
start = i + 1;
}
}
// Handle the last number
float lastNumber;
if (float.TryParse(content.Slice(start, content.Length - start), NumberStyles.Float, CultureInfo.InvariantCulture, out lastNumber))
{
coordinates.Add(lastNumber);
}
return coordinates;
}
public static IEnumerable<CombatLog> EnumerateCombatLogs()
{
foreach (FileInfo file in CombatLogsDirectory.EnumerateFiles("*.txt", new EnumerationOptions() { RecurseSubdirectories = true }))
{
yield return new CombatLog
{
FileInfo = file
};
}
}
public static List<ReadOnlyMemory<char>> GetSubSections(ReadOnlyMemory<char> section)
{
List<ReadOnlyMemory<char>> subSections = new List<ReadOnlyMemory<char>>();
int start = 0;
for (int i = 0; i < section.Length; i++)
{
if (section.Span[i] == '|')
{
if (i > start)
{
subSections.Add(new ReadOnlyMemory<char>(section.Slice(start, i - start).ToArray()));
}
start = i + 1;
}
}
if (section.Length > start)
{
subSections.Add(new ReadOnlyMemory<char>(section.Slice(start, section.Length - start).ToArray()));
}
return subSections;
}
public static List<ReadOnlyMemory<char>> GetSections(ReadOnlyMemory<char> span)
{
List<ReadOnlyMemory<char>> sections = new List<ReadOnlyMemory<char>>();
int start = -1;
int end = -1;
for (int i = 0; i < span.Length; i++)
{
if (span.Span[i] == '[')
{
start = i + 1;
}
else if (span.Span[i] == ']')
{
end = i;
if (start != -1)
{
sections.Add(span.Slice(start, end - start));
start = -1;
}
}
}
return sections;
}
public static IEnumerable<CombatLogItem> EnumerateCombatLogItems(CombatLog combatLog)
{
using (var stream = combatLog.FileInfo.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line is not null)
yield return new CombatLogItem(line.AsMemory());
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment