diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs index a376a4d..316e55a 100644 --- a/Models/AppSettings.cs +++ b/Models/AppSettings.cs @@ -85,6 +85,11 @@ static Settings() /// public bool ColorfulObjectives { get; set; } = true; + /// + /// Enables OSC and sends avatar parameters based on in-game events. + /// + public bool OSCEnabled { get; set; } + /// /// Stores a github release tag if the player clicks no when asking for update. /// diff --git a/Models/Entry.cs b/Models/Entry.cs index 001519c..1435f87 100644 --- a/Models/Entry.cs +++ b/Models/Entry.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using System.Diagnostics; using System.Text; namespace ToNSaveManager.Models @@ -116,6 +117,9 @@ public string GetTooltip(bool showPlayers, bool showTerrors, bool showNote = tru public void CopyToClipboard() { +#if DEBUG + Debug.WriteLine("COPYING TO CLIPBOARD"); +#endif // Windows 11 please... :[ // Clipboard.Clear(); Clipboard.SetDataObject(Content, false, 4, 200); diff --git a/Models/ToNIndex.cs b/Models/ToNIndex.cs index 35f8e6b..daa0f28 100644 --- a/Models/ToNIndex.cs +++ b/Models/ToNIndex.cs @@ -4,21 +4,29 @@ namespace ToNSaveManager.Models { internal struct TerrorMatrix { - internal static TerrorMatrix Empty => new TerrorMatrix(); + internal static TerrorMatrix Empty = new TerrorMatrix(); + public int[] TerrorIndex; public string[] TerrorNames; public string RoundTypeRaw; public ToNRoundType RoundType; + public bool IsSaboteour; + + public int Terror1 => TerrorIndex != null && TerrorIndex.Length > 0 ? TerrorIndex[0] : 0; + public int Terror2 => TerrorIndex != null && TerrorIndex.Length > 1 ? TerrorIndex[1] : 0; + public int Terror3 => TerrorIndex != null && TerrorIndex.Length > 2 ? TerrorIndex[2] : 0; public TerrorMatrix() { - TerrorNames = new string[0]; + TerrorIndex = new int[3]; + TerrorNames = new string[3]; RoundTypeRaw = string.Empty; RoundType = ToNRoundType.Unknown; } public TerrorMatrix(string roundType, params int[] indexes) { + TerrorIndex = indexes; RoundTypeRaw = GetEngRoundType(roundType); RoundType = GetRoundType(RoundTypeRaw); @@ -95,9 +103,6 @@ public string GetNames(string separator = ", ") // Events "Cold Night" , "冷たい夜", // Winterfest - - // Beyond's favorite - "Custom" , "カスタム", // IGNORE SAVES FOR THIS ONE }; static string GetEngRoundType(string roundType) @@ -141,7 +146,6 @@ static ToNRoundType GetRoundType(string raw) { ToNRoundType.RUN, 0xC15E3D }, { ToNRoundType.Eight_Pages, 0xFFFFFF }, - { ToNRoundType.Custom, 0x000000 }, // Ignored { ToNRoundType.Cold_Night, 0xA37BE4 }, }; @@ -170,9 +174,9 @@ public enum ToNRoundType Mystic_Moon, Blood_Moon, Twilight, Solstice, // Special // Replace 8 with Eight - RUN, Eight_Pages, Custom, + RUN, Eight_Pages, - // Events + // New Cold_Night } diff --git a/README.md b/README.md index 3f58af8..59c6cc6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ - While the tool is running, it will detect new codes as you play. - Previously detected save codes will be saved to a local database, so if VRChat deletes logs overtime, you'll have a history of Save Codes locally, and safe. -### Settings Window +## Settings Window +- `Check For Updates` When clicked, it will check this github repo for new releases, and prompt you to try an automatic update. - `Auto Clipboard Copy` Automatically copy new save codes to clipboard. - `Collect Player Names` Save codes will show players that were in the instance. - `XSOverlay Popup` XSOverlay notifications when new save codes are detected. @@ -30,11 +31,11 @@ - Double Click to select a custom audio file. (Only '.wav' files) - Right Click to reset audio file back to 'default.wav' - `Colorful Objectives` Items in the 'Objectives' window will show colors that correspond to those of the items in the game. -- `Auto Discord Backup` Uses a [discord webhook](##how-to-properly-configure-automatic-discord-backup-using-webhooks) to automatically upload a backup of your new codes to a discord channel as you play. -- `Check For Updates` When clicked, it will check this github repo for new releases, and prompt you to try an automatic update. +- `Auto Discord Backup` Uses a [discord webhook](#how-to-properly-configure-automatic-discord-backup-using-webhooks) to automatically upload a backup of your new codes to a discord channel as you play. +- `Send OSC Parameters` Sends avatar parameters to VRChat using OSC. Check the [documentation](#osc-documentation) below for more info.
Preview Image

