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
-
}
}