diff --git a/.gitignore b/.gitignore
index 9491a2f..ec1bbfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,7 @@ MigrationBackup/
 .ionide/
 
 # Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+
+### Rider ###
+.idea/
\ No newline at end of file
diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs
index f65af75..73918ca 100644
--- a/AutoTrackR2/HomePage.xaml.cs
+++ b/AutoTrackR2/HomePage.xaml.cs
@@ -3,309 +3,313 @@ using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
 using System.Windows.Media.Effects;
-using System.IO;
 using System.Windows.Documents;
 using System.Globalization;
 using System.Windows.Media.Imaging;
+using AutoTrackR2.LogEventHandlers;
 
-namespace AutoTrackR2
+namespace AutoTrackR2;
+
+public struct PlayerData
 {
-    public partial class HomePage : UserControl
+    public string? PFPURL;
+    public string? UEERecord;
+    public string? OrgURL;
+    public string? OrgName;
+    public string? JoinDate;
+}
+
+public partial class HomePage : UserControl
+{
+    public HomePage()
     {
-        public HomePage()
+        InitializeComponent();
+
+        // Get the current month
+        string currentMonth = DateTime.Now.ToString("MMMM", CultureInfo.InvariantCulture);
+
+        // Set the TextBlock text
+        KillTallyTitle.Text = $"Kill Tally - {currentMonth}";
+    }
+
+    private Process runningProcess; // Field to store the running process
+    private LogHandler _logHandler;
+    private bool _UIEventsRegistered = false;
+
+
+    // Update Start/Stop button states based on the isRunning flag
+    public void UpdateButtonState(bool isRunning)
+    {
+        var accentColor = (Color)Application.Current.Resources["AccentColor"];
+
+        if (isRunning)
         {
-            InitializeComponent();
+            // Set Start button to "Running..." and apply glow effect
+            StartButton.Content = "Running...";
+            StartButton.IsEnabled = false; // Disable Start button
+            StartButton.Style = (Style)FindResource("DisabledButtonStyle");
 
-            // Get the current month
-            string currentMonth = DateTime.Now.ToString("MMMM", CultureInfo.InvariantCulture);
-
-            // Set the TextBlock text
-            KillTallyTitle.Text = $"Kill Tally - {currentMonth}";
-        }
-
-        private Process runningProcess; // Field to store the running process
-
-        // Update Start/Stop button states based on the isRunning flag
-        public void UpdateButtonState(bool isRunning)
-        {
-            var accentColor = (Color)Application.Current.Resources["AccentColor"];
-
-            if (isRunning)
+            // Add glow effect to the Start button
+            StartButton.Effect = new DropShadowEffect
             {
-                // Set Start button to "Running..." and apply glow effect
-                StartButton.Content = "Running...";
-                StartButton.IsEnabled = false; // Disable Start button
-                StartButton.Style = (Style)FindResource("DisabledButtonStyle");
+                Color = accentColor,
+                BlurRadius = 30,       // Adjust blur radius for desired glow intensity
+                ShadowDepth = 0,       // Set shadow depth to 0 for a pure glow effect
+                Opacity = 1,           // Set opacity for glow visibility
+                Direction = 0          // Direction doesn't matter for glow
+            };
 
-                // Add glow effect to the Start button
-                StartButton.Effect = new DropShadowEffect
-                {
-                    Color = accentColor,
-                    BlurRadius = 30,       // Adjust blur radius for desired glow intensity
-                    ShadowDepth = 0,       // Set shadow depth to 0 for a pure glow effect
-                    Opacity = 1,           // Set opacity for glow visibility
-                    Direction = 0          // Direction doesn't matter for glow
-                };
-
-                StopButton.Style = (Style)FindResource("ButtonStyle");
-                StopButton.IsEnabled = true;  // Enable Stop button
-            }
-            else
-            {
-                // Reset Start button back to its original state
-                StartButton.Content = "Start";
-                StartButton.IsEnabled = true;  // Enable Start button
-
-                // Remove the glow effect from Start button
-                StartButton.Effect = null;
-
-                StopButton.Style = (Style)FindResource("DisabledButtonStyle");
-                StartButton.Style = (Style)FindResource("ButtonStyle");
-                StopButton.IsEnabled = false; // Disable Stop button
-            }
+            StopButton.Style = (Style)FindResource("ButtonStyle");
+            StopButton.IsEnabled = true;  // Enable Stop button
         }
-
-        public void StartButton_Click(object sender, RoutedEventArgs e)
+        else
         {
-            UpdateButtonState(true);
-            string scriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "KillTrackR_MainScript.ps1");
-            TailFileAsync(scriptPath);
+            // Reset Start button back to its original state
+            StartButton.Content = "Start";
+            StartButton.IsEnabled = true;  // Enable Start button
+
+            // Remove the glow effect from Start button
+            StartButton.Effect = null;
+
+            StopButton.Style = (Style)FindResource("DisabledButtonStyle");
+            StartButton.Style = (Style)FindResource("ButtonStyle");
+            StopButton.IsEnabled = false; // Disable Stop button
         }
+        
+        RegisterUIEventHandlers();
+    }
 