-### Right Click Menus +## Right Click Menus - ### Log Dates (Left Panel) * `Import` You can enter your own code and save it in that collection. * `Rename` Lets you rename a collection. @@ -45,9 +46,44 @@ * `Backup` Forces a backup upload to Discord if **Auto Discord Backup** is configured on settings. * `Delete` Deletes just this save code from the database. -### Objectives Window +## Objectives Window - This window gives you a list of unlockables that you can check to track your progress. Just click on the things you already unlocked. +## OSC Documentation +
Parameter Names & Types

+

+Parameter Name | Type |
+---------------|------|--------------------------
+ToN_RoundType  | INT  | The current round type.
+ToN_Terror1    | INT  | The current terror index.
+ToN_Terror2    | INT  | The second terror index.
+ToN_Terror3    | INT  | The third terror index.
+ToN_OptedIn    | BOOL | Is the player opted-in at the lobby
+ToN_Saboteur   | BOOL | Is the player currently the Saboteur
+
+

+ +
Round Type Values

+

+ 0  =  Unknown
+ 1  =  Classic
+ 2  =  Fog
+ 3  =  Punished
+ 4  =  Sabotage
+ 5  =  Cracked
+ 6  =  Bloodbath
+ 7  =  Midnight
+ 8  =  Alternate
+ 9  =  Mystic_Moon
+10  =  Blood_Moon
+11  =  Twilight
+12  =  Solstice
+13  =  RUN
+14  =  Eight_Pages
+15  =  Cold_Night
+
+

