From 9400a087e22f6ce734e157d1c77085eeb6c7cd7c Mon Sep 17 00:00:00 2001 From: BubbaGumpShrump <joshua.p.farrell@gmail.com> Date: Wed, 19 Feb 2025 16:27:47 -0500 Subject: [PATCH 01/17] Location Fix --- AutoTrackR2/KillTrackR_MainScript.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AutoTrackR2/KillTrackR_MainScript.ps1 b/AutoTrackR2/KillTrackR_MainScript.ps1 index 26f0ed2..a418a80 100644 --- a/AutoTrackR2/KillTrackR_MainScript.ps1 +++ b/AutoTrackR2/KillTrackR_MainScript.ps1 @@ -250,15 +250,18 @@ function Read-LogEntry { if ($damageType -eq "Bullet" -or $weapon -like "apar_special_ballistic*") { $ship = "Player" $enemyShip = "Player" + $global:got_location = "NONE" } If ($ship -match $cleanupPattern){ $ship = $matches[1] } if ($ship -notmatch $shipManPattern){ $ship = "Player" + $global:got_location = "NONE" } If ($enemyShip -notmatch $shipManPattern -and $enemyShip -notlike "Passenger" ) { $enemyShip = "Player" + $global:got_location = "NONE" } # Repeatedly remove all suffixes From 4a001801aa650874d37f4cea81f2ab4535519a62 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:02:42 +1100 Subject: [PATCH 02/17] Added git actions --- .github/workflows/build.yml | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0577741 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,40 @@ +name: Build and Package + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: "6.0.x" + + - name: Restore dependencies + run: dotnet restore AutoTrackR2.sln + + - name: Build + run: dotnet build AutoTrackR2.sln --configuration Release --no-restore + + - name: Create artifacts directory + run: mkdir artifacts + + - name: Copy build output + run: | + Copy-Item "AutoTrackR2\bin\Release\net6.0-windows\*" "artifacts\" + Copy-Item "AutoTrackR2_Setup\bin\Release\*" "artifacts\" + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: AutoTrackR2-Release + path: artifacts/ + retention-days: 5 From b680debb49d629c582441ac266b34d9ab94a3d00 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:09:14 +1100 Subject: [PATCH 03/17] Main branch is default not main --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0577741..14c2946 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,9 @@ name: Build and Package on: push: - branches: [main] + branches: [default] pull_request: - branches: [main] + branches: [default] jobs: build: From e2df69526d706682209294f751c17ea02b61d887 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:12:03 +1100 Subject: [PATCH 04/17] Git actions v4 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 14c2946..a29d148 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: Copy-Item "AutoTrackR2_Setup\bin\Release\*" "artifacts\" - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: AutoTrackR2-Release path: artifacts/ From dfc2dd5477e771d6c164a233e0b1ffc91f274939 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:15:25 +1100 Subject: [PATCH 05/17] Use correct net framework. --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a29d148..8e31dd3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "9.0.x" - name: Restore dependencies run: dotnet restore AutoTrackR2.sln @@ -29,7 +29,7 @@ jobs: - name: Copy build output run: | - Copy-Item "AutoTrackR2\bin\Release\net6.0-windows\*" "artifacts\" + Copy-Item "AutoTrackR2\bin\Release\net9.0-windows\*" "artifacts\" Copy-Item "AutoTrackR2_Setup\bin\Release\*" "artifacts\" - name: Upload artifacts From 49dfbb229191ac26997165b9acceecab07005730 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:17:29 +1100 Subject: [PATCH 06/17] debugging --- .github/workflows/build.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8e31dd3..d6f30fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,13 +24,24 @@ jobs: - name: Build run: dotnet build AutoTrackR2.sln --configuration Release --no-restore + - name: List build output directories + run: | + Write-Host "Listing build output directories:" + Get-ChildItem -Recurse -Directory -Filter "Release" | ForEach-Object { Write-Host $_.FullName } + - name: Create artifacts directory run: mkdir artifacts - name: Copy build output run: | - Copy-Item "AutoTrackR2\bin\Release\net9.0-windows\*" "artifacts\" - Copy-Item "AutoTrackR2_Setup\bin\Release\*" "artifacts\" + $releaseDir = Get-ChildItem -Recurse -Directory -Filter "Release" | Select-Object -First 1 + if ($releaseDir) { + Write-Host "Copying from: $($releaseDir.FullName)" + Copy-Item "$($releaseDir.FullName)\*" "artifacts\" -Recurse + } else { + Write-Host "No Release directory found" + exit 1 + } - name: Upload artifacts uses: actions/upload-artifact@v4 From bd8bbfb365e739a30d99f5abdbad1e9e8a216c35 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sat, 5 Apr 2025 23:21:09 +1100 Subject: [PATCH 07/17] Added version to git actions. --- .github/workflows/build.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6f30fa..ea031d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,12 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Extract version + id: version + run: | + $version = (Select-String -Path "AutoTrackR2/UpdatePage.xaml.cs" -Pattern 'currentVersion = "(.+?)"' | Select-Object -First 1).Matches.Groups[1].Value + echo "version=$version" >> $env:GITHUB_OUTPUT + - name: Setup .NET uses: actions/setup-dotnet@v3 with: @@ -46,6 +52,6 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: AutoTrackR2-Release + name: AutoTrackR2-${{ steps.version.outputs.version }} path: artifacts/ retention-days: 5 From a09a99c0646e6bd4291411ee281d0b2af0ad3982 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sun, 6 Apr 2025 00:06:04 +1100 Subject: [PATCH 08/17] Added autohotkey scripts to artifact --- .github/workflows/build.yml | 7 +++++++ AutoTrackR2/scripts/videorecord.ahk | 2 ++ AutoTrackR2/scripts/visorwipe.ahk | 2 ++ 3 files changed, 11 insertions(+) create mode 100644 AutoTrackR2/scripts/videorecord.ahk create mode 100644 AutoTrackR2/scripts/visorwipe.ahk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ea031d1..77e2971 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,6 +49,13 @@ jobs: exit 1 } + - name: Copy AutoHotkey scripts + run: | + # Create scripts directory in artifacts + mkdir artifacts\scripts + # Copy AutoHotkey scripts + Copy-Item "AutoTrackR2\scripts\*.ahk" -Destination "artifacts\scripts\" -Force + - name: Upload artifacts uses: actions/upload-artifact@v4 with: diff --git a/AutoTrackR2/scripts/videorecord.ahk b/AutoTrackR2/scripts/videorecord.ahk new file mode 100644 index 0000000..11c5556 --- /dev/null +++ b/AutoTrackR2/scripts/videorecord.ahk @@ -0,0 +1,2 @@ +; AutoHotkey v2 script to press Win + Alt + G +Send("{LWin Down}{Alt Down}g{Alt Up}{LWin Up}") ; Simulate pressing Win + Alt + G \ No newline at end of file diff --git a/AutoTrackR2/scripts/visorwipe.ahk b/AutoTrackR2/scripts/visorwipe.ahk new file mode 100644 index 0000000..564b7f2 --- /dev/null +++ b/AutoTrackR2/scripts/visorwipe.ahk @@ -0,0 +1,2 @@ +; AutoHotkey v2 script to press Alt + X +Send("{Alt Down}x{Alt Up}") ; Simulate pressing Alt + X \ No newline at end of file From bab96123c555ca964cd7faf80bdf176ad6319a3a Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sun, 6 Apr 2025 00:11:12 +1100 Subject: [PATCH 09/17] Added autohotkey scripts to artifacts --- .github/workflows/build.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77e2971..294769d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,16 +49,23 @@ jobs: exit 1 } - - name: Copy AutoHotkey scripts - run: | - # Create scripts directory in artifacts - mkdir artifacts\scripts - # Copy AutoHotkey scripts - Copy-Item "AutoTrackR2\scripts\*.ahk" -Destination "artifacts\scripts\" -Force - - - name: Upload artifacts + - name: Upload application artifact uses: actions/upload-artifact@v4 with: name: AutoTrackR2-${{ steps.version.outputs.version }} path: artifacts/ retention-days: 5 + + - name: Upload visorwipe script + uses: actions/upload-artifact@v4 + with: + name: visorwipe.ahk + path: AutoTrackR2/scripts/visorwipe.ahk + retention-days: 5 + + - name: Upload videorecord script + uses: actions/upload-artifact@v4 + with: + name: videorecord.ahk + path: AutoTrackR2/scripts/videorecord.ahk + retention-days: 5 From 408c071a85523577bb4447d83049e817f3e57992 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Sun, 6 Apr 2025 00:13:38 +1100 Subject: [PATCH 10/17] Updated Readme. --- README.md | 148 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 1a5db19..08b38c6 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,104 @@ -AutoTrackR2 - Star Citizen Kill-Tracking Tool +# AutoTrackR2 - Star Citizen Kill-Tracking Tool + AutoTrackR2 is a powerful and customizable kill-tracking tool for Star Citizen. Designed with gankers and combat enthusiasts in mind, it integrates seamlessly with the game to log, display, and manage your kills, providing detailed information and optional API integration for advanced tracking. -🚀 Features -Log File Integration: Point to Star Citizen's live game.log to track kills in real-time. +## Features -API Integration (Optional): -Configure a desired API to send kill data for external tracking or display. -Secure your data with an optional API key. +- **Log File Integration**: Point to Star Citizen's live game.log to track kills in real-time. +- **API Integration (Optional)**: + - Configure a desired API to send kill data for external tracking or display. + - Secure your data with an optional API key. +- **Video Clipping (Optional)**: + - Set a path to your clipping software to save kills automatically. +- **Visor Wipe Integration (Optional)**: + - Automates visor wiping using an AutoHotkey script (`visorwipe.ahk`). + - Requires AutoHotkey v2. + - Script must be placed in `%LOCALAPPDATA%\AutoTrackR2\`. +- **Video Record Integration (Optional)**: + - Customize the `videorecord.ahk` script for your specific video recording keybinds. + - Requires AutoHotkey v2. + - Script must be placed in `%LOCALAPPDATA%\AutoTrackR2\` +- **Offline Mode**: + - Disables API submissions. The tool will still scrape and display information from the RobertsSpaceIndustries website for the profile of whomever you have killed. +- **Custom Themes**: + - Easily add or modify themes by adjusting the `ThemeSlider_ValueChanged` function in `ConfigPage.xaml.cs`. + - Update the ThemeSlider maximum value in `ConfigPage.xaml` to reflect the number of themes added. -Video Clipping (Optional): -Set a path to your clipping software to save kills automatically. +## Setup -Visor Wipe Integration: -Automates visor wiping using an AutoHotkey script (visorwipe.ahk). -Requires AutoHotkey v2. -Script must be placed in C:\Users\<Username>\AppData\Local\AutoTrackR2\. +1. **Prerequisites**: -Video Record Integration: -Customize the videorecord.ahk script for your specific video recording keybinds. -Requires AutoHotkey v2. -Script must be placed in C:\Users\<Username>\AppData\Local\AutoTrackR2\ + - Windows 10 or later + - .NET Framework 4.7.2 or higher + - Star Citizen installed + - AutoHotkey v2 (optional - only needed for visor wipe and video recording features): + - Download from the official website at https://www.autohotkey.com/ + - Install AutoHotkey v2 (not v1.x as they are not compatible) + - Verify installation by right-clicking on your desktop and confirming "New > AutoHotkey v2 Script" appears in the context menu -Offline Mode: -Disables API submissions. The tool will still scrape and display information from the RobertsSpaceIndustries website for the profile of whomever you have killed. +2. **Installation Steps**: -Custom Themes: -Easily add or modify themes by adjusting the ThemeSlider_ValueChanged function in ConfigPage.xaml.cs. -Update the ThemeSlider maximum value in ConfigPage.xaml to reflect the number of themes added. + - Download the latest release package from the releases page + - Run the installer and follow the on-screen instructions + - Launch AutoTrackR2 after installation completes -📁 Configuration -Log File: -Specify the path to Star Citizen's game.log. +3. **Initial Configuration**: + - Open the Configuration panel within the application + - Set the path to your Star Citizen game.log file (typically found in `[Star Citizen Install Path]\LIVE\game.log`) + - Configure API settings if desired (leave blank for offline mode) + - Set up video clipping paths if using this feature + - If using AutoHotkey features, verify the scripts are properly placed in the `%LOCALAPPDATA%\AutoTrackR2\` location -API Settings (Optional): +## Configuration -API URL: Provide the endpoint for posting kill data. +- **Log File**: + - Specify the path to Star Citizen's `game.log`. +- **API Settings (Optional)**: + - API URL: Provide the endpoint for posting kill data. + - API Key: Secure access to the API with your unique key. +- **Video Clipping Path (Optional)**: + - Set the directory where your clipping software saves kills. +- **Visor Wipe Setup (Optional)**: + - Place `visorwipe.ahk` in `%LOCALAPPDATA%\AutoTrackR2\`. + - AutoHotkey v2 is required. +- **Video Recording Setup (Optional)**: + - Modify `videorecord.ahk` to use the keybinds of your video recording software. + - Place `videorecord.ahk` in `%LOCALAPPDATA%\AutoTrackR2\`. + - AutoHotkey v2 is required. +- **Offline Mode**: + - Enable to disable API submission. Restart the tracker to apply changes. -API Key: Secure access to the API with your unique key. +## Privacy & Data Usage -Video Clipping Path (Optional): -Set the directory where your clipping software saves kills. +- **No Personal Data Collection**: + - AutoTrackR2 does not collect or store personal or system information, other than common file paths to manage necessary files. +- **Access and Permissions**: + - The tool reads its own `config.ini` and the `game.log` from Star Citizen. It will also create a CSV file of all your logged kills, stored locally on your machine in the `%LOCALAPPDATA%\AutoTrackR2\` folder. +- **Optional Data Submission**: + - Data is only sent to an API if explicitly configured by the user. Offline Mode disables all outgoing submissions. +- **Killfeed Scraping**: + - The program scrapes the profile page of killed players to display their information locally. This feature remains active even in Offline Mode. -Visor Wipe Setup: -Place visorwipe.ahk in C:\Users\<Username>\AppData\Local\AutoTrackR2\. -AutoHotkey v2 is required. +## Customization -Video Recording Setup: -Modify videorecord.ahk to use the keybinds of your video recording software. -Place videorecord.ahk in C:\Users\<Username>\AppData\Local\AutoTrackR2\. -AutoHotkey v2 is required. - -Offline Mode: -Enable to disable API submission. Restart the tracker to apply changes. - -🛡️ Privacy & Data Usage -No Personal Data Collection: -AutoTrackR2 does not collect or store personal or system information, other than common file paths to manage necessary files. - -Access and Permissions: -The tool reads its own config.ini and the game.log from Star Citizen. It will also create a CSV file of all your logged kills, stored locally on your machine in the AppData folder. - -Optional Data Submission: -Data is only sent to an API if explicitly configured by the user. Offline Mode disables all outgoing submissions. - -Killfeed Scraping: -The program scrapes the profile page of killed players to display their information locally. This feature remains active even in Offline Mode. - -⚙️ Installation -Download the latest release from the releases page. -Follow the setup instructions included in the installer. -Configure the tool using the settings outlined above. - -💡 Customization To customize themes or behaviors: -Add Themes: -Update the ThemeSlider_ValueChanged function in ConfigPage.xaml.cs with your desired colors and logos. -Adjust the ThemeSlider maximum value in ConfigPage.xaml to match the number of themes. +- **Add Themes**: + - Update the `ThemeSlider_ValueChanged` function in `ConfigPage.xaml.cs` with your desired colors and logos. + - Adjust the ThemeSlider maximum value in `ConfigPage.xaml` to match the number of themes. +- **Modify AHK Scripts (Optional)**: + - Edit `visorwipe.ahk` and `videorecord.ahk` to fit your specific keybinds and preferences. -Modify AHK Scripts: -Edit visorwipe.ahk and videorecord.ahk to fit your specific keybinds and preferences. +## Support -📞 Support -For questions, issues, or feature requests, please visit discord.gg/griefernet. +For questions, issues, or feature requests: + +- Join our Discord server: [discord.gg/griefernet](https://discord.gg/griefernet) +- Report bugs through the Discord's #autotrackr-bugs channel +- For urgent issues, you can escalate in Discord by tagging moderators + +## License -🔒 License AutoTrackR2 is released under the GNU v3 License. GRIEFERNET VICTORY! From 5fc670b65237a0d2af99592ef133f94f97f034d9 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 08:44:42 +1000 Subject: [PATCH 11/17] Sync'd Reznoks changes --- AutoTrackR2/App.xaml | 471 +++++++++---- AutoTrackR2/AutoTrackR2.csproj | 3 - AutoTrackR2/ConfigPage.xaml | 218 ++++-- AutoTrackR2/ConfigPage.xaml.cs | 50 +- AutoTrackR2/HomePage.xaml.cs | 651 ++++++++++-------- AutoTrackR2/KillHistoryManager.cs | 98 +++ AutoTrackR2/KillTrackR_MainScript.ps1 | 510 -------------- AutoTrackR2/LocalPlayerData.cs | 17 + .../LogEventHandlers/ActorDeathEvent.cs | 59 ++ .../LogEventHandlers/GameVersionEvent.cs | 21 + .../LogEventHandlers/ILogEventHandler.cs | 10 + .../LogEventHandlers/InArenaCommanderEvent.cs | 22 + .../InPersistentUniverseEvent.cs | 22 + .../InstancedInteriorEvent.cs | 77 +++ .../JumpDriveStateChangedEvent.cs | 27 + AutoTrackR2/LogEventHandlers/LoginEvent.cs | 24 + .../RequestJumpFailedEvent.cs | 27 + .../VehicleDestructionEvent.cs | 59 ++ AutoTrackR2/LogHandler.cs | 151 ++++ AutoTrackR2/MainWindow.xaml.cs | 35 +- AutoTrackR2/TrackREventDispatcher.cs | 57 ++ AutoTrackR2/UpdatePage.xaml.cs | 2 +- AutoTrackR2/Util.cs | 31 + AutoTrackR2/WebHandler.cs | 122 ++++ 24 files changed, 1742 insertions(+), 1022 deletions(-) create mode 100644 AutoTrackR2/KillHistoryManager.cs delete mode 100644 AutoTrackR2/KillTrackR_MainScript.ps1 create mode 100644 AutoTrackR2/LocalPlayerData.cs create mode 100644 AutoTrackR2/LogEventHandlers/ActorDeathEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/GameVersionEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/ILogEventHandler.cs create mode 100644 AutoTrackR2/LogEventHandlers/InArenaCommanderEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/InPersistentUniverseEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/InstancedInteriorEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/JumpDriveStateChangedEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/LoginEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/RequestJumpFailedEvent.cs create mode 100644 AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs create mode 100644 AutoTrackR2/LogHandler.cs create mode 100644 AutoTrackR2/TrackREventDispatcher.cs create mode 100644 AutoTrackR2/Util.cs create mode 100644 AutoTrackR2/WebHandler.cs diff --git a/AutoTrackR2/App.xaml b/AutoTrackR2/App.xaml index 3f3362f..b2f9f06 100644 --- a/AutoTrackR2/App.xaml +++ b/AutoTrackR2/App.xaml @@ -15,20 +15,30 @@ <Color x:Key="TextColor">#FFFFFF</Color> <Color x:Key="AltTextColor">#A88F2C</Color> - <SolidColorBrush x:Key="TextBrush" Color="{DynamicResource TextColor}" /> - <SolidColorBrush x:Key="AccentBrush" Color="{DynamicResource AccentColor}" /> - <SolidColorBrush x:Key="BackgroundDarkBrush" Color="{DynamicResource BackgroundDarkColor}" /> - <SolidColorBrush x:Key="BackgroundLightBrush" Color="{DynamicResource BackgroundLightColor}" /> - <SolidColorBrush x:Key="AltTextBrush" Color="{DynamicResource AltTextColor}" /> + <SolidColorBrush x:Key="TextBrush" + Color="{DynamicResource TextColor}"/> + <SolidColorBrush x:Key="AccentBrush" + Color="{DynamicResource AccentColor}"/> + <SolidColorBrush x:Key="BackgroundDarkBrush" + Color="{DynamicResource BackgroundDarkColor}"/> + <SolidColorBrush x:Key="BackgroundLightBrush" + Color="{DynamicResource BackgroundLightColor}"/> + <SolidColorBrush x:Key="AltTextBrush" + Color="{DynamicResource AltTextColor}"/> <!-- Define the Style for Window --> - <Style TargetType="Window" x:Key="CustomWindowStyle"> + <Style TargetType="Window" + x:Key="CustomWindowStyle"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Window"> - <Border BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="10" Background="{DynamicResource BackgroundLightBrush}"> + <Border BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="10" + Background="{DynamicResource BackgroundLightBrush}"> <Grid> - <ContentPresenter HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/> + <ContentPresenter HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"/> </Grid> </Border> </ControlTemplate> @@ -37,62 +47,99 @@ </Style> <!-- Tab Button Style --> - <Style x:Key="TabButtonStyle" TargetType="Button"> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="BorderThickness" Value="2"/> - <Setter Property="Cursor" Value="Hand"/> - <Setter Property="Padding" Value="10"/> - <Setter Property="Margin" Value="5"/> - <Setter Property="FontFamily" Value="{StaticResource Orbitron}"/> + <Style x:Key="TabButtonStyle" + TargetType="Button"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> + <Setter Property="Cursor" + Value="Hand"/> + <Setter Property="Padding" + Value="10"/> + <Setter Property="Margin" + Value="5"/> + <Setter Property="FontFamily" + Value="{StaticResource Orbitron}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="5"> <!-- ContentPresenter will automatically inherit Foreground from Button --> - <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> + <ContentPresenter HorizontalAlignment="Center" + VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> </Setter> - + </Style> <!-- General Button Style --> - <Style x:Key="DisabledButtonStyle" TargetType="Button"> - <Setter Property="Foreground" Value="Gray"/> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> - <Setter Property="BorderBrush" Value="Gray"/> - <Setter Property="BorderThickness" Value="2"/> - <Setter Property="FontWeight" Value="Bold"/> - <Setter Property="Cursor" Value="Hand"/> - <Setter Property="Padding" Value="5"/> + <Style x:Key="DisabledButtonStyle" + TargetType="Button"> + <Setter Property="Foreground" + Value="Gray"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="Gray"/> + <Setter Property="BorderThickness" + Value="2"/> + <Setter Property="FontWeight" + Value="Bold"/> + <Setter Property="Cursor" + Value="Hand"/> + <Setter Property="Padding" + Value="5"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> - <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="5"> + <ContentPresenter HorizontalAlignment="Center" + VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> - <Style x:Key="ButtonStyle" TargetType="Button"> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="BorderThickness" Value="2"/> - <Setter Property="FontWeight" Value="Bold"/> - <Setter Property="Cursor" Value="Hand"/> - <Setter Property="Padding" Value="5"/> + <Style x:Key="ButtonStyle" + TargetType="Button"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> + <Setter Property="FontWeight" + Value="Bold"/> + <Setter Property="Cursor" + Value="Hand"/> + <Setter Property="Padding" + Value="5"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="5"> - <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="5"> + <ContentPresenter HorizontalAlignment="Center" + VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> @@ -100,69 +147,141 @@ </Style> <!-- Title Bar Button Style --> - <Style x:Key="TitleButtonStyle" TargetType="Button"> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="BorderThickness" Value="2"/> - <Setter Property="FontWeight" Value="Bold"/> - <Setter Property="Cursor" Value="Hand"/> + <Style x:Key="TitleButtonStyle" + TargetType="Button"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> + <Setter Property="FontWeight" + Value="Bold"/> + <Setter Property="Cursor" + Value="Hand"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> - <Border Background="{DynamicResource BackgroundDarkBrush}" - BorderBrush="{DynamicResource AccentBrush}" - BorderThickness="2" - CornerRadius="5" - Margin="0,1,4,1"> - <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="5" + Margin="0,1,4,1"> + <ContentPresenter HorizontalAlignment="Center" + VerticalAlignment="Center"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> - + <!-- Custom style for text blocks --> - <Style x:Key="RoundedTextBlock" TargetType="TextBlock"> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> - <Setter Property="FontFamily" Value="{StaticResource Roboto}" /> - <Setter Property="Background" Value="Transparent" /> - <Setter Property="FontSize" Value="14" /> - <Setter Property="Padding" Value="10,0,10,0" /> - <Setter Property="VerticalAlignment" Value="Center" /> - <Setter Property="HorizontalAlignment" Value="Stretch" /> + <Style x:Key="RoundedTextBlock" + TargetType="TextBlock"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="FontFamily" + Value="{StaticResource Roboto}"/> + <Setter Property="Background" + Value="Transparent"/> + <Setter Property="FontSize" + Value="14"/> + <Setter Property="Padding" + Value="10,0,10,0"/> + <Setter Property="VerticalAlignment" + Value="Center"/> + <Setter Property="HorizontalAlignment" + Value="Stretch"/> </Style> <!-- Wrap TextBlock in Border to apply rounded corners --> - <Style x:Key="RoundedTextBlockWithBorder" TargetType="Border"> - <Setter Property="Background" Value="{DynamicResource BackgroundLightBrush}"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="BorderThickness" Value="2"/> - <Setter Property="CornerRadius" Value="5"/> - <Setter Property="Padding" Value="0"/> - <Setter Property="Margin" Value="0,10,0,0"/> + <Style x:Key="RoundedTextBlockWithBorder" + TargetType="Border"> + <Setter Property="Background" + Value="{DynamicResource BackgroundLightBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> + <Setter Property="CornerRadius" + Value="5"/> + <Setter Property="Padding" + Value="0"/> + <Setter Property="Margin" + Value="0,10,0,0"/> </Style> <!-- Custom Style for Rounded TextBox --> - <Style x:Key="RoundedTextBox" TargetType="TextBox"> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="FontFamily" Value="{StaticResource Roboto}"/> - <Setter Property="Height" Value="30"/> - <Setter Property="Padding" Value="5"/> - <Setter Property="VerticalContentAlignment" Value="Center"/> - <Setter Property="HorizontalAlignment" Value="Stretch"/> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}"/> - <Setter Property="BorderThickness" Value="2"/> + <Style x:Key="RoundedTextBox" + TargetType="TextBox"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="FontFamily" + Value="{StaticResource Roboto}"/> + <Setter Property="Height" + Value="30"/> + <Setter Property="Padding" + Value="5"/> + <Setter Property="VerticalContentAlignment" + Value="Center"/> + <Setter Property="HorizontalAlignment" + Value="Stretch"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> <!-- The actual border with rounded corners --> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TextBox"> <Border Background="{DynamicResource BackgroundDarkBrush}" - BorderBrush="{DynamicResource AccentBrush}" - BorderThickness="{TemplateBinding BorderThickness}" - CornerRadius="5"> + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="5"> + <ScrollViewer x:Name="PART_ContentHost"/> + </Border> + </ControlTemplate> + </Setter.Value> + </Setter> + </Style> + + <!-- Custom Style for Rounded PasswordBox --> + <Style x:Key="RoundedPasswordBox" + TargetType="PasswordBox"> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="FontFamily" + Value="{StaticResource Roboto}"/> + <Setter Property="Height" + Value="30"/> + <Setter Property="Padding" + Value="5"/> + <Setter Property="VerticalContentAlignment" + Value="Center"/> + <Setter Property="HorizontalAlignment" + Value="Stretch"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> + <!-- The actual border with rounded corners --> + <Setter Property="Template"> + <Setter.Value> + <ControlTemplate TargetType="PasswordBox"> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + CornerRadius="5"> <ScrollViewer x:Name="PART_ContentHost"/> </Border> </ControlTemplate> @@ -171,39 +290,57 @@ </Style> <!-- Custom Style for Slider --> - <Style x:Key="ThreePositionSlider" TargetType="Slider"> - <Setter Property="Height" Value="40" /> - <Setter Property="Width" Value="160" /> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" /> - <Setter Property="BorderThickness" Value="2" /> + <Style x:Key="ThreePositionSlider" + TargetType="Slider"> + <Setter Property="Height" + Value="40"/> + <Setter Property="Width" + Value="160"/> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Slider"> - <Grid Width="200" Height="30" HorizontalAlignment="Left" Margin="58,-6,0,0"> + <Grid Width="200" + Height="30" + HorizontalAlignment="Left" + Margin="58,-6,0,0"> <!-- Track Background --> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="15" + Margin="0,0,-5,-4"/> <!-- Track --> <Track x:Name="PART_Track"> <Track.Thumb> <Thumb x:Name="PART_Thumb" - Width="22" - Height="22" - Margin="6,4,1,0"> + Width="22" + Height="22" + Margin="6,4,1,0"> <Thumb.Template> <ControlTemplate TargetType="Thumb"> - <Ellipse Fill="{DynamicResource AccentBrush}" /> + <Ellipse Fill="{DynamicResource AccentBrush}"/> </ControlTemplate> </Thumb.Template> </Thumb> </Track.Thumb> <Track.DecreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.DecreaseRepeatButton> <Track.IncreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.IncreaseRepeatButton> </Track> </Grid> @@ -213,39 +350,56 @@ </Style> <!-- Toggle Slider Style --> - <Style x:Key="ToggleSliderStyle" TargetType="Slider"> - <Setter Property="Height" Value="40" /> - <Setter Property="Width" Value="160" /> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> - <Setter Property="BorderBrush" Value="{DynamicResource AccentBrush}" /> - <Setter Property="BorderThickness" Value="2" /> + <Style x:Key="ToggleSliderStyle" + TargetType="Slider"> + <Setter Property="Height" + Value="40"/> + <Setter Property="Width" + Value="160"/> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="{DynamicResource AccentBrush}"/> + <Setter Property="BorderThickness" + Value="2"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Slider"> - <Grid Width="50" Height="30" HorizontalAlignment="Left" > + <Grid Width="50" + Height="30" + HorizontalAlignment="Left"> <!-- Track Background --> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="15" + Margin="0,0,-5,-4"/> <!-- Track --> <Track x:Name="PART_Track"> <Track.Thumb> <Thumb x:Name="PART_Thumb" - Width="22" - Height="22" - Margin="6,4,1,0"> + Width="22" + Height="22" + Margin="6,4,1,0"> <Thumb.Template> <ControlTemplate TargetType="Thumb"> - <Ellipse Fill="{DynamicResource AccentBrush}" /> + <Ellipse Fill="{DynamicResource AccentBrush}"/> </ControlTemplate> </Thumb.Template> </Thumb> </Track.Thumb> <Track.DecreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.DecreaseRepeatButton> <Track.IncreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.IncreaseRepeatButton> </Track> </Grid> @@ -255,39 +409,56 @@ </Style> <!-- False toggle theme --> - <Style x:Key="FalseToggleStyle" TargetType="Slider"> - <Setter Property="Height" Value="40" /> - <Setter Property="Width" Value="160" /> - <Setter Property="Foreground" Value="{DynamicResource TextBrush}" /> - <Setter Property="Background" Value="{DynamicResource BackgroundDarkBrush}" /> - <Setter Property="BorderBrush" Value="Gray" /> - <Setter Property="BorderThickness" Value="2" /> + <Style x:Key="FalseToggleStyle" + TargetType="Slider"> + <Setter Property="Height" + Value="40"/> + <Setter Property="Width" + Value="160"/> + <Setter Property="Foreground" + Value="{DynamicResource TextBrush}"/> + <Setter Property="Background" + Value="{DynamicResource BackgroundDarkBrush}"/> + <Setter Property="BorderBrush" + Value="Gray"/> + <Setter Property="BorderThickness" + Value="2"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Slider"> - <Grid Width="50" Height="30" HorizontalAlignment="Left" > + <Grid Width="50" + Height="30" + HorizontalAlignment="Left"> <!-- Track Background --> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="15" Margin="0,0,-5,-4" /> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="15" + Margin="0,0,-5,-4"/> <!-- Track --> <Track x:Name="PART_Track"> <Track.Thumb> <Thumb x:Name="PART_Thumb" - Width="22" - Height="22" - Margin="6,4,1,0"> + Width="22" + Height="22" + Margin="6,4,1,0"> <Thumb.Template> <ControlTemplate TargetType="Thumb"> - <Ellipse Fill="Gray" /> + <Ellipse Fill="Gray"/> </ControlTemplate> </Thumb.Template> </Thumb> </Track.Thumb> <Track.DecreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.DecreaseRepeatButton> <Track.IncreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="Transparent" IsHitTestVisible="False"/> + <RepeatButton Background="Transparent" + BorderBrush="Transparent" + IsHitTestVisible="False"/> </Track.IncreaseRepeatButton> </Track> </Grid> @@ -298,15 +469,21 @@ <!-- Modern Rounded ScrollBar Style --> <Style TargetType="ScrollBar"> - <Setter Property="Width" Value="6" /> + <Setter Property="Width" + Value="6"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ScrollBar"> <Grid> - <Track Name="PART_Track" IsDirectionReversed="true" Width="6" Margin="0,0,0,0"> + <Track Name="PART_Track" + IsDirectionReversed="true" + Width="6" + Margin="0,0,0,0"> <!-- Decrease Repeat Button --> <Track.DecreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="0"> + <RepeatButton Background="Transparent" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="0"> <RepeatButton.Template> <ControlTemplate TargetType="RepeatButton"> <Grid x:Name="RepeatButtonGrid"> @@ -314,12 +491,18 @@ <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal"> <Storyboard> - <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="1" Duration="0:0:0"/> + <DoubleAnimation Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="RepeatButtonGrid" + To="1" + Duration="0:0:0"/> </Storyboard> </VisualState> <VisualState Name="MouseOver"> <Storyboard> - <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="0" Duration="0:0:0.1"/> + <DoubleAnimation Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="RepeatButtonGrid" + To="0" + Duration="0:0:0.1"/> </Storyboard> </VisualState> </VisualStateGroup> @@ -338,10 +521,10 @@ <ControlTemplate TargetType="Thumb"> <Grid> <Border - Background="{DynamicResource AccentBrush}" - BorderBrush="{DynamicResource AccentBrush}" - BorderThickness="0" - CornerRadius="3" /> + Background="{DynamicResource AccentBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="0" + CornerRadius="3"/> </Grid> </ControlTemplate> </Thumb.Template> @@ -350,7 +533,9 @@ <!-- Increase Repeat Button --> <Track.IncreaseRepeatButton> - <RepeatButton Background="Transparent" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="0"> + <RepeatButton Background="Transparent" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="0"> <RepeatButton.Template> <ControlTemplate TargetType="RepeatButton"> <Grid x:Name="RepeatButtonGrid"> @@ -358,12 +543,18 @@ <VisualStateGroup Name="CommonStates"> <VisualState Name="Normal"> <Storyboard> - <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="1" Duration="0:0:0"/> + <DoubleAnimation Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="RepeatButtonGrid" + To="1" + Duration="0:0:0"/> </Storyboard> </VisualState> <VisualState Name="MouseOver"> <Storyboard> - <DoubleAnimation Storyboard.TargetProperty="Opacity" Storyboard.TargetName="RepeatButtonGrid" To="0" Duration="0:0:0.1"/> + <DoubleAnimation Storyboard.TargetProperty="Opacity" + Storyboard.TargetName="RepeatButtonGrid" + To="0" + Duration="0:0:0.1"/> </Storyboard> </VisualState> </VisualStateGroup> diff --git a/AutoTrackR2/AutoTrackR2.csproj b/AutoTrackR2/AutoTrackR2.csproj index 0ba95b7..875c53d 100644 --- a/AutoTrackR2/AutoTrackR2.csproj +++ b/AutoTrackR2/AutoTrackR2.csproj @@ -109,9 +109,6 @@ </ItemGroup> <ItemGroup> - <None Update="KillTrackR_MainScript.ps1"> - <CopyToOutputDirectory>Always</CopyToOutputDirectory> - </None> <None Update="update.ps1"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> diff --git a/AutoTrackR2/ConfigPage.xaml b/AutoTrackR2/ConfigPage.xaml index 3b2016f..5e425e9 100644 --- a/AutoTrackR2/ConfigPage.xaml +++ b/AutoTrackR2/ConfigPage.xaml @@ -1,96 +1,234 @@ <UserControl x:Class="AutoTrackR2.ConfigPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - Height="396" Width="626"> + Height="410" + Width="626"> <Grid Background="{DynamicResource BackgroundLightBrush}"> <!-- Main Layout Grid --> <Grid Margin="0,0,5,7"> <Grid.RowDefinitions> <!-- One row for the content, the other for buttons --> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <!-- Left column for the main content area --> - <ColumnDefinition Width="*" /> + <ColumnDefinition Width="*"/> <!-- Right column for the buttons --> - <ColumnDefinition Width="Auto" /> + <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <!-- Section for Config Fields --> - <StackPanel Grid.Column="0" VerticalAlignment="Center" Height="389"> + <StackPanel Grid.Column="0" + VerticalAlignment="Center" + Height="389"> <!-- Log File --> - <StackPanel Margin="0,10,0,15" Orientation="Horizontal"> - <TextBlock Text="Log File:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5" FontFamily="{StaticResource Roboto}"/> - <StackPanel Orientation="Horizontal" Margin="30,0,0,0"> - <TextBox Name="LogFilePath" Width="340" Height="30" Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="LogFileBrowseButton_Click"/> + <StackPanel Margin="0,10,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,0,3,5"/> + <TextBlock Text="Log File:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5" + FontFamily="{StaticResource Roboto}"/> + <StackPanel Orientation="Horizontal" + Margin="30,0,0,0"> + <TextBox Name="LogFilePath" + Width="330" + Height="30" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="LogFileBrowseButton_Click"/> </StackPanel> </StackPanel> <!-- API URL --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="API URL:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> - <StackPanel Orientation="Horizontal" Margin="30,0,0,0"> - <TextBox Name="ApiUrl" Width="340" Height="30" Style="{StaticResource RoundedTextBox}"/> - <Button Content="Test" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="TestApiButton_Click"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API URL:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <StackPanel Orientation="Horizontal" + Margin="30,0,0,0"> + <TextBox Name="ApiUrl" + Width="330" + Height="30" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Test" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="TestApiButton_Click"/> </StackPanel> </StackPanel> <!-- API Key --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="API Key:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> - <TextBox Name="ApiKey" Width="340" Height="30" Margin="33,0,0,0" Style="{StaticResource RoundedTextBox}"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API Key:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <PasswordBox Name="ApiKey" + Width="330" + Height="30" + Margin="33,0,0,0" + Style="{StaticResource RoundedPasswordBox}"/> </StackPanel> <!-- Video Path --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="Video Path:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,5,0,5"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="The directory where your clipping software saves kills. Check the README." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="Video Path:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> <StackPanel Orientation="Horizontal"> - <TextBox Name="VideoPath" Width="340" Height="30" Margin="10,0,0,0" Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" Width="75" Height="30" FontFamily="{StaticResource Orbitron}" Margin="5,0" Style="{StaticResource ButtonStyle}" Click="VideoPathBrowseButton_Click"/> + <TextBox Name="VideoPath" + Width="330" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="VideoPathBrowseButton_Click"/> </StackPanel> </StackPanel> <!-- Visor Wipe Toggle Slider --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="Visor Wipe:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> - <Slider Name="VisorWipeSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="27,-4,0,0" ValueChanged="VisorWipeSlider_ValueChanged"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Visor Wipe:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="VisorWipeSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="27,-4,0,0" + ValueChanged="VisorWipeSlider_ValueChanged"/> </StackPanel> <!-- Video Record Toggle Slider --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="Video Record:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> - <Slider Name="VideoRecordSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="10,-4,0,0" ValueChanged="VideoRecordSlider_ValueChanged"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Automatically clip your last kill. Check the README for more info." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Video Record:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="VideoRecordSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="10,-4,0,0" + ValueChanged="VideoRecordSlider_ValueChanged"/> </StackPanel> <!-- Offline Mode Toggle Slider --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="Offline Mode:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> - <Slider Name="OfflineModeSlider" Minimum="0" Maximum="1" TickFrequency="1" IsSnapToTickEnabled="True" Value="0" Style="{StaticResource ToggleSliderStyle}" Margin="12,-4,0,0" ValueChanged="OfflineModeSlider_ValueChanged"/> + <StackPanel Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Offline Mode:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="OfflineModeSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="12,-4,0,0" + ValueChanged="OfflineModeSlider_ValueChanged"/> </StackPanel> <!-- 3-Position Toggle Slider --> - <StackPanel Margin="0,0,0,15" Orientation="Horizontal"> - <TextBlock Text="Theme:" Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,5"/> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="Theme:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> <Slider x:Name="ThemeSlider" - Minimum="0" + Minimum="0" Maximum="21" Value="0" TickFrequency="1" IsSnapToTickEnabled="True" - ValueChanged="ThemeSlider_ValueChanged" Width="447" - Style="{StaticResource ThreePositionSlider}" - /> + ValueChanged="ThemeSlider_ValueChanged" + Width="447" + Style="{StaticResource ThreePositionSlider}"/> </StackPanel> </StackPanel> <!-- Save Button --> - <StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0"> - <Button x:Name="SaveButton" Content="Save" Width="100" Height="40" Style="{StaticResource ButtonStyle}" FontFamily="{StaticResource Orbitron}" Click="SaveButton_Click"/> + <StackPanel Grid.Column="2" + HorizontalAlignment="Right" + VerticalAlignment="Bottom" + Margin="0"> + <Button x:Name="SaveButton" + Content="Save" + Width="100" + Height="40" + Style="{StaticResource ButtonStyle}" + FontFamily="{StaticResource Orbitron}" + Click="SaveButton_Click"/> </StackPanel> </Grid> </Grid> diff --git a/AutoTrackR2/ConfigPage.xaml.cs b/AutoTrackR2/ConfigPage.xaml.cs index 5e7f366..bf83314 100644 --- a/AutoTrackR2/ConfigPage.xaml.cs +++ b/AutoTrackR2/ConfigPage.xaml.cs @@ -24,10 +24,10 @@ namespace AutoTrackR2 { InitializeComponent(); this.mainWindow = mainWindow; - + LogFilePath.Text = ConfigManager.LogFile; ApiUrl.Text = ConfigManager.ApiUrl; - ApiKey.Text = ConfigManager.ApiKey; + ApiKey.Password = ConfigManager.ApiKey; VideoPath.Text = ConfigManager.VideoPath; VisorWipeSlider.Value = ConfigManager.VisorWipe; VideoRecordSlider.Value = ConfigManager.VideoRecord; @@ -70,7 +70,7 @@ namespace AutoTrackR2 // Set the textboxes with the loaded values LogFilePath.Text = logFile; ApiUrl.Text = apiUrl; - ApiKey.Text = apiKey; + ApiKey.Password = apiKey; VideoPath.Text = videoPath; // Set the sliders with the loaded values @@ -123,7 +123,7 @@ namespace AutoTrackR2 // Apply the selected theme ApplyTheme(themeIndex); - + mainWindow.UpdateTabVisuals(); } @@ -413,8 +413,7 @@ namespace AutoTrackR2 // Build the dynamic file path for the current user string filePath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "AutoTrackR2", + ConfigManager.AHKScriptFolder, "visorwipe.ahk" ); @@ -508,36 +507,19 @@ namespace AutoTrackR2 private void SaveButton_Click(object sender, RoutedEventArgs e) { - // Get the directory for the user's local application data - string appDataDirectory = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "AutoTrackR2" - ); - - // Ensure the directory exists - if (!Directory.Exists(appDataDirectory)) - { - Directory.CreateDirectory(appDataDirectory); - } - - // Combine the app data directory with the config file name - string configFilePath = Path.Combine(appDataDirectory, "config.ini"); - - using (StreamWriter writer = new StreamWriter(configFilePath)) - { - writer.WriteLine($"LogFile={LogFilePath.Text}"); - writer.WriteLine($"ApiUrl={ApiUrl.Text}"); - writer.WriteLine($"ApiKey={ApiKey.Text}"); - writer.WriteLine($"VideoPath={VideoPath.Text}"); - writer.WriteLine($"VisorWipe={(int)VisorWipeSlider.Value}"); - writer.WriteLine($"VideoRecord={(int)VideoRecordSlider.Value}"); - writer.WriteLine($"OfflineMode={(int)OfflineModeSlider.Value}"); - writer.WriteLine($"Theme={(int)ThemeSlider.Value}"); // Assumes you are saving the theme slider value (0, 1, or 2) - } + ConfigManager.ApiKey = ApiKey.Password; + ConfigManager.ApiUrl = ApiUrl.Text; + ConfigManager.LogFile = LogFilePath.Text; + ConfigManager.VideoPath = VideoPath.Text; + ConfigManager.VisorWipe = (int)VisorWipeSlider.Value; + ConfigManager.VideoRecord = (int)VideoRecordSlider.Value; + ConfigManager.OfflineMode = (int)OfflineModeSlider.Value; + ConfigManager.Theme = (int)ThemeSlider.Value; + // Save the current config values + ConfigManager.SaveConfig(); // Start the flashing effect FlashSaveButton(); - ConfigManager.LoadConfig(); } private void FlashSaveButton() @@ -591,7 +573,7 @@ namespace AutoTrackR2 { string apiUrl = ApiUrl.Text; string modifiedUrl = Regex.Replace(apiUrl, @"(https?://[^/]+)/?.*", "$1/test"); - string apiKey = ApiKey.Text; + string apiKey = ApiKey.Password; Debug.WriteLine($"Sending to {modifiedUrl}"); try diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index f65af75..e3eb36a 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -3,309 +3,360 @@ 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.IO; +using System.Text; using System.Windows.Media.Imaging; +using AutoTrackR2.LogEventHandlers; -namespace AutoTrackR2 +namespace AutoTrackR2; + +public partial class HomePage : UserControl { - public partial class HomePage : UserControl + private Process runningProcess; // Field to store the running process + private LogHandler? _logHandler; + private KillHistoryManager _killHistoryManager; + private bool _UIEventsRegistered = false; + + public HomePage() { - public HomePage() + InitializeComponent(); + + _killHistoryManager = new KillHistoryManager(ConfigManager.KillHistoryFile); + + // Set the TextBlock text + KillTallyTitle.Text = $"Kill Tally - {_killHistoryManager.GetKillsInCurrentMonth().Count}"; + AddKillHistoryKillsToUI(); + + } + // + 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) + 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 AddKillHistoryKillsToUI() + { + var kills = _killHistoryManager.GetKills(); + foreach (var kill in kills) { - await Task.Run(() => + Dispatcher.Invoke(() => { AddKillToScreen(kill); }); + } + } + + private void RegisterUIEventHandlers() + { + if (_UIEventsRegistered) + return; + + // Username + TrackREventDispatcher.PlayerLoginEvent += (username) => { + Dispatcher.Invoke(() => { - try + PilotNameTextBox.Text = username; + AdjustFontSize(PilotNameTextBox); + LocalPlayerData.Username = username; + }); + }; + + // Ship + TrackREventDispatcher.JumpDriveStateChangedEvent += (shipName) => { + Dispatcher.Invoke(() => { - 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 - }; + PlayerShipTextBox.Text = shipName; + AdjustFontSize(PlayerShipTextBox); + LocalPlayerData.PlayerShip = shipName; + }); + }; + + // 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 (actorDeathData) => { + if (actorDeathData.VictimPilot != LocalPlayerData.Username) + { + var playerData = await WebHandler.GetPlayerData(actorDeathData.VictimPilot); - 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) + if (playerData != null) { + var killData = new KillData + { + EnemyPilot = actorDeathData.VictimPilot, + EnemyShip = actorDeathData.VictimShip, + OrgAffiliation = playerData?.OrgName, + Weapon = actorDeathData.Weapon, + Ship = LocalPlayerData.PlayerShip ?? "Unknown", + Method = actorDeathData.DamageType, + RecordNumber = playerData?.UEERecord, + GameVersion = LocalPlayerData.GameVersion ?? "Unknown", + TrackRver = UpdatePage.currentVersion.Replace("v", "") ?? "Unknown", + Enlisted = playerData?.JoinDate, + KillTime = DateTime.UtcNow.ToString("dd MMM yyyy HH:mm"), + PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg" + }; + + switch (LocalPlayerData.CurrentGameMode) + { + case GameMode.PersistentUniverse: + killData.Mode = "pu"; + break; + case GameMode.ArenaCommander: + killData.Mode = "ac"; + break; + } + + // Add kill to UI Dispatcher.Invoke(() => { - MessageBox.Show($"Error running script: {ex.Message}"); + AddKillToScreen(killData); }); + + // Only submit kill data if not in offline mode + if (ConfigManager.OfflineMode == 0) + { + await WebHandler.SubmitKill(killData); + } + + _killHistoryManager.AddKill(killData); + VisorWipe(); + VideoRecord(); } - }); - } - - public void StopButton_Click(object sender, RoutedEventArgs e) - { - if (runningProcess != null && !runningProcess.HasExited) - { - // Kill the running process - runningProcess.Kill(); - runningProcess = null; // Clear the reference to the process } + }; + + // Vehicle Destruction + TrackREventDispatcher.VehicleDestructionEvent += (data) => { + LocalPlayerData.LastSeenVehicleLocation = data.VehicleZone; + }; + + _UIEventsRegistered = true; + } - // 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 AddKillToScreen(KillData killData) + { + // Fetch the dynamic resource for AltTextColor + var altTextColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AltTextColor"]); + var accentColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AccentColor"]); - private void AdjustFontSize(TextBlock textBlock) + // 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($"{killData.EnemyPilot}\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($"{killData.EnemyShip}\n")); + + killTextBlock.Inlines.Add(new Run("Victim Org: ") + { + Foreground = altTextColorBrush, + FontFamily = orbitronFontFamily, + }); + killTextBlock.Inlines.Add(new Run($"{killData.OrgAffiliation}\n")); + + killTextBlock.Inlines.Add(new Run("Join Date: ") + { + Foreground = altTextColorBrush, + FontFamily = orbitronFontFamily, + }); + killTextBlock.Inlines.Add(new Run($"{killData.Enlisted}\n")); + + killTextBlock.Inlines.Add(new Run("UEE Record: ") + { + Foreground = altTextColorBrush, + FontFamily = orbitronFontFamily, + }); + + killTextBlock.Inlines.Add(new Run($"{killData.RecordNumber}\n")); + + killTextBlock.Inlines.Add(new Run("Kill Time: ") + { + Foreground = altTextColorBrush, + FontFamily = orbitronFontFamily, + }); + killTextBlock.Inlines.Add(new Run($"{killData.KillTime}")); + + // 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); + + if (killData.PFP == "") + { + killData.PFP = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg"; + } + + // Create the Image for the profile + var profileImage = new Image + { + Source = new BitmapImage(new Uri(killData.PFP)), // 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 +365,44 @@ 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; + } + + public static void RunAHKScript(string path) + { + string scriptPath = Path.Combine(ConfigManager.AHKScriptFolder, path); + + if (!File.Exists(scriptPath)) + { + return; + } + + // Run the script using powershell + using var ahkProcess = new Process(); + + // Runs the script via Explorer, ensuring it uses whatever the + // default binary for AHK is. Skips having to find a specific path to AHK + ahkProcess.StartInfo.FileName = "explorer"; + ahkProcess.StartInfo.Arguments = "\"" + scriptPath + "\""; + ahkProcess.Start(); + } - // Apply the adjusted font size - textBlock.FontSize = fontSize; + private void VisorWipe() + { + if (ConfigManager.VisorWipe == 1) + { + RunAHKScript(ConfigManager.VisorWipeScript); + } + } + + private void VideoRecord() + { + if (ConfigManager.VideoRecord == 1) + { + RunAHKScript(ConfigManager.VideoRecordScript); } } } diff --git a/AutoTrackR2/KillHistoryManager.cs b/AutoTrackR2/KillHistoryManager.cs new file mode 100644 index 0000000..5c87a17 --- /dev/null +++ b/AutoTrackR2/KillHistoryManager.cs @@ -0,0 +1,98 @@ +using System.Globalization; +using System.IO; +using System.Text; + +namespace AutoTrackR2; + +public class KillHistoryManager +{ + private string _killHistoryPath; + private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP\n"; + + public KillHistoryManager(string logPath) + { + _killHistoryPath = logPath; + + if (!File.Exists(_killHistoryPath)) + { + File.WriteAllText(_killHistoryPath, _headers); + } + } + + public void AddKill(KillData killData) + { + // Ensure the CSV file exists + // This should only happen if the file was deleted or corrupted + if (!File.Exists(_killHistoryPath)) + { + File.WriteAllText(_killHistoryPath, _headers); + } + + // Remove comma from Enlisted + killData.Enlisted = killData.Enlisted?.Replace(",", string.Empty); + + // Append the new kill data to the CSV file + var csv = new StringBuilder(); + csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\""); + + // Check file can be written to + try + { + using var fileStream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.None); + using var writer = new StreamWriter(fileStream); + writer.Write(csv.ToString()); + } + catch (IOException ex) + { + // Handle the exception (e.g., log it) + Console.WriteLine($"Error writing to file: {ex.Message}"); + } + } + + public List<KillData> GetKills() + { + var kills = new List<KillData>(); + + using var reader = new StreamReader(_killHistoryPath); + reader.ReadLine(); // Skip headers + + while (reader.Peek() >= 0) + { + var line = reader.ReadLine(); + + // Remove extra quotes from CSV data + // Todo: These quotes are for handling commas in the data, but not sure if they're necessary + line = line?.Replace("\"", string.Empty); + + var data = line?.Split(','); + + kills.Add(new KillData + { + KillTime = data?[0], + EnemyPilot = data?[1], + EnemyShip = data?[2], + Enlisted = data?[3], + RecordNumber = data?[4], + OrgAffiliation = data?[5], + Player = data?[6], + Weapon = data?[7], + Ship = data?[8], + Method = data?[9], + Mode = data?[10], + GameVersion = data?[11], + TrackRver = data?[12], + Logged = data?[13], + PFP = data?[14] + }); + } + + return kills; + } + + public List<KillData> GetKillsInCurrentMonth() + { + string currentMonth = DateTime.Now.ToString("MMM", CultureInfo.InvariantCulture); + var kills = GetKills(); + return kills.Where(kill => kill.KillTime?.Contains(currentMonth) == true).ToList(); + } +} \ No newline at end of file diff --git a/AutoTrackR2/KillTrackR_MainScript.ps1 b/AutoTrackR2/KillTrackR_MainScript.ps1 deleted file mode 100644 index 2fb1d52..0000000 --- a/AutoTrackR2/KillTrackR_MainScript.ps1 +++ /dev/null @@ -1,510 +0,0 @@ -$TrackRver = "2.07" - -# Path to the config file -$appName = "AutoTrackR2" -$scriptFolder = Join-Path -Path $env:LOCALAPPDATA -ChildPath $appName -$configFile = Join-Path -Path $scriptFolder -ChildPath "config.ini" - -# Read the config file into a hashtable -if (Test-Path $configFile) { - Write-Output "PlayerName=Config.ini found." - $configContent = Get-Content $configFile | Where-Object { $_ -notmatch '^#|^\s*$' } - - # Escape backslashes by doubling them - $configContent = $configContent -replace '\\', '\\\\' - - # Convert to key-value pairs - $config = $configContent -replace '^([^=]+)=(.+)$', '$1=$2' | ConvertFrom-StringData -} else { - Write-Output "Config.ini not found." - exit -} - -$parentApp = (Get-Process -Name AutoTrackR2).ID - -# Access config values -$logFilePath = $config.Logfile -$apiUrl = $config.ApiUrl -$apiKey = $config.ApiKey -$videoPath = $config.VideoPath -$visorWipe = $config.VisorWipe -$videoRecord = $config.VideoRecord -$offlineMode = $config.OfflineMode - -if ($offlineMode -eq 1){ - $offlineMode = $true -} else { - $offlineMode = $false -} -Write-Output "PlayerName=OfflineMode: $offlineMode" - -if ($videoRecord -eq 1){ - $videoRecord = $true -} else { - $videoRecord = $false -} -Write-Output "PlayerName=VideoRecord: $videoRecord" - -if ($visorWipe -eq 1){ - $visorWipe = $true -} else { - $visorWipe = $false -} -Write-Output "PlayerName=VisorWipe: $visorWipe" - -If (Test-Path $logFilePath) { - Write-Output "PlayerName=Logfile found" -} else { - Write-Output "Logfile not found." -} - -If ($null -ne $apiUrl){ - if ($apiUrl -notlike "*/register-kill") { - $apiUrl = $apiUrl.TrimEnd("/") + "/register-kill" - } - Write-output "PlayerName=$apiURL" -} - -# Ship Manufacturers -$prefixes = @( - "ORIG", - "CRUS", - "RSI", - "AEGS", - "VNCL", - "DRAK", - "ANVL", - "BANU", - "MISC", - "CNOU", - "XIAN", - "GAMA", - "TMBL", - "ESPR", - "KRIG", - "GRIN", - "XNAA", - "MRAI" -) - -# Define the regex pattern to extract information -$killPattern = "<Actor Death> CActor::Kill: '(?<EnemyPilot>[^']+)' \[\d+\] in zone '(?<EnemyShip>[^']+)' killed by '(?<Player>[^']+)' \[[^']+\] using '(?<Weapon>[^']+)' \[Class (?<Class>[^\]]+)\] with damage type '(?<DamageType>[^']+)'" -$puPattern = '<\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\]' -$loadoutPattern = '<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in' -$acPattern = "Requesting Mode Change" # "ArenaCommanderFeature" -$shipManPattern = "^(" + ($prefixes -join "|") + ")" -# $loginPattern = "\[Notice\] <AccountLoginCharacterStatus_Character> Character: createdAt [A-Za-z0-9]+ - updatedAt [A-Za-z0-9]+ - geid [A-Za-z0-9]+ - accountId [A-Za-z0-9]+ - name (?<Player>[A-Za-z0-9_-]+) - state STATE_CURRENT" # KEEP THIS INCASE LEGACY LOGIN IS REMOVED -$loginPattern = "\[Notice\] <Legacy login response> \[CIG-net\] User Login Success - Handle\[(?<Player>[A-Za-z0-9_-]+)\]" -$cleanupPattern = '^(.+?)_\d+$' -$versionPattern = "--system-trace-env-id='pub-sc-alpha-(?<gameversion>\d{3,4}-\d{7})'" -$vehiclePattern = "<(?<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>[^']+)'" - -# Lookup Patterns -$joinDatePattern = '<span class="label">Enlisted</span>\s*<strong class="value">([^<]+)</strong>' -$ueePattern = '<p class="entry citizen-record">\s*<span class="label">UEE Citizen Record<\/span>\s*<strong class="value">#?(n\/a|\d+)<\/strong>\s*<\/p>' - -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$process = Get-Process | Where-Object {$_.Name -like "AutoTrackR2"} -$global:killTally = 0 - -# Load historic kills from csv -if (Test-Path "$scriptFolder\Kill-Log.csv") { - $historicKills = Import-CSV "$scriptFolder\Kill-log.csv" - $currentDate = Get-Date - $dateFormat = "dd MMM yyyy HH:mm UTC" - foreach ($kill in $historicKills) { - $killDate = [datetime]::ParseExact($kill.KillTime.Trim(), $dateFormat, [System.Globalization.CultureInfo]::InvariantCulture) - If ($killdate.year -eq $currentDate.Year -and $killdate.month -eq $currentDate.Month) { - $global:killTally++ - } - Try { - Write-Output "NewKill=throwaway,$($kill.EnemyPilot),$($kill.EnemyShip),$($kill.OrgAffiliation),$($kill.Enlisted),$($kill.RecordNumber),$($kill.KillTime), $($kill.PFP)" - } Catch { - Write-Output "Error Loading Kill: $($kill.EnemyPilot)" - } - } -} -Write-Output "KillTally=$global:killTally" - -# Match and extract username from gamelog -Do { - # Load gamelog into memory - $authLog = Get-Content -Path $logFilePath - - # Initialize variable to store username - $global:userName = $null - $global:loadout = "Player" - - # Loop through each line in the log to find the matching line - foreach ($line in $authLog) { - if ($line -match $loginPattern) { - $global:userName = $matches['Player'] - Write-Output "PlayerName=$global:userName" - } - # Get Loadout - if ($line -match $loadoutPattern) { - If ($matches['ShipName'] -match $cleanupPattern){ - if ($null -ne $matches[1]){ - $global:loadOut = $matches[1] - } - } - Write-Output "PlayerShip=$global:loadOut" - } - - If ($line -match $versionPattern){ - $global:GameVersion = $matches['gameversion'] - } - if ($line -match $acPattern){ - $global:GameMode = "AC" - } - if ($line -match $puPattern){ - $global:GameMode = "PU" - } - Write-Output "GameMode=$global:GameMode" - - } - # If no match found, print "Logged In: False" - if (-not $global:userName) { - Write-Output "PlayerName=No Player Found..." - Start-Sleep -Seconds 30 - } - - # Clear the log from memory - $authLog = $null -} until ($null -ne $global:userName) - -# Function to process new log entries and write to the host -function Read-LogEntry { - param ( - [string]$line - ) - - # Look for vehicle events - if ($line -match $vehiclePattern) { - # Access the named capture groups from the regex match - $global:vehicle_id = $matches['vehicle'] - $global:location = $matches['vehicle_zone'] - - } - - # Apply the regex pattern to the line - if ($line -match $killPattern) { - # Access the named capture groups from the regex match - $enemyPilot = $matches['EnemyPilot'] - $enemyShip = $matches['EnemyShip'] - $player = $matches['Player'] - $weapon = $matches['Weapon'] - $damageType = $matches['DamageType'] - $ship = $global:loadOut - - If ($enemyShip -ne "vehicle_id"){ - - $global:got_location = $location - } - else - { - $global:got_location = "NONE" - } - - Try { - $page1 = Invoke-WebRequest -uri "https://robertsspaceindustries.com/citizens/$enemyPilot" - } Catch { - $page1 = $null - } - - If ($null -ne $page1){ - # Check if the Autotrackr2 process is running - if ($null -eq (Get-Process -ID $parentApp -ErrorAction SilentlyContinue)) { - Stop-Process -Id $PID -Force - } - If ($enemyShip -ne "Player"){ - If ($enemyShip -eq $global:lastKill){ - $enemyShip = "Passenger" - } Else { - $global:lastKill = $enemyShip - } - } - - If ($player -eq $global:userName -and $enemyPilot -ne $global:userName){ - If ($enemyShip -match $cleanupPattern){ - $enemyShip = $matches[1] - } - If ($weapon -match $cleanupPattern){ - $weapon = $matches[1] - } - If ($weapon -eq "KLWE_MassDriver_S10"){ - $global:loadOut = "AEGS_Idris" - $ship = "AEGS_Idris" - } - if ($damageType -eq "Bullet" -or $weapon -like "apar_special_ballistic*") { - $ship = "Player" - $enemyShip = "Player" - $global:got_location = "NONE" - } - If ($ship -match $cleanupPattern){ - $ship = $matches[1] - } - if ($ship -notmatch $shipManPattern){ - $ship = "Player" - $global:got_location = "NONE" - } - If ($enemyShip -notmatch $shipManPattern -and $enemyShip -notlike "Passenger" ) { - $enemyShip = "Player" - $global:got_location = "NONE" - } - - # Repeatedly remove all suffixes - while ($enemyShip -match '_(PU|AI|CIV|MIL|PIR)$') { - $enemyShip = $enemyShip -replace '_(PU|AI|CIV|MIL|PIR)$', '' - } - # Repeatedly remove all suffixes - while ($ship -match '_(PU|AI|CIV|MIL|PIR)$') { - $ship = $ship -replace '_(PU|AI|CIV|MIL|PIR)$', '' - } - while ($enemyShip -match '-00(1|2|3|4|5|6|7|8|9|0)$') { - $enemyShip = $enemyShip -replace '-00(1|2|3|4|5|6|7|8|9|0)$', '' - }while ($ship -match '-00(1|2|3|4|5|6|7|8|9|0)$') { - $ship = $ship -replace '-00(1|2|3|4|5|6|7|8|9|0)$', '' - } - - $KillTime = (Get-Date).ToUniversalTime().ToString("dd MMM yyyy HH:mm 'UTC'", [System.Globalization.CultureInfo]::InvariantCulture) - - # Get Enlisted Date - if ($($page1.content) -match $joinDatePattern) { - $joinDate = $matches[1] - $joinDate2 = $joinDate -replace ',', '' - } else { - $joinDate2 = "-" - } - - # Check if there are any matches - If ($null -eq $page1.links[0].innerHTML) { - $enemyOrgs = $page1.links[4].innerHTML - } Else { - $enemyOrgs = $page1.links[3].innerHTML - } - - if ($null -eq $enemyOrgs) { - $enemyOrgs = "-" - } - - # Get UEE Number - if ($($page1.content) -match $ueePattern) { - # The matched UEE Citizen Record number is in $matches[1] - $citizenRecord = $matches[1] - } else { - $citizenRecord = "n/a" - } - If ($citizenRecord -eq "n/a") { - $citizenRecordAPI = "-1" - $citizenRecord = "-" - } Else { - $citizenRecordAPI = $citizenRecord - } - - # Get PFP - if ($page1.images[0].src -like "/media/*") { - $victimPFP = "https://robertsspaceindustries.com$($page1.images[0].src)" - } Else { - $victimPFP = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg" - } - - $global:killTally++ - Write-Output "KillTally=$global:killTally" - Write-Output "NewKill=throwaway,$enemyPilot,$enemyShip,$enemyOrgs,$joinDate2,$citizenRecord,$killTime,$victimPFP" - - $global:GameMode = $global:GameMode.ToLower() - # Send to API - # Define the data to send - If ($null -ne $apiUrl -and $offlineMode -eq $false){ - $data = @{ - victim_ship = $enemyShip - victim = $enemyPilot - enlisted = $joinDate - rsi = $citizenRecordAPI - weapon = $weapon - method = $damageType - loadout_ship = $ship - game_version = $global:GameVersion - gamemode = $global:GameMode - trackr_version = $TrackRver - location = $got_location - } - - # Headers which may or may not be necessary - $headers = @{ - "Authorization" = "Bearer $apiKey" - "Content-Type" = "application/json" - "User-Agent" = "AutoTrackR2" - } - - try { - # Send the POST request with JSON data - $null = Invoke-RestMethod -Uri $apiURL -Method Post -Body ($data | ConvertTo-Json -Depth 5) -Headers $headers - $logMode = "API" - $global:got_location = "NONE" - } catch { - # Catch and display errors - $apiError = $_ - # Add to output file - $logMode = "Err-Local" - } - } Else { - $logMode = "Local" - } - - # Define the output CSV path - $csvPath = "$scriptFolder\Kill-log.csv" - - # Create an object to hold the data - $killData = [PSCustomObject]@{ - KillTime = $killTime - EnemyPilot = $enemyPilot - EnemyShip = $enemyShip - Enlisted = $joinDate2 - RecordNumber = $citizenRecord - OrgAffiliation = $enemyOrgs - Player = $player - Weapon = $weapon - Ship = $ship - Method = $damageType - Mode = $global:GameMode - GameVersion = $global:GameVersion - TrackRver = $TrackRver - Logged = $logMode - PFP = $victimPFP - } - - # Remove commas from all properties - foreach ($property in $killData.PSObject.Properties) { - if ($property.Value -is [string]) { - $property.Value = $property.Value -replace ',', '' - } - } - - # Export to CSV - if (-Not (Test-Path $csvPath)) { - # If file doesn't exist, create it with headers - $killData | Export-Csv -Path $csvPath -NoTypeInformation - } else { - # Append data to the existing file without adding headers - $killData | ConvertTo-Csv -NoTypeInformation | Select-Object -Skip 1 | Out-File -Append -Encoding utf8 -FilePath $csvPath - } - - $sleeptimer = 10 - - # VisorWipe - If ($visorwipe -eq $true -and $enemyShip -ne "Passenger" -and $damageType -notlike "*Bullet*"){ - # send keybind for visorwipe - start-sleep 1 - $sleeptimer = $sleeptimer -1 - &"$scriptFolder\visorwipe.ahk" - } - - # Record video - if ($videoRecord -eq $true -and $enemyShip -ne "Passenger"){ - # send keybind for windows game bar recording - Start-Sleep 2 - $sleeptimer = $sleeptimer -9 - &"$scriptFolder\videorecord.ahk" - Start-Sleep 7 - - $latestFile = Get-ChildItem -Path $videoPath | Where-Object { -not $_.PSIsContainer } | Sort-Object CreationTime -Descending | Select-Object -First 1 - # Check if the latest file is no more than 30 seconds old - if ($latestFile) { - $fileAgeInSeconds = (New-TimeSpan -Start $latestFile.CreationTime -End (Get-Date)).TotalSeconds - if ($fileAgeInSeconds -le 30) { - # Generate a timestamp in ddMMMyyyy-HH:mm format - $timestamp = (Get-Date).ToString("ddMMMyyyy-HHmm") - - # Extract the file extension to preserve it - $fileExtension = $latestFile.Extension - - # Rename the file, preserving the original file extension - Rename-Item -Path $latestFile.FullName -NewName "$enemyPilot.$enemyShip.$timestamp$fileExtension" - } else {} - } else {} - } - Start-Sleep $sleeptimer - } - } - } - - # Get Logged-in User - If ($line -match $loginPattern) { - # Load gamelog into memory - $authLog = Get-Content -Path $logFilePath - $authLog = $authlog -match $loginPattern - $authLog = $authLog | Out-String - - # Extract User Name - $nameExtract = "name\s+(?<PlayerName>[^\s-]+)" - - If ($authLog -match $nameExtract -and $global:userName -ne $nameExtract){ - $global:userName = $matches['PlayerName'] - Write-Output "PlayerName=$global:userName" - } - } - - # Detect PU or AC - if ($line -match $puPattern) { - $global:GameMode = "PU" - Write-Output "GameMode=$global:GameMode" - } - if ($line -match $acPattern) { - $global:GameMode = "AC" - Write-Output "GameMode=$global:GameMode" - } - - #Set loadout - if ($line -match $loadoutPattern) { - If ($matches['ShipName'] -match $cleanupPattern){ - if ($null -ne $matches[1]){ - $global:loadOut = $matches[1] - } - } - Write-Output "PlayerShip=$global:loadOut" - } - -} - - -# Monitor the log file and process new lines as they are added -Get-Content -Path $logFilePath -Wait -Tail 0 | ForEach-Object { - Read-LogEntry $_ -} - -<# -# Open the log file with shared access for reading and writing -$fileStream = [System.IO.FileStream]::new($logFilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite) -$reader = [System.IO.StreamReader]::new($fileStream, [System.Text.Encoding]::UTF8) # Ensure we're reading as UTF-8 - -try { - # Move to the end of the file to start monitoring new entries - $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::End) - - while ($true) { - # Read the next line from the file - $line = $reader.ReadLine() - - # Ensure we have new content to process - if ($line) { - # Process the line (this is where your log entry handler would go) - Read-LogEntry $line - } - - # Sleep for a brief moment to avoid high CPU usage - Start-Sleep -Milliseconds 100 - } -} -finally { - # Ensure we close the reader and file stream properly when done - $reader.Close() - $fileStream.Close() -} -#> 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/JumpDriveStateChangedEvent.cs b/AutoTrackR2/LogEventHandlers/JumpDriveStateChangedEvent.cs new file mode 100644 index 0000000..9955f7f --- /dev/null +++ b/AutoTrackR2/LogEventHandlers/JumpDriveStateChangedEvent.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace AutoTrackR2.LogEventHandlers; + +public class JumpDriveStateChangedEvent : ILogEventHandler +{ + public Regex Pattern { get; } + private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$"); + + public JumpDriveStateChangedEvent() + { + Pattern = new Regex(@"<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in"); + } + + public void Handle(LogEntry entry) + { + if (entry.Message is null) return; + var match = Pattern.Match(entry.Message); + if (!match.Success) return; + + match = _cleanUpPattern.Match(match.Groups["ShipName"].Value); + if (match.Success) + { + TrackREventDispatcher.OnJumpDriveStateChangedEvent(match.Groups[1].Value);; + } + } +} \ 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/RequestJumpFailedEvent.cs b/AutoTrackR2/LogEventHandlers/RequestJumpFailedEvent.cs new file mode 100644 index 0000000..c998685 --- /dev/null +++ b/AutoTrackR2/LogEventHandlers/RequestJumpFailedEvent.cs @@ -0,0 +1,27 @@ +using System.Text.RegularExpressions; + +namespace AutoTrackR2.LogEventHandlers; + +public class RequestJumpFailedEvent : ILogEventHandler +{ + public Regex Pattern { get; } + private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$"); + + public RequestJumpFailedEvent() + { + Pattern = new Regex(@"<Request Jump Failed>.*.adam: (?<ShipName>.*.) in"); + } + + public void Handle(LogEntry entry) + { + if (entry.Message is null) return; + var match = Pattern.Match(entry.Message); + if (!match.Success) return; + + match = _cleanUpPattern.Match(match.Groups["ShipName"].Value); + if (match.Success) + { + TrackREventDispatcher.OnJumpDriveStateChangedEvent(match.Groups[1].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..38ddd6e --- /dev/null +++ b/AutoTrackR2/LogHandler.cs @@ -0,0 +1,151 @@ +using System.Diagnostics; +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; } + +} + +enum GameProcessState +{ + NotRunning, + Running, + Unknown +} + +public class LogHandler(string logPath) +{ + private readonly string? _logPath = logPath; + private FileStream? _fileStream; + private StreamReader? _reader; + private GameProcessState _gameProcessState = GameProcessState.Unknown; + + 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(), + new JumpDriveStateChangedEvent(), + new RequestJumpFailedEvent() + ]; + + // 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) + { + // Console.WriteLine(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 == null || _fileStream == null) + { + break; + } + + CheckGameProcessState(); + + List<string> lines = new List<string>(); + while (_reader.ReadLine() is { } line) + { + lines.Add(line); + } + + foreach (var line in lines) + { + // start new thread to handle log entry + var thread = new Thread(() => HandleLogEntry(line)); + thread.Start(); + // Console.WriteLine(line); + } + + { + // Wait for new lines to be written to the log file + Thread.Sleep(1000); + } + } + Console.WriteLine("Monitor thread stopped"); + } + + private void CheckGameProcessState() + { + // Check if the game process is running by window name + var process = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == "Star Citizen"); + + GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning; + + if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning) + { + // Game process went from NotRunning to Running, so reload the Game.log file + Console.WriteLine("Game process started, reloading log file"); + + _reader?.Close(); + _fileStream?.Close(); + + _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _reader = new StreamReader(_fileStream); + } + + _gameProcessState = newGameProcessState; + } +} \ No newline at end of file diff --git a/AutoTrackR2/MainWindow.xaml.cs b/AutoTrackR2/MainWindow.xaml.cs index e432e97..637e90f 100644 --- a/AutoTrackR2/MainWindow.xaml.cs +++ b/AutoTrackR2/MainWindow.xaml.cs @@ -1,4 +1,6 @@ //using System.Collections.Generic; + +using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -33,9 +35,6 @@ namespace AutoTrackR2 { InitializeComponent(); - // Load configuration settings before setting them in any page - ConfigManager.LoadConfig(); - homePage = new HomePage(); // Create a single instance of HomePage ContentControl.Content = homePage; // Default to HomePage @@ -195,6 +194,13 @@ namespace AutoTrackR2 public static class ConfigManager { public static string LogFile { get; set; } + public static string KillHistoryFile { get; set; } + + public static string AHKScriptFolder { get; set; } + + public static string VisorWipeScript { get; set; } + public static string VideoRecordScript { get; set; } + public static string ApiUrl { get; set; } public static string ApiKey { get; set; } public static string VideoPath { get; set; } @@ -202,6 +208,27 @@ namespace AutoTrackR2 public static int VideoRecord { get; set; } public static int OfflineMode { get; set; } public static int Theme { get; set; } + + static ConfigManager() + { + LoadConfig(); + + // Set default values + // AppData\Local\AutoTrackR2\Kill-log.csv + KillHistoryFile = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "AutoTrackR2", + "Kill-log.csv" + ); + + AHKScriptFolder = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "AutoTrackR2" + ); + + VisorWipeScript = "visorwipe.ahk"; + VideoRecordScript = "videorecord.ahk"; + } public static void LoadConfig() { @@ -241,7 +268,7 @@ namespace AutoTrackR2 // Define the config file path in a writable location string configDirectory = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "YourAppName" + "AutoTrackR2" ); // Ensure the directory exists diff --git a/AutoTrackR2/TrackREventDispatcher.cs b/AutoTrackR2/TrackREventDispatcher.cs new file mode 100644 index 0000000..9aaab03 --- /dev/null +++ b/AutoTrackR2/TrackREventDispatcher.cs @@ -0,0 +1,57 @@ +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); + } + + // Jump Drive state has changed + // Todo: Add proper data for this event. Right now only ship name is used. + public static event Action<string>? JumpDriveStateChangedEvent; + public static void OnJumpDriveStateChangedEvent(string shipName) + { + JumpDriveStateChangedEvent?.Invoke(shipName); + } +} \ No newline at end of file diff --git a/AutoTrackR2/UpdatePage.xaml.cs b/AutoTrackR2/UpdatePage.xaml.cs index 0d834fd..87b260e 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.08"; + public static string currentVersion = "v2.09"; private string latestVersion; public UpdatePage() diff --git a/AutoTrackR2/Util.cs b/AutoTrackR2/Util.cs new file mode 100644 index 0000000..b5931de --- /dev/null +++ b/AutoTrackR2/Util.cs @@ -0,0 +1,31 @@ +namespace AutoTrackR2; + +// Data returned from the CIG API +public struct PlayerData +{ + public string? PFPURL; + public string? UEERecord; + public string? OrgURL; + public string? OrgName; + public string? JoinDate; +} + +// Amalgamation of all data from a single kill +public struct KillData +{ + public string? KillTime; + public string? EnemyPilot; + public string? EnemyShip; + public string? Enlisted; + public string? RecordNumber; + public string? OrgAffiliation; + public string? Player; + public string? Weapon; + public string? Ship; + public string? Method; + public string? Mode; + public string? GameVersion; + public string? TrackRver; + public string? Logged; + public string? PFP; +} \ No newline at end of file diff --git a/AutoTrackR2/WebHandler.cs b/AutoTrackR2/WebHandler.cs new file mode 100644 index 0000000..2d8ad59 --- /dev/null +++ b/AutoTrackR2/WebHandler.cs @@ -0,0 +1,122 @@ +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\">\\n.*.<span class=\"label\">UEE Citizen Record<\\/span>\\n.*.<strong class=\"value\">#(?<UEERecord>\\d+)<\\/strong>"); + 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["UEERecord"].Value == "n/a" ? "-1" : 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(KillData killData) + { + // int secondSpaceIndex = killData.Enlisted.IndexOf(" ", killData.Enlisted.IndexOf(" ") + 1); + // killData.Enlisted = killData.Enlisted.Insert(secondSpaceIndex, ","); + var apiKillData = new APIKillData + { + victim_ship = killData.EnemyShip, + victim = killData.EnemyPilot, + enlisted = killData.Enlisted, + rsi = killData.RecordNumber, + weapon = killData.Weapon, + method = killData.Method, + gamemode = killData.Mode, + // loadout_ship = LocalPlayerData.PlayerShip ?? "Unknown", + loadout_ship = killData.Ship, + game_version = killData.GameVersion, + trackr_version = killData.TrackRver, + location = "Unknown" + }; + + if (string.IsNullOrEmpty(apiKillData.rsi)) + { + apiKillData.rsi = "-1"; + } + + var httpClient = new HttpClient(); + string jsonData = JsonSerializer.Serialize(apiKillData); + httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + ConfigManager.ApiKey); + httpClient.DefaultRequestHeaders.Add("User-Agent", "AutoTrackR2"); + httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); + var response = await httpClient.PostAsync(ConfigManager.ApiUrl + "register-kill", new StringContent(jsonData, Encoding.UTF8, "application/json")); + if (response.StatusCode != HttpStatusCode.OK) + { + Console.WriteLine("Failed to submit kill data: "); + Console.WriteLine(jsonData); + } + else if (response.StatusCode == HttpStatusCode.OK) + { + Console.WriteLine("Successfully submitted kill data"); + } + } +} \ No newline at end of file From f1167935919f5607157a767396346ef0f6dad323 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 09:20:13 +1000 Subject: [PATCH 12/17] Fixed Theme's not being applied correctly Sometimes when switching themes it wouldn't apply the theme correctly. --- AutoTrackR2/HomePage.xaml.cs | 104 ++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index e3eb36a..3afdaca 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -18,17 +18,17 @@ public partial class HomePage : UserControl private LogHandler? _logHandler; private KillHistoryManager _killHistoryManager; private bool _UIEventsRegistered = false; - + public HomePage() { InitializeComponent(); - + _killHistoryManager = new KillHistoryManager(ConfigManager.KillHistoryFile); // Set the TextBlock text KillTallyTitle.Text = $"Kill Tally - {_killHistoryManager.GetKillsInCurrentMonth().Count}"; AddKillHistoryKillsToUI(); - + } // public void UpdateButtonState(bool isRunning) @@ -68,7 +68,7 @@ public partial class HomePage : UserControl StartButton.Style = (Style)FindResource("ButtonStyle"); StopButton.IsEnabled = false; // Disable Stop button } - + RegisterUIEventHandlers(); } @@ -77,13 +77,13 @@ public partial class HomePage : UserControl 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 AddKillHistoryKillsToUI() { var kills = _killHistoryManager.GetKills(); @@ -97,9 +97,10 @@ public partial class HomePage : UserControl { if (_UIEventsRegistered) return; - + // Username - TrackREventDispatcher.PlayerLoginEvent += (username) => { + TrackREventDispatcher.PlayerLoginEvent += (username) => + { Dispatcher.Invoke(() => { PilotNameTextBox.Text = username; @@ -107,19 +108,21 @@ public partial class HomePage : UserControl LocalPlayerData.Username = username; }); }; - + // Ship - TrackREventDispatcher.JumpDriveStateChangedEvent += (shipName) => { - Dispatcher.Invoke(() => - { - PlayerShipTextBox.Text = shipName; - AdjustFontSize(PlayerShipTextBox); - LocalPlayerData.PlayerShip = shipName; - }); + TrackREventDispatcher.JumpDriveStateChangedEvent += (shipName) => + { + Dispatcher.Invoke(() => + { + PlayerShipTextBox.Text = shipName; + AdjustFontSize(PlayerShipTextBox); + LocalPlayerData.PlayerShip = shipName; + }); }; - + // Game Mode - TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) => { + TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) => + { Dispatcher.Invoke(() => { GameModeTextBox.Text = mode.ToString(); @@ -127,14 +130,16 @@ public partial class HomePage : UserControl LocalPlayerData.CurrentGameMode = mode; }); }; - + // Game Version - TrackREventDispatcher.GameVersionEvent += (version) => { - LocalPlayerData.GameVersion = version; + TrackREventDispatcher.GameVersionEvent += (version) => + { + LocalPlayerData.GameVersion = version; }; - + // Actor Death - TrackREventDispatcher.ActorDeathEvent += async (actorDeathData) => { + TrackREventDispatcher.ActorDeathEvent += async (actorDeathData) => + { if (actorDeathData.VictimPilot != LocalPlayerData.Username) { var playerData = await WebHandler.GetPlayerData(actorDeathData.VictimPilot); @@ -156,7 +161,7 @@ public partial class HomePage : UserControl KillTime = DateTime.UtcNow.ToString("dd MMM yyyy HH:mm"), PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg" }; - + switch (LocalPlayerData.CurrentGameMode) { case GameMode.PersistentUniverse: @@ -166,13 +171,13 @@ public partial class HomePage : UserControl killData.Mode = "ac"; break; } - + // Add kill to UI Dispatcher.Invoke(() => { AddKillToScreen(killData); }); - + // Only submit kill data if not in offline mode if (ConfigManager.OfflineMode == 0) { @@ -185,17 +190,18 @@ public partial class HomePage : UserControl } } }; - + // Vehicle Destruction - TrackREventDispatcher.VehicleDestructionEvent += (data) => { + TrackREventDispatcher.VehicleDestructionEvent += (data) => + { LocalPlayerData.LastSeenVehicleLocation = data.VehicleZone; }; - + _UIEventsRegistered = true; } private void AddKillToScreen(KillData killData) - { + { // Fetch the dynamic resource for AltTextColor var altTextColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AltTextColor"]); var accentColorBrush = new SolidColorBrush((Color)Application.Current.Resources["AccentColor"]); @@ -236,22 +242,22 @@ public partial class HomePage : UserControl FontFamily = orbitronFontFamily, }); killTextBlock.Inlines.Add(new Run($"{killData.OrgAffiliation}\n")); - + killTextBlock.Inlines.Add(new Run("Join Date: ") { Foreground = altTextColorBrush, FontFamily = orbitronFontFamily, }); killTextBlock.Inlines.Add(new Run($"{killData.Enlisted}\n")); - + killTextBlock.Inlines.Add(new Run("UEE Record: ") { Foreground = altTextColorBrush, FontFamily = orbitronFontFamily, }); - + killTextBlock.Inlines.Add(new Run($"{killData.RecordNumber}\n")); - + killTextBlock.Inlines.Add(new Run("Kill Time: ") { Foreground = altTextColorBrush, @@ -284,7 +290,7 @@ public partial class HomePage : UserControl { killData.PFP = "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg"; } - + // Create the Image for the profile var profileImage = new Image { @@ -295,15 +301,13 @@ public partial class HomePage : UserControl }; // 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 - }; + var imageBorder = new Border(); + imageBorder.SetResourceReference(Border.BorderBrushProperty, "AccentBrush"); + imageBorder.BorderThickness = new Thickness(2); + imageBorder.Padding = new Thickness(0); + imageBorder.CornerRadius = new CornerRadius(5); + imageBorder.Margin = new Thickness(10, 18, 15, 18); + imageBorder.Child = profileImage; // Add the Border (with the image inside) to the Grid Grid.SetColumn(imageBorder, 1); @@ -370,19 +374,19 @@ public partial class HomePage : UserControl // Apply the adjusted font size textBlock.FontSize = fontSize; } - + public static void RunAHKScript(string path) { string scriptPath = Path.Combine(ConfigManager.AHKScriptFolder, path); - + if (!File.Exists(scriptPath)) { return; } - + // Run the script using powershell using var ahkProcess = new Process(); - + // Runs the script via Explorer, ensuring it uses whatever the // default binary for AHK is. Skips having to find a specific path to AHK ahkProcess.StartInfo.FileName = "explorer"; @@ -397,7 +401,7 @@ public partial class HomePage : UserControl RunAHKScript(ConfigManager.VisorWipeScript); } } - + private void VideoRecord() { if (ConfigManager.VideoRecord == 1) From c2cdd2a6a1ee809928f42447d65991498a9aec4d Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 09:27:00 +1000 Subject: [PATCH 13/17] Added Kill Tally for current month. --- AutoTrackR2/HomePage.xaml.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index 3afdaca..1baf48b 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -26,7 +26,9 @@ public partial class HomePage : UserControl _killHistoryManager = new KillHistoryManager(ConfigManager.KillHistoryFile); // Set the TextBlock text - KillTallyTitle.Text = $"Kill Tally - {_killHistoryManager.GetKillsInCurrentMonth().Count}"; + KillTallyTitle.Text = $"Kill Tally - {DateTime.Now.ToString("MMMM")}"; + KillTallyTextBox.Text = _killHistoryManager.GetKillsInCurrentMonth().Count.ToString(); + AdjustFontSize(KillTallyTextBox); AddKillHistoryKillsToUI(); } @@ -125,7 +127,7 @@ public partial class HomePage : UserControl { Dispatcher.Invoke(() => { - GameModeTextBox.Text = mode.ToString(); + GameModeTextBox.Text = mode == GameMode.PersistentUniverse ? "PU" : mode.ToString(); AdjustFontSize(GameModeTextBox); LocalPlayerData.CurrentGameMode = mode; }); From 0593969f77ee2eefdbe7ff019bfaf03a5ac4bdb2 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 09:29:23 +1000 Subject: [PATCH 14/17] Fixed save button on config page going off the page. --- AutoTrackR2/ConfigPage.xaml | 434 ++++++++++++++++++------------------ 1 file changed, 217 insertions(+), 217 deletions(-) diff --git a/AutoTrackR2/ConfigPage.xaml b/AutoTrackR2/ConfigPage.xaml index 5e425e9..2d98288 100644 --- a/AutoTrackR2/ConfigPage.xaml +++ b/AutoTrackR2/ConfigPage.xaml @@ -2,234 +2,234 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="410" - Width="626"> + Width="626"> - <Grid Background="{DynamicResource BackgroundLightBrush}"> - <!-- Main Layout Grid --> - <Grid Margin="0,0,5,7"> - <Grid.RowDefinitions> - <!-- One row for the content, the other for buttons --> - <RowDefinition Height="*"/> - <RowDefinition Height="Auto"/> - </Grid.RowDefinitions> + <Grid Background="{DynamicResource BackgroundLightBrush}"> + <!-- Main Layout Grid --> + <Grid Margin="0,0,5,7"> + <Grid.RowDefinitions> + <!-- One row for the content, the other for buttons --> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <!-- Left column for the main content area --> - <ColumnDefinition Width="*"/> - <!-- Right column for the buttons --> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> + <Grid.ColumnDefinitions> + <!-- Left column for the main content area --> + <ColumnDefinition Width="*"/> + <!-- Right column for the buttons --> + <ColumnDefinition Width="Auto"/> + </Grid.ColumnDefinitions> - <!-- Section for Config Fields --> - <StackPanel Grid.Column="0" - VerticalAlignment="Center" - Height="389"> - <!-- Log File --> - <StackPanel Margin="0,10,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,0,3,5"/> - <TextBlock Text="Log File:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5" - FontFamily="{StaticResource Roboto}"/> - <StackPanel Orientation="Horizontal" - Margin="30,0,0,0"> - <TextBox Name="LogFilePath" - Width="330" - Height="30" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="LogFileBrowseButton_Click"/> - </StackPanel> - </StackPanel> + <!-- Section for Config Fields --> + <StackPanel Grid.Column="0" + VerticalAlignment="Center" + Height="389"> + <!-- Log File --> + <StackPanel Margin="0,10,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,0,3,5"/> + <TextBlock Text="Log File:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5" + FontFamily="{StaticResource Roboto}"/> + <StackPanel Orientation="Horizontal" + Margin="30,0,0,0"> + <TextBox Name="LogFilePath" + Width="330" + Height="30" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="LogFileBrowseButton_Click"/> + </StackPanel> + </StackPanel> - <!-- API URL --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="API URL:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <StackPanel Orientation="Horizontal" - Margin="30,0,0,0"> - <TextBox Name="ApiUrl" - Width="330" - Height="30" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Test" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="TestApiButton_Click"/> - </StackPanel> - </StackPanel> + <!-- API URL --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API URL:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <StackPanel Orientation="Horizontal" + Margin="30,0,0,0"> + <TextBox Name="ApiUrl" + Width="330" + Height="30" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Test" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="TestApiButton_Click"/> + </StackPanel> + </StackPanel> - <!-- API Key --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="API Key:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <PasswordBox Name="ApiKey" - Width="330" - Height="30" - Margin="33,0,0,0" - Style="{StaticResource RoundedPasswordBox}"/> - </StackPanel> + <!-- API Key --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API Key:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <PasswordBox Name="ApiKey" + Width="330" + Height="30" + Margin="33,0,0,0" + Style="{StaticResource RoundedPasswordBox}"/> + </StackPanel> - <!-- Video Path --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="The directory where your clipping software saves kills. Check the README." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="Video Path:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <StackPanel Orientation="Horizontal"> - <TextBox Name="VideoPath" - Width="330" - Height="30" - Margin="10,0,0,0" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="VideoPathBrowseButton_Click"/> - </StackPanel> - </StackPanel> + <!-- Video Path --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="The directory where your clipping software saves kills. Check the README." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="Video Path:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <StackPanel Orientation="Horizontal"> + <TextBox Name="VideoPath" + Width="330" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="VideoPathBrowseButton_Click"/> + </StackPanel> + </StackPanel> - <!-- Visor Wipe Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,4,3,5"/> - <TextBlock Text="Visor Wipe:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,7,0,5"/> - <Slider Name="VisorWipeSlider" - Minimum="0" - Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" - Style="{StaticResource ToggleSliderStyle}" - Margin="27,-4,0,0" - ValueChanged="VisorWipeSlider_ValueChanged"/> - </StackPanel> + <!-- Visor Wipe Toggle Slider --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Visor Wipe:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="VisorWipeSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="27,-4,0,0" + ValueChanged="VisorWipeSlider_ValueChanged"/> + </StackPanel> - <!-- Video Record Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Automatically clip your last kill. Check the README for more info." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,4,3,5"/> - <TextBlock Text="Video Record:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,7,0,5"/> - <Slider Name="VideoRecordSlider" - Minimum="0" - Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" - Style="{StaticResource ToggleSliderStyle}" - Margin="10,-4,0,0" - ValueChanged="VideoRecordSlider_ValueChanged"/> - </StackPanel> + <!-- Video Record Toggle Slider --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Automatically clip your last kill. Check the README for more info." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Video Record:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="VideoRecordSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="10,-4,0,0" + ValueChanged="VideoRecordSlider_ValueChanged"/> + </StackPanel> - <!-- Offline Mode Toggle Slider --> - <StackPanel Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,4,3,5"/> - <TextBlock Text="Offline Mode:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,7,0,5"/> - <Slider Name="OfflineModeSlider" - Minimum="0" - Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" - Style="{StaticResource ToggleSliderStyle}" - Margin="12,-4,0,0" - ValueChanged="OfflineModeSlider_ValueChanged"/> - </StackPanel> + <!-- Offline Mode Toggle Slider --> + <StackPanel Margin="0,0,0,10" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,5"/> + <TextBlock Text="Offline Mode:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider Name="OfflineModeSlider" + Minimum="0" + Maximum="1" + TickFrequency="1" + IsSnapToTickEnabled="True" + Value="0" + Style="{StaticResource ToggleSliderStyle}" + Margin="12,-4,0,0" + ValueChanged="OfflineModeSlider_ValueChanged"/> + </StackPanel> - <!-- 3-Position Toggle Slider --> - <StackPanel Margin="0,0,0,15" - Orientation="Horizontal"> - <TextBlock Text="Theme:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,7,0,5"/> - <Slider x:Name="ThemeSlider" - Minimum="0" - Maximum="21" - Value="0" - TickFrequency="1" - IsSnapToTickEnabled="True" - ValueChanged="ThemeSlider_ValueChanged" - Width="447" - Style="{StaticResource ThreePositionSlider}"/> - </StackPanel> + <!-- 3-Position Toggle Slider --> + <StackPanel Margin="0,0,0,15" + Orientation="Horizontal"> + <TextBlock Text="Theme:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,0,5"/> + <Slider x:Name="ThemeSlider" + Minimum="0" + Maximum="21" + Value="0" + TickFrequency="1" + IsSnapToTickEnabled="True" + ValueChanged="ThemeSlider_ValueChanged" + Width="447" + Style="{StaticResource ThreePositionSlider}"/> + </StackPanel> - </StackPanel> + </StackPanel> - <!-- Save Button --> - <StackPanel Grid.Column="2" - HorizontalAlignment="Right" - VerticalAlignment="Bottom" - Margin="0"> - <Button x:Name="SaveButton" - Content="Save" - Width="100" - Height="40" - Style="{StaticResource ButtonStyle}" - FontFamily="{StaticResource Orbitron}" - Click="SaveButton_Click"/> - </StackPanel> + <!-- Save Button --> + <StackPanel Grid.Column="2" + HorizontalAlignment="Right" + VerticalAlignment="Bottom" + Margin="0,0,0,10"> + <Button x:Name="SaveButton" + Content="Save" + Width="100" + Height="40" + Style="{StaticResource ButtonStyle}" + FontFamily="{StaticResource Orbitron}" + Click="SaveButton_Click"/> + </StackPanel> + </Grid> </Grid> - </Grid> </UserControl> From 2cd80970a4c23b21fc3607d4ed31a60872c248c5 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 10:11:12 +1000 Subject: [PATCH 15/17] Removed Buttons, fixed application detect + other stuff When attempting to run the app and close, it wouldn't actually close... Set default states when starcitizen isn't running to unknown. Start reading file when starcitizen is detected. Stop when starcitizen isn't running. Issue, Need to fix reading file when game is already running. --- AutoTrackR2/HomePage.xaml | 208 ++++++++++++++++++++----- AutoTrackR2/HomePage.xaml.cs | 270 +++++++++++++++++++++++++++------ AutoTrackR2/LocalPlayerData.cs | 5 +- AutoTrackR2/LogHandler.cs | 70 +++++---- AutoTrackR2/MainWindow.xaml.cs | 58 +++---- 5 files changed, 456 insertions(+), 155 deletions(-) diff --git a/AutoTrackR2/HomePage.xaml b/AutoTrackR2/HomePage.xaml index d1e2557..9c08122 100644 --- a/AutoTrackR2/HomePage.xaml +++ b/AutoTrackR2/HomePage.xaml @@ -1,48 +1,176 @@ <UserControl x:Class="AutoTrackR2.HomePage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - Height="396" Width="626"> - <Grid Background="{DynamicResource BackgroundLightBrush}"> - <!-- Main Layout Grid --> - <Grid Margin="0,0,5,7"> - <Grid.RowDefinitions> - <!-- One row for the content, the other for buttons --> - <RowDefinition Height="*" /> - <RowDefinition Height="Auto" /> - </Grid.RowDefinitions> + Height="396" + Width="626"> + <Grid Background="{DynamicResource BackgroundLightBrush}"> + <!-- Main Layout Grid --> + <Grid Margin="0,0,5,7"> + <Grid.RowDefinitions> + <!-- One row for the content, the other for buttons --> + <RowDefinition Height="*"/> + <RowDefinition Height="Auto"/> + </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <!-- Left column for the main content area --> - <ColumnDefinition /> - <!-- Right column for the buttons --> - <ColumnDefinition Width="Auto" MinWidth="173" /> - </Grid.ColumnDefinitions> + <Grid.ColumnDefinitions> + <!-- Left column for the main content area --> + <ColumnDefinition/> + <!-- Right column for the buttons --> + <ColumnDefinition Width="Auto" + MinWidth="173"/> + </Grid.ColumnDefinitions> - <!-- Border for the kill feed section --> - <!--TextBox Name="OutputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Height="NaN" Margin="0,0,20,0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="True" Style="{StaticResource RoundedTextBox}"/--> - <Border Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" BorderBrush="{DynamicResource AccentBrush}" BorderThickness="2" CornerRadius="5" Padding="10,0,0,0" Background="{DynamicResource BackgroundDarkBrush}" Margin="0,0,20,0"> - <ScrollViewer VerticalScrollBarVisibility="Auto" Width="419" Margin="0,0,-5,0"> - <StackPanel Name="KillFeedStackPanel" Orientation="Vertical" Margin="0,0,0,0" Width="402" HorizontalAlignment="Left"/> - </ScrollViewer> - </Border> + <!-- Border for the kill feed section --> + <!--TextBox Name="OutputTextBox" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Height="NaN" Margin="0,0,20,0" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" IsReadOnly="True" Style="{StaticResource RoundedTextBox}"/--> + <Border Grid.Row="0" + Grid.Column="0" + Grid.RowSpan="2" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="5" + Padding="10,0,0,0" + Background="{DynamicResource BackgroundDarkBrush}" + Margin="0,0,20,0"> + <ScrollViewer VerticalScrollBarVisibility="Auto" + Width="419" + Margin="0,0,-5,0"> + <StackPanel Name="KillFeedStackPanel" + Orientation="Vertical" + Margin="0,0,0,0" + Width="402" + HorizontalAlignment="Left"/> + </ScrollViewer> + </Border> - <!-- StackPanel for Start and Stop buttons --> - <Border Background="{DynamicResource BackgroundDarkBrush}" BorderBrush="{DynamicResource AccentBrush}" Grid.Row="0" Grid.Column="1" BorderThickness="2" CornerRadius="5" Margin="0,0,0,82"/> - <StackPanel Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Height="269" Width="152"> - <TextBlock Name="PilotNameTitle" Text="Pilot" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> - <TextBlock Name="PilotNameTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> - <TextBlock Name="PlayerShipTitle" Text="Ship" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14" /> - <TextBlock Name="PlayerShipTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> - <TextBlock Name="GameModeTitle" Text="Game Mode" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> - <TextBlock Name="GameModeTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> - <TextBlock Name="KillTallyTitle" Text="Kill Tally" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,5,0,0" Foreground="{DynamicResource AltTextBrush}" FontSize="14"/> - <TextBlock Name="KillTallyTextBox" Text="" Width="152" Height="20" Background="Transparent" FontFamily="{StaticResource Orbitron}" Margin="0,0,0,0" Foreground="{DynamicResource TextBrush}" FontSize="10" TextAlignment="Center"/> - <TextBox x:Name="DebugPanel" Text="" Width="152" Height="98" Background="Transparent" FontFamily="{StaticResource Orbitron}" Foreground="{DynamicResource TextBrush}" FontSize="8" BorderThickness="0" Margin="0,9,0,0"/> - </StackPanel> - <StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Height="120" Width="172" > - <Button Name="StartButton" Content="Start" Width="100" Height="40" Style="{StaticResource ButtonStyle}" FontFamily="{StaticResource Orbitron}" Margin="0,20" Click="StartButton_Click"/> - <Button Name="StopButton" Content="Stop" Width="100" Height="40" Style="{StaticResource DisabledButtonStyle}" FontFamily="{StaticResource Orbitron}" IsEnabled="False" Click="StopButton_Click"/> - </StackPanel> + <!-- StackPanel for Start and Stop buttons --> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + Grid.Row="0" + Grid.Column="1" + BorderThickness="2" + CornerRadius="5" + Margin="0,0,0,82"/> + <StackPanel Grid.Column="1" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Height="269" + Width="152"> + <TextBlock Name="PilotNameTitle" + Text="Pilot" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,5,0,0" + Foreground="{DynamicResource AltTextBrush}" + FontSize="14"/> + <TextBlock Name="PilotNameTextBox" + Text="" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,0,0,0" + Foreground="{DynamicResource TextBrush}" + FontSize="10" + TextAlignment="Center"/> + <TextBlock Name="PlayerShipTitle" + Text="Ship" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,5,0,0" + Foreground="{DynamicResource AltTextBrush}" + FontSize="14"/> + <TextBlock Name="PlayerShipTextBox" + Text="" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,0,0,0" + Foreground="{DynamicResource TextBrush}" + FontSize="10" + TextAlignment="Center"/> + <TextBlock Name="GameModeTitle" + Text="Game Mode" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,5,0,0" + Foreground="{DynamicResource AltTextBrush}" + FontSize="14"/> + <TextBlock Name="GameModeTextBox" + Text="" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,0,0,0" + Foreground="{DynamicResource TextBrush}" + FontSize="10" + TextAlignment="Center"/> + <TextBlock Name="KillTallyTitle" + Text="Kill Tally" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,5,0,0" + Foreground="{DynamicResource AltTextBrush}" + FontSize="14"/> + <TextBlock Name="KillTallyTextBox" + Text="" + Width="152" + Height="20" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Margin="0,0,0,0" + Foreground="{DynamicResource TextBrush}" + FontSize="10" + TextAlignment="Center"/> + <TextBox x:Name="DebugPanel" + Text="" + Width="152" + Height="98" + Background="Transparent" + FontFamily="{StaticResource Orbitron}" + Foreground="{DynamicResource TextBrush}" + FontSize="8" + BorderThickness="0" + Margin="0,9,0,0"/> + </StackPanel> + <StackPanel Grid.Row="1" + Grid.Column="1" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Height="120" + Width="172"> + <Border Background="{DynamicResource BackgroundDarkBrush}" + BorderBrush="{DynamicResource AccentBrush}" + BorderThickness="2" + CornerRadius="5" + Height="80" + Margin="0,10,0,0"> + <StackPanel Orientation="Horizontal" + HorizontalAlignment="Center" + VerticalAlignment="Center"> + <Ellipse x:Name="StatusLight" + Width="15" + Height="15" + Margin="0,0,10,0" + Fill="Red"/> + <TextBlock x:Name="StatusText" + Text="TrackR
Standby" + Foreground="{DynamicResource TextBrush}" + FontFamily="{StaticResource Orbitron}" + FontSize="14" + VerticalAlignment="Center"/> + </StackPanel> + </Border> + </StackPanel> + </Grid> </Grid> - </Grid> </UserControl> diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index 1baf48b..f8755df 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -9,15 +9,18 @@ using System.IO; using System.Text; using System.Windows.Media.Imaging; using AutoTrackR2.LogEventHandlers; +using System.Timers; +using System.Linq; namespace AutoTrackR2; public partial class HomePage : UserControl { - private Process runningProcess; // Field to store the running process private LogHandler? _logHandler; private KillHistoryManager _killHistoryManager; private bool _UIEventsRegistered = false; + private System.Timers.Timer _statusCheckTimer; + private bool _isLogHandlerRunning = false; public HomePage() { @@ -31,59 +34,71 @@ public partial class HomePage : UserControl AdjustFontSize(KillTallyTextBox); AddKillHistoryKillsToUI(); - } - // - public void UpdateButtonState(bool isRunning) - { - var accentColor = (Color)Application.Current.Resources["AccentColor"]; + // Initialize and start the status check timer + _statusCheckTimer = new System.Timers.Timer(1000); // Check every second + _statusCheckTimer.Elapsed += CheckStarCitizenStatus; + _statusCheckTimer.Start(); + // Check if Star Citizen is already running and initialize accordingly + if (IsStarCitizenRunning()) + { + Dispatcher.Invoke(() => + { + UpdateStatusIndicator(true); + ReadInitialStates(); // Read states first + InitializeLogHandler(); // Then initialize the log handler + }); + } + } + + private void CheckStarCitizenStatus(object sender, ElapsedEventArgs e) + { + bool isRunning = IsStarCitizenRunning(); + Dispatcher.Invoke(() => + { + UpdateStatusIndicator(isRunning); + + if (isRunning) + { + if (!_isLogHandlerRunning) + { + // Game is running, start log monitoring and read initial states + InitializeLogHandler(); + ReadInitialStates(); + } + } + else + { + // Game is not running, set everything to Unknown + GameModeTextBox.Text = "Unknown"; + PlayerShipTextBox.Text = "Unknown"; + PilotNameTextBox.Text = "Unknown"; + LocalPlayerData.CurrentGameMode = GameMode.Unknown; + LocalPlayerData.PlayerShip = string.Empty; + LocalPlayerData.Username = string.Empty; + + // Stop log monitoring if it's running + if (_isLogHandlerRunning) + { + _logHandler?.StopMonitoring(); + _isLogHandlerRunning = false; + } + } + }); + } + + private void UpdateStatusIndicator(bool isRunning) + { if (isRunning) { - // Set Start button to "Running..." and apply glow effect - StartButton.Content = "Running..."; - StartButton.IsEnabled = false; // Disable Start button - StartButton.Style = (Style)FindResource("DisabledButtonStyle"); - - // 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 + StatusLight.Fill = new SolidColorBrush(Colors.Green); + StatusText.Text = "TrackR\nActive"; } 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 + StatusLight.Fill = new SolidColorBrush(Colors.Red); + StatusText.Text = "TrackR\nStandby"; } - - RegisterUIEventHandlers(); - } - - 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 AddKillHistoryKillsToUI() @@ -116,7 +131,7 @@ public partial class HomePage : UserControl { Dispatcher.Invoke(() => { - PlayerShipTextBox.Text = shipName; + PlayerShipTextBox.Text = LocalPlayerData.CurrentGameMode == GameMode.PersistentUniverse ? "Player" : shipName; AdjustFontSize(PlayerShipTextBox); LocalPlayerData.PlayerShip = shipName; }); @@ -127,7 +142,7 @@ public partial class HomePage : UserControl { Dispatcher.Invoke(() => { - GameModeTextBox.Text = mode == GameMode.PersistentUniverse ? "PU" : mode.ToString(); + GameModeTextBox.Text = mode == GameMode.PersistentUniverse ? "Player" : mode.ToString(); AdjustFontSize(GameModeTextBox); LocalPlayerData.CurrentGameMode = mode; }); @@ -327,7 +342,7 @@ public partial class HomePage : UserControl public void StopButton_Click(object sender, RoutedEventArgs e) { - _logHandler?.Stop(); + _logHandler?.StopMonitoring(); // Clear the text boxes // System.Threading.Thread.Sleep(200); @@ -411,4 +426,159 @@ public partial class HomePage : UserControl RunAHKScript(ConfigManager.VideoRecordScript); } } + + public void InitializeLogHandler() + { + if (_logHandler == null) + { + _logHandler = new LogHandler(ConfigManager.LogFile); + _logHandler.Initialize(); + RegisterUIEventHandlers(); + _isLogHandlerRunning = true; + + // Read initial states after initializing log handler + ReadInitialStates(); + } + else if (!_isLogHandlerRunning) + { + _logHandler.Initialize(); + _isLogHandlerRunning = true; + ReadInitialStates(); + } + } + + private void ReadInitialStates() + { + if (string.IsNullOrEmpty(ConfigManager.LogFile) || !File.Exists(ConfigManager.LogFile)) + { + Debug.WriteLine("Log file not found or path is empty"); + return; + } + + try + { + Debug.WriteLine("Reading initial states from log file..."); + // Read the entire log file + var lines = File.ReadAllLines(ConfigManager.LogFile); + string username = ""; + string shipName = ""; + GameMode gameMode = GameMode.Unknown; + + // Read from the end of the file to get the most recent states + for (int i = lines.Length - 1; i >= 0; i--) + { + var line = lines[i]; + + // Check for username (login) + if (line.Contains("'s Character")) + { + int startIndex = line.IndexOf("'s Character"); + if (startIndex > 0) + { + username = line.Substring(0, startIndex).Trim(); + Debug.WriteLine($"Found username: {username}"); + } + } + // Check for ship name + else if (line.Contains("Entering quantum travel from")) + { + int startIndex = line.IndexOf("in ship") + 8; + int endIndex = line.IndexOf(" to ", startIndex); + if (startIndex > 8 && endIndex > startIndex) + { + shipName = line.Substring(startIndex, endIndex - startIndex).Trim(); + Debug.WriteLine($"Found ship: {shipName}"); + } + } + // Check for game mode + else if (line.Contains("Loading level")) + { + if (line.Contains("Persistent_Universe")) + { + gameMode = GameMode.PersistentUniverse; + Debug.WriteLine("Found game mode: PU"); + } + else if (line.Contains("Arena_Commander")) + { + gameMode = GameMode.ArenaCommander; + Debug.WriteLine("Found game mode: AC"); + } + } + + // If we've found all the information we need, we can stop reading + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(shipName) && gameMode != GameMode.Unknown) + { + break; + } + } + + // Update UI with found states + Dispatcher.Invoke(() => + { + if (!string.IsNullOrEmpty(username)) + { + PilotNameTextBox.Text = username; + LocalPlayerData.Username = username; + AdjustFontSize(PilotNameTextBox); + Debug.WriteLine($"Set username in UI: {username}"); + } + else + { + PilotNameTextBox.Text = "Unknown"; + LocalPlayerData.Username = string.Empty; + AdjustFontSize(PilotNameTextBox); + Debug.WriteLine("Username not found, set to Unknown"); + } + + if (!string.IsNullOrEmpty(shipName)) + { + PlayerShipTextBox.Text = gameMode == GameMode.PersistentUniverse ? "Player" : shipName; + LocalPlayerData.PlayerShip = shipName; + AdjustFontSize(PlayerShipTextBox); + Debug.WriteLine($"Set ship in UI: {PlayerShipTextBox.Text}"); + } + else + { + PlayerShipTextBox.Text = "Unknown"; + LocalPlayerData.PlayerShip = string.Empty; + AdjustFontSize(PlayerShipTextBox); + Debug.WriteLine("Ship not found, set to Unknown"); + } + + if (gameMode != GameMode.Unknown) + { + GameModeTextBox.Text = gameMode == GameMode.PersistentUniverse ? "Player" : gameMode.ToString(); + LocalPlayerData.CurrentGameMode = gameMode; + AdjustFontSize(GameModeTextBox); + Debug.WriteLine($"Set game mode in UI: {GameModeTextBox.Text}"); + } + else + { + GameModeTextBox.Text = "Unknown"; + LocalPlayerData.CurrentGameMode = GameMode.Unknown; + AdjustFontSize(GameModeTextBox); + Debug.WriteLine("Game mode not found, set to Unknown"); + } + }); + } + catch (Exception ex) + { + Debug.WriteLine($"Error reading initial states: {ex.Message}"); + } + } + + public void Cleanup() + { + // Stop and dispose the status check timer + _statusCheckTimer?.Stop(); + _statusCheckTimer?.Dispose(); + + // Stop the log handler if it's running + _logHandler?.StopMonitoring(); + } + + private bool IsStarCitizenRunning() + { + return Process.GetProcessesByName("StarCitizen").Length > 0; + } } diff --git a/AutoTrackR2/LocalPlayerData.cs b/AutoTrackR2/LocalPlayerData.cs index 668ebcd..87d8911 100644 --- a/AutoTrackR2/LocalPlayerData.cs +++ b/AutoTrackR2/LocalPlayerData.cs @@ -3,8 +3,9 @@ public enum GameMode { - ArenaCommander, - PersistentUniverse + Unknown, + PersistentUniverse, + ArenaCommander } public static class LocalPlayerData diff --git a/AutoTrackR2/LogHandler.cs b/AutoTrackR2/LogHandler.cs index 38ddd6e..fed7b33 100644 --- a/AutoTrackR2/LogHandler.cs +++ b/AutoTrackR2/LogHandler.cs @@ -12,7 +12,7 @@ public class LogEntry { public DateTime Timestamp { get; set; } public required string? Message { get; set; } - + } enum GameProcessState @@ -22,16 +22,18 @@ enum GameProcessState Unknown } -public class LogHandler(string logPath) +public class LogHandler { - private readonly string? _logPath = logPath; + private string _logPath; private FileStream? _fileStream; private StreamReader? _reader; - private GameProcessState _gameProcessState = GameProcessState.Unknown; + private Thread? _monitorThread; + private CancellationTokenSource? _cancellationTokenSource; + private GameProcessState _gameProcessState = GameProcessState.NotRunning; + private bool _isMonitoring = false; + + public bool IsMonitoring => _isMonitoring; - 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 = [ @@ -44,17 +46,21 @@ public class LogHandler(string logPath) new RequestJumpFailedEvent() ]; - // Initialize the LogHandler and run all startup handlers + public LogHandler(string logPath) + { + _logPath = logPath; + } + 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); @@ -62,19 +68,31 @@ public class LogHandler(string logPath) // 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(); + + StartMonitoring(); } - public void Stop() + public void StartMonitoring() { - // Stop the monitor thread - cancellationToken?.Cancel(); + if (_isMonitoring) return; + + _cancellationTokenSource = new CancellationTokenSource(); + _monitorThread = new Thread(() => MonitorLog(_cancellationTokenSource.Token)); + _monitorThread.Start(); + _isMonitoring = true; + } + + public void StopMonitoring() + { + if (!_isMonitoring) return; + + _cancellationTokenSource?.Cancel(); + _monitorThread?.Join(); _reader?.Close(); _fileStream?.Close(); + _isMonitoring = false; } - + // Parse a single line of the log file and run matching handlers private void HandleLogEntry(string line) { @@ -83,7 +101,7 @@ public class LogHandler(string logPath) { var match = handler.Pattern.Match(line); if (!match.Success) continue; - + var entry = new LogEntry { Timestamp = DateTime.Now, @@ -93,7 +111,7 @@ public class LogHandler(string logPath) break; } } - + private void MonitorLog(CancellationToken token) { while (!token.IsCancellationRequested) @@ -102,9 +120,9 @@ public class LogHandler(string logPath) { break; } - + CheckGameProcessState(); - + List<string> lines = new List<string>(); while (_reader.ReadLine() is { } line) { @@ -131,21 +149,21 @@ public class LogHandler(string logPath) { // Check if the game process is running by window name var process = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == "Star Citizen"); - + GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning; - + if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning) { // Game process went from NotRunning to Running, so reload the Game.log file Console.WriteLine("Game process started, reloading log file"); - + _reader?.Close(); _fileStream?.Close(); - + _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); _reader = new StreamReader(_fileStream); } - + _gameProcessState = newGameProcessState; } } \ No newline at end of file diff --git a/AutoTrackR2/MainWindow.xaml.cs b/AutoTrackR2/MainWindow.xaml.cs index 637e90f..4efcee9 100644 --- a/AutoTrackR2/MainWindow.xaml.cs +++ b/AutoTrackR2/MainWindow.xaml.cs @@ -13,7 +13,6 @@ namespace AutoTrackR2 { public partial class MainWindow : Window { - private Dictionary<string, bool> tabStates = new Dictionary<string, bool> { { "HomeTab", true }, // HomeTab is selected by default @@ -23,9 +22,7 @@ namespace AutoTrackR2 }; private HomePage homePage; // Persistent HomePage instance - private bool isRunning = false; // Single source of truth for the running state - // Ensure this method is not static public void ChangeLogoImage(string imagePath) { Logo.Source = new BitmapImage(new Uri(imagePath, UriKind.RelativeOrAbsolute)); @@ -38,10 +35,6 @@ namespace AutoTrackR2 homePage = new HomePage(); // Create a single instance of HomePage ContentControl.Content = homePage; // Default to HomePage - // Attach event handlers for the HomePage buttons - homePage.StartButton.Click += StartButton_Click; - homePage.StopButton.Click += StopButton_Click; - // Create ConfigPage and pass the MainWindow reference to it var configPage = new ConfigPage(this); @@ -51,6 +44,7 @@ namespace AutoTrackR2 UpdateTabVisuals(); Loaded += MainWindow_Loaded; // Handle Loaded event + Closing += MainWindow_Closing; // Handle Closing event } private void MainWindow_Loaded(object sender, RoutedEventArgs e) @@ -59,16 +53,23 @@ namespace AutoTrackR2 var args = Environment.GetCommandLineArgs(); if (args.Contains("-start", StringComparer.OrdinalIgnoreCase)) { - homePage.StartButton_Click(null, null); + // Initialize log handler if needed + homePage.InitializeLogHandler(); } } + private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + // Clean up resources + homePage?.Cleanup(); + + // Make sure the application exits completely + Application.Current.Shutdown(); + } + private void CloseWindow(object sender, RoutedEventArgs e) { - // If runningProcess is not null and still active, terminate it - homePage.StopButton_Click(sender, e); - - // Close the main window + // This will trigger the Closing event this.Close(); } @@ -91,9 +92,6 @@ namespace AutoTrackR2 { // Reuse the existing HomePage instance ContentControl.Content = homePage; - - // Update the button state on the HomePage - homePage.UpdateButtonState(isRunning); } else if (clickedTabName == "StatsTab") { @@ -158,20 +156,6 @@ namespace AutoTrackR2 } } - private void StartButton_Click(object sender, RoutedEventArgs e) - { - isRunning = true; // Update the running state - homePage.UpdateButtonState(isRunning); // Update HomePage button visuals - // Start your logic here - } - - private void StopButton_Click(object sender, RoutedEventArgs e) - { - isRunning = false; // Update the running state - homePage.UpdateButtonState(isRunning); // Update HomePage button visuals - // Stop your logic here - } - private void InitializeConfigPage() { // Set the values from the loaded config @@ -195,12 +179,12 @@ namespace AutoTrackR2 { public static string LogFile { get; set; } public static string KillHistoryFile { get; set; } - + public static string AHKScriptFolder { get; set; } - + public static string VisorWipeScript { get; set; } public static string VideoRecordScript { get; set; } - + public static string ApiUrl { get; set; } public static string ApiKey { get; set; } public static string VideoPath { get; set; } @@ -208,11 +192,11 @@ namespace AutoTrackR2 public static int VideoRecord { get; set; } public static int OfflineMode { get; set; } public static int Theme { get; set; } - + static ConfigManager() - { + { LoadConfig(); - + // Set default values // AppData\Local\AutoTrackR2\Kill-log.csv KillHistoryFile = Path.Combine( @@ -220,12 +204,12 @@ namespace AutoTrackR2 "AutoTrackR2", "Kill-log.csv" ); - + AHKScriptFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoTrackR2" ); - + VisorWipeScript = "visorwipe.ahk"; VideoRecordScript = "videorecord.ahk"; } From 1acae27793c94f922616a613dfda0fa11a3d7693 Mon Sep 17 00:00:00 2001 From: Heavy Bob <ferrettclay@gmail.com> Date: Mon, 7 Apr 2025 10:18:52 +1000 Subject: [PATCH 16/17] Fixed all the cursed warnings when building. Fixed all the values that can be nullable on the client. --- AutoTrackR2/ConfigPage.xaml.cs | 16 +++++++--- AutoTrackR2/HomePage.xaml.cs | 15 ++++++++-- .../VehicleDestructionEvent.cs | 6 +++- AutoTrackR2/LogHandler.cs | 6 +++- AutoTrackR2/MainWindow.xaml.cs | 29 +++++++++---------- AutoTrackR2/UpdatePage.xaml.cs | 9 +++--- 6 files changed, 52 insertions(+), 29 deletions(-) diff --git a/AutoTrackR2/ConfigPage.xaml.cs b/AutoTrackR2/ConfigPage.xaml.cs index bf83314..9191f75 100644 --- a/AutoTrackR2/ConfigPage.xaml.cs +++ b/AutoTrackR2/ConfigPage.xaml.cs @@ -399,11 +399,14 @@ namespace AutoTrackR2 dialog.ValidateNames = false; dialog.Filter = "All files|*.*"; - if (dialog.ShowDialog() == true) + if (dialog.ShowDialog() == true && dialog.FileName != null) { // Extract only the directory path from the file - string selectedFolder = Path.GetDirectoryName(dialog.FileName); - VideoPath.Text = selectedFolder; // Set the folder path + string? selectedFolder = Path.GetDirectoryName(dialog.FileName); + if (selectedFolder != null) + { + VideoPath.Text = selectedFolder; // Set the folder path + } } } @@ -412,6 +415,11 @@ namespace AutoTrackR2 Slider slider = (Slider)sender; // Build the dynamic file path for the current user + if (string.IsNullOrEmpty(ConfigManager.AHKScriptFolder)) + { + MessageBox.Show("AHK script folder path is not configured.", "Configuration Error", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } string filePath = Path.Combine( ConfigManager.AHKScriptFolder, "visorwipe.ahk" @@ -524,7 +532,7 @@ namespace AutoTrackR2 private void FlashSaveButton() { - string originalText = SaveButton.Content.ToString(); + string? originalText = SaveButton.Content?.ToString() ?? string.Empty; SaveButton.Content = "Saved"; // Save button color change effect diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index f8755df..3d4b897 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -26,6 +26,10 @@ public partial class HomePage : UserControl { InitializeComponent(); + if (string.IsNullOrEmpty(ConfigManager.KillHistoryFile)) + { + throw new InvalidOperationException("KillHistoryFile path is not configured."); + } _killHistoryManager = new KillHistoryManager(ConfigManager.KillHistoryFile); // Set the TextBlock text @@ -51,7 +55,7 @@ public partial class HomePage : UserControl } } - private void CheckStarCitizenStatus(object sender, ElapsedEventArgs e) + private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e) { bool isRunning = IsStarCitizenRunning(); Dispatcher.Invoke(() => @@ -311,7 +315,7 @@ public partial class HomePage : UserControl // Create the Image for the profile var profileImage = new Image { - Source = new BitmapImage(new Uri(killData.PFP)), // Assuming the 8th part contains the profile image URL + Source = new BitmapImage(new Uri(killData.PFP ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg")), Width = 90, Height = 90, Stretch = Stretch.Fill, // Adjust how the image fits @@ -392,8 +396,13 @@ public partial class HomePage : UserControl textBlock.FontSize = fontSize; } - public static void RunAHKScript(string path) + public static void RunAHKScript(string? path) { + if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(ConfigManager.AHKScriptFolder)) + { + return; + } + string scriptPath = Path.Combine(ConfigManager.AHKScriptFolder, path); if (!File.Exists(scriptPath)) diff --git a/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs b/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs index 041859a..786f46f 100644 --- a/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs +++ b/AutoTrackR2/LogEventHandlers/VehicleDestructionEvent.cs @@ -19,7 +19,7 @@ public struct VehicleDestructionData public class VehicleDestructionEvent : ILogEventHandler { public Regex Pattern { get; } - + public VehicleDestructionEvent() { Pattern = new Regex(""" @@ -34,6 +34,10 @@ public class VehicleDestructionEvent : ILogEventHandler public void Handle(LogEntry entry) { + if (entry.Message == null) + { + return; + } var match = Pattern.Match(entry.Message); if (!match.Success) { diff --git a/AutoTrackR2/LogHandler.cs b/AutoTrackR2/LogHandler.cs index fed7b33..ae62e25 100644 --- a/AutoTrackR2/LogHandler.cs +++ b/AutoTrackR2/LogHandler.cs @@ -46,8 +46,12 @@ public class LogHandler new RequestJumpFailedEvent() ]; - public LogHandler(string logPath) + public LogHandler(string? logPath) { + if (string.IsNullOrEmpty(logPath)) + { + throw new ArgumentNullException(nameof(logPath), "Log path cannot be null or empty"); + } _logPath = logPath; } diff --git a/AutoTrackR2/MainWindow.xaml.cs b/AutoTrackR2/MainWindow.xaml.cs index 4efcee9..8c0a4e9 100644 --- a/AutoTrackR2/MainWindow.xaml.cs +++ b/AutoTrackR2/MainWindow.xaml.cs @@ -58,7 +58,7 @@ namespace AutoTrackR2 } } - private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) + private void MainWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e) { // Clean up resources homePage?.Cleanup(); @@ -163,10 +163,10 @@ namespace AutoTrackR2 // Set the fields in ConfigPage.xaml.cs based on the loaded config configPage.SetConfigValues( - ConfigManager.LogFile, - ConfigManager.ApiUrl, - ConfigManager.ApiKey, - ConfigManager.VideoPath, + ConfigManager.LogFile ?? string.Empty, + ConfigManager.ApiUrl ?? string.Empty, + ConfigManager.ApiKey ?? string.Empty, + ConfigManager.VideoPath ?? string.Empty, ConfigManager.VisorWipe, ConfigManager.VideoRecord, ConfigManager.OfflineMode, @@ -177,17 +177,14 @@ namespace AutoTrackR2 public static class ConfigManager { - public static string LogFile { get; set; } - public static string KillHistoryFile { get; set; } - - public static string AHKScriptFolder { get; set; } - - public static string VisorWipeScript { get; set; } - public static string VideoRecordScript { get; set; } - - public static string ApiUrl { get; set; } - public static string ApiKey { get; set; } - public static string VideoPath { get; set; } + public static string? LogFile { get; set; } = string.Empty; + public static string? KillHistoryFile { get; set; } = string.Empty; + public static string? AHKScriptFolder { get; set; } = string.Empty; + public static string? VisorWipeScript { get; set; } = string.Empty; + public static string? VideoRecordScript { get; set; } = string.Empty; + public static string? ApiUrl { get; set; } = string.Empty; + public static string? ApiKey { get; set; } = string.Empty; + public static string? VideoPath { get; set; } = string.Empty; public static int VisorWipe { get; set; } public static int VideoRecord { get; set; } public static int OfflineMode { get; set; } diff --git a/AutoTrackR2/UpdatePage.xaml.cs b/AutoTrackR2/UpdatePage.xaml.cs index 87b260e..69bed10 100644 --- a/AutoTrackR2/UpdatePage.xaml.cs +++ b/AutoTrackR2/UpdatePage.xaml.cs @@ -10,7 +10,7 @@ namespace AutoTrackR2 public partial class UpdatePage : UserControl { public static string currentVersion = "v2.09"; - private string latestVersion; + private string? latestVersion = string.Empty; public UpdatePage() { @@ -60,7 +60,7 @@ namespace AutoTrackR2 // Parse the JSON using System.Text.Json using var document = System.Text.Json.JsonDocument.Parse(response); var root = document.RootElement; - var tagName = root.GetProperty("tag_name").GetString(); + var tagName = root.GetProperty("tag_name").GetString() ?? "unknown"; return tagName; } @@ -77,7 +77,8 @@ namespace AutoTrackR2 if (root.GetArrayLength() > 0) { var firstRelease = root[0]; - return firstRelease.GetProperty("tag_name").GetString(); + var tagName = firstRelease.GetProperty("tag_name").GetString() ?? "unknown"; + return tagName; } throw new Exception("No releases found."); @@ -90,7 +91,7 @@ namespace AutoTrackR2 return !currentVersion.Equals(latestVersion, StringComparison.Ordinal); } - private async void InstallButton_Click(object sender, RoutedEventArgs e) + private void InstallButton_Click(object sender, RoutedEventArgs e) { try { From 5b9a9b925fd4656991f751fb27cbded355562757 Mon Sep 17 00:00:00 2001 From: Dork Normalize <nope> Date: Sun, 6 Apr 2025 18:07:23 -0700 Subject: [PATCH 17/17] Fix borked merge conflict resolution --- AutoTrackR2/MainWindow.xaml.cs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/AutoTrackR2/MainWindow.xaml.cs b/AutoTrackR2/MainWindow.xaml.cs index 2fccd24..d045a0c 100644 --- a/AutoTrackR2/MainWindow.xaml.cs +++ b/AutoTrackR2/MainWindow.xaml.cs @@ -211,27 +211,6 @@ namespace AutoTrackR2 VideoRecordScript = "videorecord.ahk"; } - static ConfigManager() - { - LoadConfig(); - - // Set default values - // AppData\Local\AutoTrackR2\Kill-log.csv - KillHistoryFile = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "AutoTrackR2", - "Kill-log.csv" - ); - - AHKScriptFolder = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "AutoTrackR2" - ); - - VisorWipeScript = "visorwipe.ahk"; - VideoRecordScript = "videorecord.ahk"; - } - public static void LoadConfig() { // Define the config file path in a writable location