-        private async void TailFileAsync(string scriptPath)
-        {
-            await Task.Run(() =>
+    public void StartButton_Click(object sender, RoutedEventArgs e)
+    {
+        UpdateButtonState(true);
+        //string scriptPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "KillTrackR_MainScript.ps1");
+        // TailFileAsync(scriptPath);
+        
+        // _logHandler = new LogHandler(@"U:\\StarCitizen\\StarCitizen\\LIVE\\Game.log");
+        _logHandler = new LogHandler(ConfigManager.LogFile);
+        _logHandler.Initialize();
+    }
+
+    private void RegisterUIEventHandlers()
+    {
+        if (_UIEventsRegistered)
+            return;
+        
+        // Username
+        TrackREventDispatcher.PlayerLoginEvent += (username) => {
+            Dispatcher.Invoke(() =>
             {
-                try
-                {
-                    ProcessStartInfo psi = new ProcessStartInfo
-                    {
-                        FileName = "powershell.exe",
-                        Arguments = $"-NoProfile -ExecutionPolicy Bypass -File \"{scriptPath}\"",
-                        WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory,
-                        RedirectStandardOutput = true,
-                        RedirectStandardError = true,
-                        UseShellExecute = false,
-                        CreateNoWindow = true
-                    };
-
-                    runningProcess = new Process { StartInfo = psi }; // Store the process in the field
-
-                    runningProcess.OutputDataReceived += (s, e) =>
-                    {
-                        if (!string.IsNullOrEmpty(e.Data))
-                        {
-                            Dispatcher.Invoke(() =>
-                            {
-                                // Parse and display key-value pairs in the OutputTextBox
-                                if (e.Data.Contains("PlayerName="))
-                                {
-                                    string pilotName = e.Data.Split('=')[1].Trim();
-                                    PilotNameTextBox.Text = pilotName; // Update the Button's Content
-                                    AdjustFontSize(PilotNameTextBox);
-                                }
-                                else if (e.Data.Contains("PlayerShip="))
-                                {
-                                    string playerShip = e.Data.Split('=')[1].Trim();
-                                    PlayerShipTextBox.Text = playerShip;
-                                    AdjustFontSize(PlayerShipTextBox);
-                                }
-                                else if (e.Data.Contains("GameMode="))
-                                {
-                                    string gameMode = e.Data.Split('=')[1].Trim();
-                                    GameModeTextBox.Text = gameMode;
-                                    AdjustFontSize(GameModeTextBox);
-                                }
-                                else if (e.Data.Contains("KillTally="))
-                                {
-                                    string killTally = e.Data.Split('=')[1].Trim();
-                                    KillTallyTextBox.Text = killTally;
-                                    AdjustFontSize(KillTallyTextBox);
-                                }
-                                else if (e.Data.Contains("NewKill="))
-                                {
-                                    // Parse the kill data
-                                    var killData = e.Data.Split('=')[1].Trim(); // Assume the kill data follows after "NewKill="
-                                    var killParts = killData.Split(',');
-
-                                    // Fetch the dynamic resource for AltTextColor
-                                    var altTextColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AltTextColor"]);
-                                    var accentColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AccentColor"]);
-
-                                    // Fetch the Orbitron FontFamily from resources
-                                    var orbitronFontFamily = (FontFamily)Application.Current.Resources["Orbitron"];
-                                    var gemunuFontFamily = (FontFamily)Application.Current.Resources["Gemunu"];
-
-                                    // Create a new TextBlock for each kill
-                                    var killTextBlock = new TextBlock
-                                    {
-                                        Margin = new Thickness(0, 10, 0, 10),
-                                        Style = (Style)Application.Current.Resources["RoundedTextBlock"], // Apply style for text
-                                        FontSize = 14,
-                                        FontWeight = FontWeights.Bold,
-                                        FontFamily = gemunuFontFamily,
-                                    };
-
-                                    // Add styled content using Run elements
-                                    killTextBlock.Inlines.Add(new Run("Victim Name: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[1]}\n"));
-
-                                    // Repeat for other lines
-                                    killTextBlock.Inlines.Add(new Run("Victim Ship: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[2]}\n"));
-
-                                    killTextBlock.Inlines.Add(new Run("Victim Org: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[3]}\n"));
-
-                                    killTextBlock.Inlines.Add(new Run("Join Date: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[4]}\n"));
-
-                                    killTextBlock.Inlines.Add(new Run("UEE Record: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[5]}\n"));
-
-                                    killTextBlock.Inlines.Add(new Run("Kill Time: ")
-                                    {
-                                        Foreground = altTextColorBrush,
-                                        FontFamily = orbitronFontFamily,
-                                    });
-                                    killTextBlock.Inlines.Add(new Run($"{killParts[6]}"));
-
-                                    // Create a Border and apply the RoundedTextBlockWithBorder style
-                                    var killBorder = new Border
-                                    {
-                                        Style = (Style)Application.Current.Resources["RoundedTextBlockWithBorder"], // Apply border style
-                                    };
-
-                                    // Create a Grid to hold the TextBlock and the Image
-                                    var killGrid = new Grid
-                                    {
-                                        Width = 400, // Adjust the width of the Grid
-                                        Height = 130, // Adjust the height as needed
-                                    };
-
-                                    // Define two columns in the Grid: one for the text and one for the image
-                                    killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) }); // Text column
-                                    killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); // Image column
-
-                                    // Add the TextBlock to the first column of the Grid
-                                    Grid.SetColumn(killTextBlock, 0);
-                                    killGrid.Children.Add(killTextBlock);
-
-                                    // Create the Image for the profile
-                                    var profileImage = new Image
-                                    {
-                                        Source = new BitmapImage(new Uri(killParts[7])), // Assuming the 8th part contains the profile image URL
-                                        Width = 90,
-                                        Height = 90,
-                                        Stretch = Stretch.Fill, // Adjust how the image fits
-                                    };
-
-                                    // Create a Border around the Image
-                                    var imageBorder = new Border
-                                    {
-                                        BorderBrush = accentColorBrush, // Set the border color
-                                        BorderThickness = new Thickness(2), // Set the border thickness
-                                        Padding = new Thickness(0), // Optional padding inside the border
-                                        CornerRadius = new CornerRadius(5),
-                                        Margin = new Thickness(10,18,15,18),
-                                        Child = profileImage // Set the Image as the content of the Border
-                                    };
-
-                                    // Add the Border (with the image inside) to the Grid
-                                    Grid.SetColumn(imageBorder, 1);
-                                    killGrid.Children.Add(imageBorder);
-
-                                    // Set the Grid as the child of the Border
-                                    killBorder.Child = killGrid;
-
-                                    // Add the new Border to the StackPanel inside the Border
-                                    KillFeedStackPanel.Children.Insert(0, killBorder);
-                                }
-
-                                else
-                                {
-                                    DebugPanel.AppendText(e.Data + Environment.NewLine);
-                                }
-                            });
-                        }
-                    };
-
-                    runningProcess.ErrorDataReceived += (s, e) =>
-                    {
-                        if (!string.IsNullOrEmpty(e.Data))
-                        {
-                            Dispatcher.Invoke(() =>
-                            {
-                                DebugPanel.AppendText(e.Data + Environment.NewLine);
-                            });
-                        }
-                    };
-
-                    runningProcess.Start();
-                    runningProcess.BeginOutputReadLine();
-                    runningProcess.BeginErrorReadLine();
-
-                    runningProcess.WaitForExit();
-                }
-                catch (Exception ex)
-                {
-                    Dispatcher.Invoke(() =>
-                    {
-                        MessageBox.Show($"Error running script: {ex.Message}");
-                    });
-                }
+                PilotNameTextBox.Text = username;
+                AdjustFontSize(PilotNameTextBox);
+                LocalPlayerData.Username = username;
             });