+ # 📋 FAQ: > ## How do I use this? @@ -58,7 +94,7 @@ > 5. Your code is now in the clipboard, go to VRChat and paste the code in the input field. > ## Where can I request a feature? -> If you want to suggest new features or changes, you can open an Issue here or you can ping me on the official [Toren Discord](https://discord.gg/bus-to-nowhere) as @**Kittenji** +> If you want to suggest new features or changes, you can open an [Issue](https://github.com/ChrisFeline/ToNSaveManager/issues) here or you can ping me on the official [Toren Discord](https://discord.gg/bus-to-nowhere) as @**Kittenji** > ## How does it work? > The world periodically saves a snapshot of your progress in the VRChat log files. diff --git a/Utils/Discord/DSWebHook.cs b/Utils/Discord/DSWebHook.cs index 0ee7770a..5abba6e 100644 --- a/Utils/Discord/DSWebHook.cs +++ b/Utils/Discord/DSWebHook.cs @@ -28,7 +28,7 @@ internal static class DSWebHook static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; - static readonly Queue EntryQueue = new Queue(); + static readonly Queue> EntryQueue = new Queue>(); static bool IsSending = false; internal static void Send(Entry entry, bool ignoreDuplicate = false) @@ -41,7 +41,7 @@ internal static void Send(Entry entry, bool ignoreDuplicate = false) if (!ignoreDuplicate && LastEntry != null && LastEntry.Content == entry.Content) return; LastEntry = entry; - EntryQueue.Enqueue(entry); + EntryQueue.Enqueue(new KeyValuePair(ignoreDuplicate, entry)); if (IsSending) return; _ = Send(webhookUrl); @@ -65,7 +65,11 @@ private static async Task Send(string webhookUrl) { while (EntryQueue.Count > 0) { - Entry entry = EntryQueue.Dequeue(); + KeyValuePair entryData = EntryQueue.Dequeue(); + + Entry entry = entryData.Value; + bool ignoreDuplicate = entryData.Key; + DateTime time = entry.Timestamp; EmbedData.Description = string.Empty; @@ -96,6 +100,11 @@ private static async Task Send(string webhookUrl) EmbedData.Description += $"**Player Count**: `{entry.PlayerCount}`"; } + if (ignoreDuplicate && !string.IsNullOrEmpty(entry.Note)) { + if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; + EmbedData.Description += $"**Note**: `{entry.Note.Replace('`', '\'')}`"; + } + string payloadData = JsonConvert.SerializeObject(PayloadData, JsonSettings); MultipartFormDataContent form = new MultipartFormDataContent(); diff --git a/Utils/LilOSC.cs b/Utils/LilOSC.cs new file mode 100644 index 0000000..460f938 --- /dev/null +++ b/Utils/LilOSC.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Sockets; +using System.Net; +using System.Text; +using ToNSaveManager.Models; + +namespace ToNSaveManager.Utils { + internal static class LilOSC { + static UdpClient? UdpClient; + const string ParamRoundType = "ToN_RoundType"; + const string ParamTerror1 = "ToN_Terror1"; + const string ParamTerror2 = "ToN_Terror2"; + const string ParamTerror3 = "ToN_Terror3"; + const string ParamOptedIn = "ToN_OptedIn"; + const string ParamSaboteur = "ToN_Saboteur"; + + static bool IsDirty = false; + + static int LastRoundType = -1; + static int LastTerror1 = -1; + static int LastTerror2 = -1; + static int LastTerror3 = -1; + static bool LastOptedIn = false; + static bool LastSaboteur = false; + + static bool IsOptedIn = false; + public static TerrorMatrix TMatrix = TerrorMatrix.Empty; + + internal static void SetTerrorMatrix(TerrorMatrix terrorMatrix) { + TMatrix = terrorMatrix; + IsDirty = true; + } + + internal static void SetOptInStatus(bool optedIn) { + IsOptedIn = optedIn; + IsDirty = true; + } + + internal static void SendData(bool force = false) { + if (!Settings.Get.OSCEnabled) return; + + if ((MainWindow.Started && IsDirty) || force) { + IsDirty = false; + + int value = (int)TMatrix.RoundType; + if (LastRoundType != value || force) SendParam(ParamRoundType, LastRoundType = value); + + value = TMatrix.Terror1; + if (LastTerror1 != value || force) SendParam(ParamTerror1, LastTerror1 = value); + + value = TMatrix.Terror2; + if (LastTerror2 != value || force) SendParam(ParamTerror2, LastTerror2 = value); + + value = TMatrix.Terror3; + if (LastTerror3 != value || force) SendParam(ParamTerror3, LastTerror3 = value); + + if (LastSaboteur != TMatrix.IsSaboteour || force) SendParam(ParamSaboteur, LastSaboteur = TMatrix.IsSaboteour); + + if (LastOptedIn != IsOptedIn || force) SendParam(ParamOptedIn, LastOptedIn = IsOptedIn); + } + } + + static readonly byte[] temp_buffer = new byte[2048]; + + private static void EncodeInto(byte[] data, ref int offset, string path, int value) { + byte[] tmp = Encoding.UTF8.GetBytes(path); + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + + tmp = new byte[] { 44, 105 }; // ",i" + + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + + Array.Copy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(value)), 0, data, offset, 4); + offset += 4; + } + + private static void EncodeInto(byte[] data, ref int offset, string path, bool value) { + byte[] tmp = Encoding.UTF8.GetBytes(path); + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + + tmp = new byte[] { 44, (byte)(value ? 'T' : 'F') }; // ",T" + + Array.Copy(tmp, 0, data, offset, tmp.Length); + data[tmp.Length + offset] = 0; + offset += tmp.Length; + for (int endOffset = (offset + 4) & ~3; offset < endOffset; offset++) { + data[offset] = 0; + } + } + + private static void SendParam(string name, int value) { + int encodedLength = 0; + EncodeInto(temp_buffer, ref encodedLength, "/avatar/parameters/" + name, value); + SendBuffer(temp_buffer, encodedLength); + } + + private static void SendParam(string name, bool value) { + int encodedLength = 0; + EncodeInto(temp_buffer, ref encodedLength, "/avatar/parameters/" + name, value); + SendBuffer(temp_buffer, encodedLength); + } + + private static void SendBuffer(byte[] buffer, int encodedLength, IPAddress? ipAddress = null, int port = 9000) { + if (UdpClient == null) UdpClient = new UdpClient(); + UdpClient.Send(buffer, encodedLength, (ipAddress ?? IPAddress.Loopback).ToString(), port); + } + } +} diff --git a/Utils/LogWatcher.cs b/Utils/LogWatcher.cs index 61ce6b2..8e489cd 100644 --- a/Utils/LogWatcher.cs +++ b/Utils/LogWatcher.cs @@ -98,6 +98,7 @@ private void LogTick(object? sender, EventArgs? e) continue; } + logContext.IsRecent = !isOlder; logContext.Length = fileInfo.Length; ParseLog(fileInfo, logContext, sender == null); @@ -291,6 +292,8 @@ private bool ParseUdonException(string line, DateTime lineTime, LogContext logCo public class LogContext { + public bool IsRecent; + public long Length; public long Position; public bool Initialized { get; private set; } diff --git a/Windows/MainWindow.cs b/Windows/MainWindow.cs index 7b033e2..6713ec2 100644 --- a/Windows/MainWindow.cs +++ b/Windows/MainWindow.cs @@ -18,7 +18,7 @@ public partial class MainWindow : Form // internal static readonly AppSettings Settings = AppSettings.Import(); internal static readonly SaveData SaveData = SaveData.Import(); internal static MainWindow? Instance; - private static bool Started; + internal static bool Started; public MainWindow() { @@ -78,6 +78,8 @@ private void mainWindow_Shown(object sender, EventArgs e) Started = true; SetTitle(null); + + LilOSC.SendData(true); } #endregion @@ -505,9 +507,13 @@ internal void SetBackupButton(bool enabled) const string ROUND_WON_KEYWORD = "Player Won"; const string ROUND_LOST_KEYWORD = "Player lost,"; + const string ROUND_IS_SABO_KEY = "rSabo"; + const string ROUND_IS_SABO = "You are the sussy baka of cringe naenae legend"; + const string KILLER_MATRIX_KEYWORD = "Killers have been set - "; const string KILLER_ROUND_TYPE_KEYWORD = " // Round type is "; + private void LogWatcher_OnLine(object? sender, OnLineArgs e) { DateTime timestamp = e.Timestamp; @@ -522,6 +528,7 @@ private void LogWatcher_OnTick(object? sender, EventArgs e) { CopyRecent(); Export(); + LilOSC.SendData(); } private bool HandleSaveCode(string line, DateTime timestamp, LogContext context) @@ -566,6 +573,12 @@ private bool HandleTerrorIndex(string line, DateTime timestamp, LogContext conte { context.Rem(ROUND_KILLERS_KEY); context.Rem(ROUND_RESULT_KEY); + context.Rem(ROUND_IS_SABO_KEY); + } + + if (context.IsRecent) { + LilOSC.SetTerrorMatrix(TerrorMatrix.Empty); + LilOSC.SetOptInStatus(isOptedIn); } return true; } @@ -576,11 +589,19 @@ private bool HandleTerrorIndex(string line, DateTime timestamp, LogContext conte if (!isOptedIn) return false; + if (line.StartsWith(ROUND_IS_SABO)) { + context.Set(ROUND_IS_SABO_KEY, true); + return true; + } + // Track round participation results isOptedIn = line.StartsWith(ROUND_WON_KEYWORD); if (isOptedIn || line.StartsWith(ROUND_LOST_KEYWORD)) { context.Set(ROUND_RESULT_KEY, isOptedIn ? ToNRoundResult.W : ToNRoundResult.D); + context.Rem(ROUND_IS_SABO_KEY); + + if (context.IsRecent) LilOSC.SetTerrorMatrix(TerrorMatrix.Empty); return true; } @@ -592,7 +613,7 @@ private bool HandleTerrorIndex(string line, DateTime timestamp, LogContext conte string roundType = line.Substring(rndInd + KILLER_ROUND_TYPE_KEYWORD.Length).Trim(); string[] kMatrixRaw = line.Substring(index, rndInd - index).Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - int[] killerMatrix = new int[kMatrixRaw.Length]; + int[] killerMatrix = new int[3]; for (int i = 0; i < kMatrixRaw.Length; i++) { @@ -600,7 +621,12 @@ private bool HandleTerrorIndex(string line, DateTime timestamp, LogContext conte } TerrorMatrix terrorMatrix = new TerrorMatrix(roundType, killerMatrix); + terrorMatrix.IsSaboteour = context.Get(ROUND_IS_SABO_KEY); + context.Set(ROUND_KILLERS_KEY, terrorMatrix); + context.Rem(ROUND_IS_SABO_KEY); + + if (context.IsRecent) LilOSC.SetTerrorMatrix(terrorMatrix); return true; } @@ -636,7 +662,7 @@ private void FirstImport() if (first != null) SetRecent(first); } - CopyRecent(); + // CopyRecent(); } private void AddCustomEntry(Entry entry, History? collection) diff --git a/Windows/SettingsWindow.Designer.cs b/Windows/SettingsWindow.Designer.cs index ca6f762..e397e8e 100644 --- a/Windows/SettingsWindow.Designer.cs +++ b/Windows/SettingsWindow.Designer.cs @@ -26,10 +26,10 @@ protected override void Dispose(bool disposing) /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// - private void InitializeComponent() - { + private void InitializeComponent() { components = new System.ComponentModel.Container(); groupBoxGeneral = new GroupBox(); + checkOSCEnabled = new CheckBox(); checkDiscordBackup = new CheckBox(); checkShowWinLose = new CheckBox(); checkSaveTerrorsNote = new CheckBox(); @@ -65,6 +65,7 @@ private void InitializeComponent() // groupBoxGeneral.AutoSize = true; groupBoxGeneral.AutoSizeMode = AutoSizeMode.GrowAndShrink; + groupBoxGeneral.Controls.Add(checkOSCEnabled); groupBoxGeneral.Controls.Add(checkDiscordBackup); groupBoxGeneral.Controls.Add(checkShowWinLose); groupBoxGeneral.Controls.Add(checkSaveTerrorsNote); @@ -76,11 +77,24 @@ private void InitializeComponent() groupBoxGeneral.ForeColor = Color.White; groupBoxGeneral.Location = new Point(8, 8); groupBoxGeneral.Name = "groupBoxGeneral"; - groupBoxGeneral.Size = new Size(268, 148); + groupBoxGeneral.Size = new Size(268, 166); groupBoxGeneral.TabIndex = 0; groupBoxGeneral.TabStop = false; groupBoxGeneral.Text = "General"; // + // checkOSCEnabled + // + checkOSCEnabled.Dock = DockStyle.Top; + checkOSCEnabled.Location = new Point(3, 145); + checkOSCEnabled.Name = "checkOSCEnabled"; + checkOSCEnabled.Padding = new Padding(3, 0, 3, 0); + checkOSCEnabled.Size = new Size(262, 18); + checkOSCEnabled.TabIndex = 7; + checkOSCEnabled.Tag = "OSCEnabled|Sends avatar parameters to VRChat using OSC. Right click this entry to open documentation about parameter names and types."; + checkOSCEnabled.Text = "Send OSC Parameters"; + checkOSCEnabled.UseVisualStyleBackColor = true; + checkOSCEnabled.MouseUp += checkOSCEnabled_MouseUp; + // // checkDiscordBackup // checkDiscordBackup.Dock = DockStyle.Top; @@ -175,7 +189,7 @@ private void InitializeComponent() groupBoxNotifications.Controls.Add(checkXSOverlay); groupBoxNotifications.Dock = DockStyle.Top; groupBoxNotifications.ForeColor = Color.White; - groupBoxNotifications.Location = new Point(8, 156); + groupBoxNotifications.Location = new Point(8, 174); groupBoxNotifications.Name = "groupBoxNotifications"; groupBoxNotifications.Size = new Size(268, 58); groupBoxNotifications.TabIndex = 2; @@ -219,7 +233,7 @@ private void InitializeComponent() groupBox1.Controls.Add(check24Hour); groupBox1.Dock = DockStyle.Top; groupBox1.ForeColor = Color.White; - groupBox1.Location = new Point(8, 214); + groupBox1.Location = new Point(8, 232); groupBox1.Name = "groupBox1"; groupBox1.Size = new Size(268, 94); groupBox1.TabIndex = 3; @@ -280,7 +294,7 @@ private void InitializeComponent() btnCheckForUpdates.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); btnCheckForUpdates.FlatStyle = FlatStyle.Flat; btnCheckForUpdates.ForeColor = Color.White; - btnCheckForUpdates.Location = new Point(8, 356); + btnCheckForUpdates.Location = new Point(8, 373); btnCheckForUpdates.Name = "btnCheckForUpdates"; btnCheckForUpdates.Size = new Size(209, 24); btnCheckForUpdates.TabIndex = 4; @@ -295,7 +309,7 @@ private void InitializeComponent() btnOpenData.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); btnOpenData.FlatStyle = FlatStyle.Flat; btnOpenData.ForeColor = Color.White; - btnOpenData.Location = new Point(223, 356); + btnOpenData.Location = new Point(223, 373); btnOpenData.Name = "btnOpenData"; btnOpenData.Size = new Size(53, 24); btnOpenData.TabIndex = 5; @@ -344,7 +358,7 @@ private void InitializeComponent() groupBox2.Controls.Add(checkColorObjectives); groupBox2.Dock = DockStyle.Top; groupBox2.ForeColor = Color.White; - groupBox2.Location = new Point(8, 308); + groupBox2.Location = new Point(8, 326); groupBox2.Name = "groupBox2"; groupBox2.Size = new Size(268, 40); groupBox2.TabIndex = 6; @@ -370,7 +384,7 @@ private void InitializeComponent() AutoSize = true; AutoSizeMode = AutoSizeMode.GrowAndShrink; BackColor = Color.FromArgb(46, 52, 64); - ClientSize = new Size(284, 388); + ClientSize = new Size(284, 405); Controls.Add(groupBox2); Controls.Add(btnOpenData); Controls.Add(btnCheckForUpdates); @@ -425,5 +439,6 @@ private void InitializeComponent() private ToolStripMenuItem ctxItemPickFolder; private ToolStripMenuItem ctxItemResetToDefault; private CheckBox checkDiscordBackup; + private CheckBox checkOSCEnabled; } } \ No newline at end of file diff --git a/Windows/SettingsWindow.cs b/Windows/SettingsWindow.cs index 27a637e..cbc3d19 100644 --- a/Windows/SettingsWindow.cs +++ b/Windows/SettingsWindow.cs @@ -1,31 +1,28 @@ using System.Diagnostics; using ToNSaveManager.Extensions; using ToNSaveManager.Models; +using ToNSaveManager.Utils; using ToNSaveManager.Utils.Discord; using Timer = System.Windows.Forms.Timer; namespace ToNSaveManager.Windows { - public partial class SettingsWindow : Form - { + public partial class SettingsWindow : Form { #region Initialization static SettingsWindow? Instance; readonly Timer ClickTimer = new Timer() { Interval = 200 }; readonly Stopwatch Stopwatch = new Stopwatch(); - public SettingsWindow() - { + public SettingsWindow() { InitializeComponent(); ClickTimer.Tick += ClickTimer_Tick; } - public static void Open(Form parent) - { + public static void Open(Form parent) { if (Instance == null || Instance.IsDisposed) Instance = new(); - if (Instance.Visible) - { + if (Instance.Visible) { Instance.BringToFront(); return; } @@ -41,8 +38,7 @@ public static void Open(Form parent) #region Form Events // Subscribe to events on load - private void SettingsWindow_Load(object sender, EventArgs e) - { + private void SettingsWindow_Load(object sender, EventArgs e) { BindControlsRecursive(Controls); // Custom audio handling PostAudioLocationSet(); @@ -66,10 +62,12 @@ private void SettingsWindow_Load(object sender, EventArgs e) // Discord Backups checkDiscordBackup.CheckedChanged += CheckDiscordBackup_CheckedChanged; + + // OSC + checkOSCEnabled.CheckedChanged += checkOSCEnabled_CheckedChanged; } - private void SettingsWindow_FormClosed(object sender, FormClosedEventArgs e) - { + private void SettingsWindow_FormClosed(object sender, FormClosedEventArgs e) { ClickTimer.Dispose(); MainWindow.RefreshLists(); @@ -78,27 +76,22 @@ private void SettingsWindow_FormClosed(object sender, FormClosedEventArgs e) private void TimeFormat_CheckedChanged(object? sender, EventArgs e) => MainWindow.RefreshLists(); private void CheckXSOverlay_CheckedChanged(object? sender, EventArgs e) => MainWindow.SendXSNotification(true); - private void CheckPlayAudio_CheckedChanged(object? sender, EventArgs e) - { + private void CheckPlayAudio_CheckedChanged(object? sender, EventArgs e) { MainWindow.PlayNotification(); if (!checkPlayAudio.Checked) MainWindow.ResetNotification(); } - private void checkSaveTerrors_CheckedChanged(object? sender, EventArgs e) - { + private void checkSaveTerrors_CheckedChanged(object? sender, EventArgs e) { checkSaveTerrorsNote.ForeColor = checkSaveTerrors.Checked ? Color.White : Color.Gray; checkShowWinLose.ForeColor = checkSaveTerrorsNote.ForeColor; TimeFormat_CheckedChanged(sender, e); } - private void CheckDiscordBackup_CheckedChanged(object? sender, EventArgs e) - { - if (checkDiscordBackup.Checked) - { + private void CheckDiscordBackup_CheckedChanged(object? sender, EventArgs e) { + if (checkDiscordBackup.Checked) { string url = Settings.Get.DiscordWebhookURL ?? string.Empty; EditResult edit = EditWindow.Show(Settings.Get.DiscordWebhookURL ?? string.Empty, "Discord Webhook URL", this); - if (edit.Accept && !edit.Text.Equals(url, StringComparison.Ordinal)) - { + if (edit.Accept && !edit.Text.Equals(url, StringComparison.Ordinal)) { url = edit.Text.Trim(); if (!string.IsNullOrWhiteSpace(url)) { @@ -121,27 +114,22 @@ private void CheckDiscordBackup_CheckedChanged(object? sender, EventArgs e) MainWindow.Instance?.SetBackupButton(checkDiscordBackup.Checked); } - private void btnCheckForUpdates_Click(object sender, EventArgs e) - { + private void btnCheckForUpdates_Click(object sender, EventArgs e) { if (Program.StartCheckForUpdate(true)) this.Close(); } - private void btnOpenData_Click(object sender, EventArgs e) - { + private void btnOpenData_Click(object sender, EventArgs e) { SaveData.OpenDataLocation(); } - private void checkPlayAudio_MouseDown(object sender, MouseEventArgs e) - { + private void checkPlayAudio_MouseDown(object sender, MouseEventArgs e) { if (e.Button != MouseButtons.Left) return; Stopwatch.Start(); CancelNext = false; } - private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) - { - if (e.Button == MouseButtons.Right && !string.IsNullOrEmpty(Settings.Get.AudioLocation)) - { + private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) { + if (e.Button == MouseButtons.Right && !string.IsNullOrEmpty(Settings.Get.AudioLocation)) { Settings.Get.AudioLocation = null; Settings.Export(); PostAudioLocationSet(); @@ -154,20 +142,17 @@ private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) long elapsed = Stopwatch.ElapsedMilliseconds; Stopwatch.Reset(); - if (CancelNext) - { + if (CancelNext) { CancelNext = false; return; } - if (elapsed > 210) - { + if (elapsed > 210) { TogglePlayAudio(); return; } - if (!DoubleClickCheck) - { + if (!DoubleClickCheck) { DoubleClickCheck = true; ClickTimer.Stop(); ClickTimer.Start(); @@ -177,14 +162,12 @@ private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) DoubleClickCheck = false; ClickTimer.Stop(); - using (OpenFileDialog fileDialog = new OpenFileDialog()) - { + using (OpenFileDialog fileDialog = new OpenFileDialog()) { fileDialog.InitialDirectory = "./"; fileDialog.Title = "Select Custom Sound"; fileDialog.Filter = "Waveform Audio (*.wav)|*.wav"; - if (fileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(fileDialog.FileName)) - { + if (fileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(fileDialog.FileName)) { Settings.Get.AudioLocation = fileDialog.FileName; Settings.Export(); PostAudioLocationSet(); @@ -192,29 +175,33 @@ private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) } } - private void ctxItemPickFolder_Click(object sender, EventArgs e) - { + private void checkOSCEnabled_CheckedChanged(object? sender, EventArgs e) { + if (checkOSCEnabled.Checked) LilOSC.SendData(true); + } + + private void checkOSCEnabled_MouseUp(object sender, MouseEventArgs e) { + if (e.Button == MouseButtons.Right) + MainWindow.OpenExternalLink("https://github.com/ChrisFeline/ToNSaveManager/?tab=readme-ov-file#osc-documentation"); + } + + private void ctxItemPickFolder_Click(object sender, EventArgs e) { SaveData.SetDataLocation(false); } - private void ctxItemResetToDefault_Click(object sender, EventArgs e) - { + private void ctxItemResetToDefault_Click(object sender, EventArgs e) { SaveData.SetDataLocation(true); } - private void CheckColorObjectives_CheckedChanged(object? sender, EventArgs e) - { + private void CheckColorObjectives_CheckedChanged(object? sender, EventArgs e) { ObjectivesWindow.RefreshLists(); } // Double click private bool DoubleClickCheck = false; private bool CancelNext = false; - private void ClickTimer_Tick(object? sender, EventArgs e) - { + private void ClickTimer_Tick(object? sender, EventArgs e) { ClickTimer.Stop(); - if (DoubleClickCheck) - { + if (DoubleClickCheck) { DoubleClickCheck = false; CancelNext = true; TogglePlayAudio(); @@ -223,29 +210,23 @@ private void ClickTimer_Tick(object? sender, EventArgs e) #endregion #region Utils - private void TogglePlayAudio() - { + private void TogglePlayAudio() { checkPlayAudio.Checked = !checkPlayAudio.Checked; } - private void BindControlsRecursive(Control.ControlCollection controls) - { - foreach (Control c in controls) - { + private void BindControlsRecursive(Control.ControlCollection controls) { + foreach (Control c in controls) { string? tag = c.Tag?.ToString(); - if (!string.IsNullOrEmpty(tag)) - { + if (!string.IsNullOrEmpty(tag)) { int index = tag.IndexOf('|', StringComparison.InvariantCulture); - if (index > -1) - { + if (index > -1) { string tooltip = tag.Substring(index + 1).Replace("\\n", Environment.NewLine, StringComparison.Ordinal); tag = tag.Substring(0, index); toolTip.SetToolTip(c, tooltip); } } - switch (c) - { + switch (c) { case GroupBox g: BindControlsRecursive(g.Controls); break; @@ -258,8 +239,7 @@ private void BindControlsRecursive(Control.ControlCollection controls) } } - private void PostAudioLocationSet() - { + private void PostAudioLocationSet() { bool hasLocation = string.IsNullOrEmpty(Settings.Get.AudioLocation); checkPlayAudio.Text = "Play Audio (" + (hasLocation ? "default.wav" : Path.GetFileName(Settings.Get.AudioLocation)) + ")"; } @@ -292,6 +272,5 @@ private void WriteInstanceLogs() } */ #endregion - } }