-        }
-
-        public void StopButton_Click(object sender, RoutedEventArgs e)
-        {
-            if (runningProcess != null && !runningProcess.HasExited)
+        };
+        
+        // Ship
+        TrackREventDispatcher.InstancedInteriorEvent += (data) => {
+            if (data.OwnerGEID == LocalPlayerData.Username && data.Ship != null)
             {
-                // Kill the running process
-                runningProcess.Kill();
-                runningProcess = null; // Clear the reference to the process
+                Dispatcher.Invoke(() =>
+                {
+                    PlayerShipTextBox.Text = data.Ship;
+                    AdjustFontSize(PlayerShipTextBox);
+                    LocalPlayerData.PlayerShip = data.Ship;
+                });
             }
+        };
+        
+        // Game Mode
+        TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) => {
+            Dispatcher.Invoke(() =>
+            {
+                GameModeTextBox.Text = mode.ToString();
+                AdjustFontSize(GameModeTextBox);
+                LocalPlayerData.CurrentGameMode = mode;
+            });
+        };
+        
+        // Game Version
+        TrackREventDispatcher.GameVersionEvent += (version) => {
+                LocalPlayerData.GameVersion = version;
+        };
+        
+        // Actor Death
+        TrackREventDispatcher.ActorDeathEvent += async (data) => {
+            if (data.VictimPilot != LocalPlayerData.Username)
+            {
+                var playerData = await WebHandler.GetPlayerData(data.VictimPilot);
 
-            // Clear the text boxes
-            System.Threading.Thread.Sleep(200);
-            PilotNameTextBox.Text = string.Empty;
-            PlayerShipTextBox.Text = string.Empty;
-            GameModeTextBox.Text = string.Empty;
-            KillTallyTextBox.Text = string.Empty;
-            KillFeedStackPanel.Children.Clear();
-        }
+                if (playerData != null)
+                {
+                    Dispatcher.Invoke(() => { AddKillToScreen(data, playerData); });
+                    await WebHandler.SubmitKill(data, playerData);
+                }
+            }
+        };
+        
+        _UIEventsRegistered = true;
+    }
 
-        private void AdjustFontSize(TextBlock textBlock)
+    private void AddKillToScreen(ActorDeathData deathData, PlayerData? playerData)
+    { 
+        // Fetch the dynamic resource for AltTextColor
+        var altTextColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AltTextColor"]);
+        var accentColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AccentColor"]);
+
+        // Fetch the Orbitron FontFamily from resources
+        var orbitronFontFamily = (FontFamily)Application.Current.Resources["Orbitron"];
+        var gemunuFontFamily = (FontFamily)Application.Current.Resources["Gemunu"];
+
+        // Create a new TextBlock for each kill
+        var killTextBlock = new TextBlock
         {
-            // Set a starting font size
-            double fontSize = 14;
-            double maxWidth = textBlock.Width;
+            Margin = new Thickness(0, 10, 0, 10),
+            Style = (Style)Application.Current.Resources["RoundedTextBlock"], // Apply style for text
+            FontSize = 14,
+            FontWeight = FontWeights.Bold,
+            FontFamily = gemunuFontFamily,
+        };
 
-            if (string.IsNullOrEmpty(textBlock.Text) || double.IsNaN(maxWidth))
-                return;
+        // Add styled content using Run elements
+        killTextBlock.Inlines.Add(new Run("Victim Name: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        killTextBlock.Inlines.Add(new Run($"{deathData.VictimPilot}\n"));
 
-            // Measure the rendered width of the text
-            FormattedText formattedText = new FormattedText(
+        // Repeat for other lines
+        killTextBlock.Inlines.Add(new Run("Victim Ship: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        killTextBlock.Inlines.Add(new Run($"{deathData.VictimShip}\n"));
+
+        killTextBlock.Inlines.Add(new Run("Victim Org: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        killTextBlock.Inlines.Add(new Run($"{playerData?.OrgName}\n"));
+        
+        killTextBlock.Inlines.Add(new Run("Join Date: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        killTextBlock.Inlines.Add(new Run($"{playerData?.JoinDate}\n"));
+        
+        killTextBlock.Inlines.Add(new Run("UEE Record: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        
+        
+        const string dateFormatString = "dd MMM yyyy HH:mm";
+        var currentTime = DateTime.UtcNow.ToString(dateFormatString);
+        
+        killTextBlock.Inlines.Add(new Run("Kill Time: ")
+        {
+            Foreground = altTextColorBrush,
+            FontFamily = orbitronFontFamily,
+        });
+        killTextBlock.Inlines.Add(new Run($"{currentTime}"));
+
+        // Create a Border and apply the RoundedTextBlockWithBorder style
+        var killBorder = new Border
+        {
+            Style = (Style)Application.Current.Resources["RoundedTextBlockWithBorder"], // Apply border style
+        };
+
+        // Create a Grid to hold the TextBlock and the Image
+        var killGrid = new Grid
+        {
+            Width = 400, // Adjust the width of the Grid
+            Height = 130, // Adjust the height as needed
+        };
+
+        // Define two columns in the Grid: one for the text and one for the image
+        killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) }); // Text column
+        killGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) }); // Image column
+
+        // Add the TextBlock to the first column of the Grid
+        Grid.SetColumn(killTextBlock, 0);
+        killGrid.Children.Add(killTextBlock);
+        
+        // Create the Image for the profile
+        var profileImage = new Image
+        {
+            Source = new BitmapImage(new Uri(playerData?.PFPURL)), // Assuming the 8th part contains the profile image URL
+            Width = 90,
+            Height = 90,
+            Stretch = Stretch.Fill, // Adjust how the image fits
+        };
+
+        // Create a Border around the Image
+        var imageBorder = new Border
+        {
+            BorderBrush = accentColorBrush, // Set the border color
+            BorderThickness = new Thickness(2), // Set the border thickness
+            Padding = new Thickness(0), // Optional padding inside the border
+            CornerRadius = new CornerRadius(5),
+            Margin = new Thickness(10,18,15,18),
+            Child = profileImage // Set the Image as the content of the Border
+        };
+
+        // Add the Border (with the image inside) to the Grid
+        Grid.SetColumn(imageBorder, 1);
+        killGrid.Children.Add(imageBorder);
+
+        // Set the Grid as the child of the Border
+        killBorder.Child = killGrid;
+
+        // Add the new Border to the StackPanel inside the Border
+        Dispatcher.Invoke(() =>
+        {
+            KillFeedStackPanel.Children.Insert(0, killBorder);
+        });
+    }
+
+    public void StopButton_Click(object sender, RoutedEventArgs e)
+    {
+        _logHandler.Stop();
+
+        // Clear the text boxes
+        System.Threading.Thread.Sleep(200);
+        PilotNameTextBox.Text = string.Empty;
+        PlayerShipTextBox.Text = string.Empty;
+        GameModeTextBox.Text = string.Empty;
+        KillTallyTextBox.Text = string.Empty;
+        KillFeedStackPanel.Children.Clear();
+    }
+
+    private void AdjustFontSize(TextBlock textBlock)
+    {
+        // Set a starting font size
+        double fontSize = 14;
+        double maxWidth = textBlock.Width;
+
+        if (string.IsNullOrEmpty(textBlock.Text) || double.IsNaN(maxWidth))
+            return;
+
+        // Measure the rendered width of the text
+        FormattedText formattedText = new FormattedText(
+            textBlock.Text,
+            CultureInfo.CurrentCulture,
+            FlowDirection.LeftToRight,
+            new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
+            fontSize,
+            textBlock.Foreground,
+            VisualTreeHelper.GetDpi(this).PixelsPerDip
+        );
+
+        // Reduce font size until text fits within the width
+        while (formattedText.Width > maxWidth && fontSize > 6)
+        {
+            fontSize -= 0.5;
+            formattedText = new FormattedText(
                 textBlock.Text,
                 CultureInfo.CurrentCulture,
                 FlowDirection.LeftToRight,
@@ -314,24 +318,9 @@ namespace AutoTrackR2
                 textBlock.Foreground,
                 VisualTreeHelper.GetDpi(this).PixelsPerDip
             );
-
-            // Reduce font size until text fits within the width
-            while (formattedText.Width > maxWidth && fontSize > 6)
-            {
-                fontSize -= 0.5;
-                formattedText = new FormattedText(
-                    textBlock.Text,
-                    CultureInfo.CurrentCulture,
-                    FlowDirection.LeftToRight,
-                    new Typeface(textBlock.FontFamily, textBlock.FontStyle, textBlock.FontWeight, textBlock.FontStretch),
-                    fontSize,
-                    textBlock.Foreground,
-                    VisualTreeHelper.GetDpi(this).PixelsPerDip
-                );
-            }
-
-            // Apply the adjusted font size
-            textBlock.FontSize = fontSize;
         }
+
+        // Apply the adjusted font size
+        textBlock.FontSize = fontSize;
     }
 }
diff --git a/AutoTrackR2/LocalPlayerData.cs b/AutoTrackR2/LocalPlayerData.cs
new file mode 100644
index 0000000..668ebcd
--- /dev/null
+++ b/AutoTrackR2/LocalPlayerData.cs
@@ -0,0 +1,17 @@
+namespace AutoTrackR2;
+
+
+public enum GameMode
+{
+    ArenaCommander,
+    PersistentUniverse
+}
+
+public static class LocalPlayerData
+{
+    public static string? Username;
+    public static string? PlayerShip;
+    public static string? GameVersion;
+    public static GameMode CurrentGameMode;
+    public static string? LastSeenVehicleLocation;
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/ActorDeathEvent.cs b/AutoTrackR2/LogEventHandlers/ActorDeathEvent.cs
new file mode 100644
index 0000000..5dee7f3
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/ActorDeathEvent.cs
@@ -0,0 +1,59 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public struct ActorDeathData
+{
+    public string VictimPilot;
+    public string VictimShip;
+    public string Player;
+    public string Weapon;
+    public string Class;
+    public string DamageType;
+    public string Timestamp;
+}
+
+public class ActorDeathEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    public ActorDeathEvent()
+    {
+        Pattern = new Regex(@"<Actor Death> CActor::Kill: '(?<EnemyPilot>[^']+)' \[\d+\] in zone '(?<EnemyShip>[^']+)' killed by '(?<Player>[^']+)' \[[^']+\] using '(?<Weapon>[^']+)' \[Class (?<Class>[^\]]+)\] with damage type '(?<DamageType>[^']+)");
+    }
+    
+    Regex cleanUpPattern = new Regex(@"^(.+?)_\d+$");
+
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+        
+        var data = new ActorDeathData {
+            VictimPilot = match.Groups["EnemyPilot"].Value,
+            VictimShip = match.Groups["EnemyShip"].Value,
+            Player = match.Groups["Player"].Value,
+            Weapon = match.Groups["Weapon"].Value,
+            Class = match.Groups["Class"].Value,
+            DamageType = match.Groups["DamageType"].Value,
+            Timestamp = entry.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")
+        };
+        
+        if (cleanUpPattern.IsMatch(data.VictimShip))
+        {
+            data.VictimShip = cleanUpPattern.Match(data.VictimShip).Groups[1].Value;
+        }
+        
+        if (cleanUpPattern.IsMatch(data.Weapon))
+        {
+            data.Weapon = cleanUpPattern.Match(data.Weapon).Groups[1].Value;
+        }
+        
+        
+        TrackREventDispatcher.OnActorDeathEvent(data);
+        
+    }
+
+
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/GameVersionEvent.cs b/AutoTrackR2/LogEventHandlers/GameVersionEvent.cs
new file mode 100644
index 0000000..abeca73
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/GameVersionEvent.cs
@@ -0,0 +1,21 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public class GameVersionEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    
+    public GameVersionEvent()
+    {
+        Pattern = new Regex(@"--system-trace-env-id='pub-sc-alpha-(?<GameVersion>\d{3,4}-\d{7})'");
+    }
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+        
+        TrackREventDispatcher.OnGameVersionEvent(match.Groups["GameVersion"].Value);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/ILogEventHandler.cs b/AutoTrackR2/LogEventHandlers/ILogEventHandler.cs
new file mode 100644
index 0000000..adaf1ba
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/ILogEventHandler.cs
@@ -0,0 +1,10 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public interface ILogEventHandler
+{ 
+    Regex Pattern { get; }
+    void Handle(LogEntry entry);
+
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/InArenaCommanderEvent.cs b/AutoTrackR2/LogEventHandlers/InArenaCommanderEvent.cs
new file mode 100644
index 0000000..defdd4c
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/InArenaCommanderEvent.cs
@@ -0,0 +1,22 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public class InArenaCommanderEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    
+    public InArenaCommanderEvent()
+    {
+        Pattern = new Regex("Requesting Mode Change");
+    }
+    
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+        
+        TrackREventDispatcher.OnPlayerChangedGameModeEvent(GameMode.ArenaCommander);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/InPersistentUniverseEvent.cs b/AutoTrackR2/LogEventHandlers/InPersistentUniverseEvent.cs
new file mode 100644
index 0000000..2f7c53e
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/InPersistentUniverseEvent.cs
@@ -0,0 +1,22 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public class InPersistentUniverseEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    
+    public InPersistentUniverseEvent()
+    {
+        Pattern = new Regex(@"<\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z> \[Notice\] <ContextEstablisherTaskFinished> establisher=""CReplicationModel"" message=""CET completed"" taskname=""StopLoadingScreen"" state=[^\s()]+\(\d+\) status=""Finished"" runningTime=\d+\.\d+ numRuns=\d+ map=""megamap"" gamerules=""SC_Default"" sessionId=""[a-f0-9\-]+"" \[Team_Network\]\[Network\]\[Replication\]\[Loading\]\[Persistence\]");
+    }
+    
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+        
+        TrackREventDispatcher.OnPlayerChangedGameModeEvent(GameMode.PersistentUniverse);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/InstancedInteriorEvent.cs b/AutoTrackR2/LogEventHandlers/InstancedInteriorEvent.cs
new file mode 100644
index 0000000..62ab732
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/InstancedInteriorEvent.cs
@@ -0,0 +1,77 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public struct InstancedInteriorData
+{
+    public string Entity;
+    public string OwnerGEID;
+    public string ManagerGEID;
+    public string InstancedInterior;
+    public string? Ship;
+}
+
+// A ship loadout has been changed
+public class InstancedInteriorEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+
+    private Regex _shipManufacturerPattern;
+    private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
+    
+    private List<string> _shipManufacturers = new List<string>
+    {
+        "ORIG",
+        "CRUS",
+        "RSI",
+        "AEGS",
+        "VNCL",
+        "DRAK",
+        "ANVL",
+        "BANU",
+        "MISC",
+        "CNOU",
+        "XIAN",
+        "GAMA",
+        "TMBL",
+        "ESPR",
+        "KRIG",
+        "GRIN",
+        "XNAA",
+        "MRAI"
+    };
+    
+    public InstancedInteriorEvent()
+    {
+        Pattern = new Regex(@"\[InstancedInterior\] OnEntityLeaveZone - InstancedInterior \[(?<InstancedInterior>[^\]]+)\] \[\d+\] -> Entity \[(?<Entity>[^\]]+)\] \[\d+\] -- m_openDoors\[\d+\], m_managerGEID\[(?<ManagerGEID>\d+)\], m_ownerGEID\[(?<OwnerGEID>[^\[]+)\]");
+        _shipManufacturerPattern = new Regex($"^({string.Join("|", _shipManufacturers)})");
+    }
+
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+        
+        var data = new InstancedInteriorData {
+            Entity = match.Groups["Entity"].Value,
+            OwnerGEID = match.Groups["OwnerGEID"].Value,
+            ManagerGEID = match.Groups["ManagerGEID"].Value,
+            InstancedInterior = match.Groups["InstancedInterior"].Value,
+        };
+        
+        match = _shipManufacturerPattern.Match(data.Entity);
+        if (match.Success)
+        {
+            match = _cleanUpPattern.Match(data.Entity);
+            if (match.Success)
+            {
+                data.Ship = match.Groups[1].Value;
+            }
+        }
+        
+        TrackREventDispatcher.OnInstancedInteriorEvent(data);
+        
+    }
+
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/LoginEvent.cs b/AutoTrackR2/LogEventHandlers/LoginEvent.cs
new file mode 100644
index 0000000..e6f9cc4
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/LoginEvent.cs
@@ -0,0 +1,24 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+// Local player has logged in
+public class LoginEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    
+    public LoginEvent()
+    {
+        Pattern = new Regex(@"\[Notice\] <Legacy login response> \[CIG-net\] User Login Success - Handle\[(?<Player>[A-Za-z0-9_-]+)\]");
+    }
+    
+    public void Handle(LogEntry entry)
+    {
+        if (entry.Message is null) return;
+        
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success) return;
+
+        TrackREventDispatcher.OnPlayerLoginEvent(match.Groups["Player"].Value);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs b/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs
new file mode 100644
index 0000000..041859a
--- /dev/null
+++ b/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs
@@ -0,0 +1,59 @@
+using System.Text.RegularExpressions;
+
+namespace AutoTrackR2.LogEventHandlers;
+
+public struct VehicleDestructionData
+{
+    public string Vehicle { get; set; }
+    public string VehicleZone { get; set; }
+    public float PosX { get; set; }
+    public float PosY { get; set; }
+    public float PosZ { get; set; }
+    public string Driver { get; set; }
+    public int DestroyLevelFrom { get; set; }
+    public int DestroyLevelTo { get; set; }
+    public string CausedBy { get; set; }
+    public string DamageType { get; set; }
+}
+
+public class VehicleDestructionEvent : ILogEventHandler
+{
+    public Regex Pattern { get; }
+    
+    public VehicleDestructionEvent()
+    {
+        Pattern = new Regex("""
+                            "<(?<timestamp>[^>]+)> \[Notice\] <Vehicle Destruction> CVehicle::OnAdvanceDestroyLevel: " +
+                            "Vehicle '(?<vehicle>[^']+)' \[\d+\] in zone '(?<vehicle_zone>[^']+)' " +
+                            "\[pos x: (?<pos_x>[-\d\.]+), y: (?<pos_y>[-\d\.]+), z: (?<pos_z>[-\d\.]+) " +
+                            "vel x: [^,]+, y: [^,]+, z: [^\]]+\] driven by '(?<driver>[^']+)' \[\d+\] " +
+                            "advanced from destroy level (?<destroy_level_from>\d+) to (?<destroy_level_to>\d+) " +
+                            "caused by '(?<caused_by>[^']+)' \[\d+\] with '(?<damage_type>[^']+)'"
+                            """);
+    }
+
+    public void Handle(LogEntry entry)
+    {
+        var match = Pattern.Match(entry.Message);
+        if (!match.Success)
+        {
+            return;
+        }
+
+        var data = new VehicleDestructionData
+        {
+            Vehicle = match.Groups["vehicle"].Value,
+            VehicleZone = match.Groups["vehicle_zone"].Value,
+            PosX = float.Parse(match.Groups["pos_x"].Value),
+            PosY = float.Parse(match.Groups["pos_y"].Value),
+            PosZ = float.Parse(match.Groups["pos_z"].Value),
+            Driver = match.Groups["driver"].Value,
+            DestroyLevelFrom = int.Parse(match.Groups["destroy_level_from"].Value),
+            DestroyLevelTo = int.Parse(match.Groups["destroy_level_to"].Value),
+            CausedBy = match.Groups["caused_by"].Value,
+            DamageType = match.Groups["damage_type"].Value,
+        };
+
+        TrackREventDispatcher.OnVehicleDestructionEvent(data);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/LogHandler.cs b/AutoTrackR2/LogHandler.cs
new file mode 100644
index 0000000..0bff5dc
--- /dev/null
+++ b/AutoTrackR2/LogHandler.cs
@@ -0,0 +1,101 @@
+using System.IO;
+using System.Text.RegularExpressions;
+using AutoTrackR2.LogEventHandlers;
+
+namespace AutoTrackR2;
+
+
+// Represents a single log entry
+// This is the object that will be passed to each handler, mostly for convenience
+public class LogEntry
+{
+    public DateTime Timestamp { get; set; }
+    public required string? Message { get; set; }
+    
+}
+public class LogHandler(string logPath)
+{
+    private readonly string? _logPath = logPath;
+    private FileStream? _fileStream;
+    private StreamReader? _reader;
+
+    private CancellationTokenSource cancellationToken = new CancellationTokenSource(); 
+    Thread? monitorThread;
+    
+    // Handlers that should be run on every log entry
+    // Overlap with _startupEventHandlers is fine
+    private readonly List<ILogEventHandler> _eventHandlers = [
+        new LoginEvent(),
+        new InstancedInteriorEvent(),
+        new InArenaCommanderEvent(),
+        new InPersistentUniverseEvent(),
+        new GameVersionEvent(),
+    ];
+
+    // Initialize the LogHandler and run all startup handlers
+    public void Initialize()
+    {
+        if (!File.Exists(_logPath))
+        {
+            throw new FileNotFoundException("Log file not found", _logPath);
+        }
+        
+        _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+        _reader = new StreamReader(_fileStream);
+        
+        while (_reader.ReadLine() is { } line)
+        {
+            HandleLogEntry(line);
+        }
+
+        // Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
+        _eventHandlers.Add(new ActorDeathEvent());
+        
+        monitorThread = new Thread(() => MonitorLog(cancellationToken.Token));
+        monitorThread.Start();
+    }
+
+    public void Stop()
+    {
+        // Stop the monitor thread
+        cancellationToken?.Cancel();
+        _reader?.Close();
+        _fileStream?.Close();
+    }
+    
+    // Parse a single line of the log file and run matching handlers
+    private void HandleLogEntry(string line)
+    {
+        foreach (var handler in _eventHandlers)
+        {
+            var match = handler.Pattern.Match(line);
+            if (!match.Success) continue;
+            
+            var entry = new LogEntry
+            {
+                Timestamp = DateTime.Now,
+                Message = line
+            };
+            handler.Handle(entry);
+            break;
+        }
+    }
+    
+    private void MonitorLog(CancellationToken token)
+    {
+        while (!token.IsCancellationRequested)
+        {
+            if (_reader?.ReadLine() is { } line)
+            {
+                HandleLogEntry(line);
+                Console.WriteLine(line);
+            }
+            else
+            {
+                // Wait for new lines to be written to the log file
+                Thread.Sleep(1000);
+            }
+        }
+        Console.WriteLine("Monitor thread stopped");
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/TrackREventDispatcher.cs b/AutoTrackR2/TrackREventDispatcher.cs
new file mode 100644
index 0000000..8b3f12d
--- /dev/null
+++ b/AutoTrackR2/TrackREventDispatcher.cs
@@ -0,0 +1,49 @@
+using AutoTrackR2.LogEventHandlers;
+
+namespace AutoTrackR2;
+
+public static class TrackREventDispatcher
+{
+    // Local Player Login
+    public static event Action<string>? PlayerLoginEvent;
+    public static void OnPlayerLoginEvent(string playerName)
+    {
+        PlayerLoginEvent?.Invoke(playerName);
+    }
+
+    // An instanced interior has changed
+    // Example: Player enters/leaves a ship
+    public static event Action<InstancedInteriorData>? InstancedInteriorEvent;
+    public static void OnInstancedInteriorEvent(InstancedInteriorData data)
+    {
+        InstancedInteriorEvent?.Invoke(data);
+    }
+    
+    // Player changed GameMode (AC or PU)
+    public static event Action<GameMode>? PlayerChangedGameModeEvent;
+    public static void OnPlayerChangedGameModeEvent(GameMode mode)
+    {
+        PlayerChangedGameModeEvent?.Invoke(mode);
+    }
+    
+    // Game version has been detected
+    public static event Action<string>? GameVersionEvent;
+    public static void OnGameVersionEvent(string value)
+    {
+        GameVersionEvent?.Invoke(value);
+    }
+    
+    // Actor has died
+    public static event Action<ActorDeathData>? ActorDeathEvent;
+    public static void OnActorDeathEvent(ActorDeathData data)
+    {
+        ActorDeathEvent?.Invoke(data);
+    }
+    
+    // Vehicle has been destroyed
+    public static event Action<VehicleDestructionData>? VehicleDestructionEvent;
+    public static void OnVehicleDestructionEvent(VehicleDestructionData data)
+    {
+        VehicleDestructionEvent?.Invoke(data);
+    }
+}
\ No newline at end of file
diff --git a/AutoTrackR2/UpdatePage.xaml.cs b/AutoTrackR2/UpdatePage.xaml.cs
index 11f4d24..75d9e70 100644
--- a/AutoTrackR2/UpdatePage.xaml.cs
+++ b/AutoTrackR2/UpdatePage.xaml.cs
@@ -9,7 +9,7 @@ namespace AutoTrackR2
 {
     public partial class UpdatePage : UserControl
     {
-        private string currentVersion = "v2.07";
+        public static string currentVersion = "v2.08";
         private string latestVersion;
 
         public UpdatePage()
diff --git a/AutoTrackR2/WebHandler.cs b/AutoTrackR2/WebHandler.cs
new file mode 100644
index 0000000..73740e3
--- /dev/null
+++ b/AutoTrackR2/WebHandler.cs
@@ -0,0 +1,102 @@
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using AutoTrackR2.LogEventHandlers;
+
+namespace AutoTrackR2;
+
+public static class WebHandler
+{
+    class APIKillData
+    {
+            public string? victim_ship { get; set; }
+            public string? victim{ get; set; }
+            public string? enlisted{ get; set; }
+            public string? rsi{ get; set; }
+            public string? weapon{ get; set; }
+            public string? method{ get; set; }
+            public string? loadout_ship{ get; set; }
+            public string? game_version{ get; set; }
+            public string? gamemode{ get; set; }
+            public string? trackr_version{ get; set; }
+            public string? location{ get; set; }
+    }
+
+    public static async Task<PlayerData?> GetPlayerData(string enemyPilot)
+    {
+        var joinDataPattern = new Regex("<span class=\"label\">Enlisted</span>\\s*<strong class=\"value\">([^<]+)</strong>");
+        var ueePattern = new Regex("<p class=\"entry citizen-record\">\\s*<span class=\"label\">UEE Citizen Record<\\/span>\\s*<strong class=\"value\">#?(n\\/a|\\d+)<\\/strong>\\s*<\\/p>");
+        var orgPattern = new Regex("\\/orgs\\/(?<OrgURL>[A-z0-9]+)\" .*\\>(?<OrgName>.*)<");
+        var pfpPattern = new Regex("/media/(.*)\"");
+       
+        // Make web request to check player data
+        var playerData = new PlayerData();
+        var httpClient = new HttpClient();
+        var response = await httpClient.GetAsync($"https://robertsspaceindustries.com/en/citizens/{enemyPilot}");
+        
+        if (response.StatusCode != HttpStatusCode.OK)
+        {
+            return null;
+        }
+
+        var content = await response.Content.ReadAsStringAsync();
+        var joinDataMatch = joinDataPattern.Match(content);
+        if (joinDataMatch.Success)
+        {
+            playerData.JoinDate = joinDataMatch.Groups[1].Value;
+        }
+        
+        var ueeMatch = ueePattern.Match(content);
+        if (ueeMatch.Success)
+        {
+            playerData.UEERecord = ueeMatch.Groups[1].Value;
+        }
+        
+        var orgMatch = orgPattern.Match(content);
+        if (orgMatch.Success)
+        {
+            playerData.OrgName = orgMatch.Groups["OrgName"].Value;
+            playerData.OrgURL = "https://robertsspaceindustries.com/en/orgs/" + orgMatch.Groups["OrgURL"].Value;
+        }
+        
+        var pfpMatch = pfpPattern.Match(content);
+        if (pfpMatch.Success)
+        {
+            var match = pfpMatch.Groups[1].Value;
+            if (match.Contains("heap_thumb"))
+            {
+                playerData.PFPURL = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg";
+            }
+            else
+            {
+                playerData.PFPURL = "https://robertsspaceindustries.com/media/" + pfpMatch.Groups[1].Value;
+            }
+        }
+
+        return playerData;
+    }
+    
+    public static async Task SubmitKill(ActorDeathData deathData, PlayerData? enemyPlayerData)
+    {
+        var killData = new APIKillData
+        {
+            victim_ship = deathData.VictimShip,
+            victim = deathData.VictimPilot,
+            enlisted = enemyPlayerData?.JoinDate,
+            rsi = enemyPlayerData?.UEERecord,
+            weapon = deathData.Weapon,
+            method = deathData.DamageType,
+            loadout_ship = LocalPlayerData.PlayerShip ?? "Unknown",
+            game_version = LocalPlayerData.GameVersion ?? "Unknown",
+            gamemode = LocalPlayerData.CurrentGameMode.ToString() ?? "Unknown",
+            trackr_version = UpdatePage.currentVersion ?? "Unknown",
+            location = LocalPlayerData.LastSeenVehicleLocation ?? "Unknown"
+        };
+        
+        var httpClient = new HttpClient();
+        string jsonData = JsonSerializer.Serialize(killData);
+        await httpClient.PostAsync(ConfigManager.ApiUrl + "/register-kill", new StringContent(jsonData, Encoding.UTF8, "application/json"));
+    }
+}
\ No newline at end of file