Skip to content

Commit

Permalink
Merge pull request #37 from ChrisFeline/dev
Browse files Browse the repository at this point in the history
OSC Parameters Feature
  • Loading branch information
ChrisFeline authored Jul 21, 2024
2 parents 28f7417 + 31f407b commit 845af39
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 96 deletions.
5 changes: 5 additions & 0 deletions Models/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ static Settings()
/// </summary>
public bool ColorfulObjectives { get; set; } = true;

/// <summary>
/// Enables OSC and sends avatar parameters based on in-game events.
/// </summary>
public bool OSCEnabled { get; set; }

/// <summary>
/// Stores a github release tag if the player clicks no when asking for update.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions Models/Entry.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Diagnostics;
using System.Text;

namespace ToNSaveManager.Models
Expand Down Expand Up @@ -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);
Expand Down
20 changes: 12 additions & 8 deletions Models/ToNIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -141,7 +146,6 @@ static ToNRoundType GetRoundType(string raw)

{ ToNRoundType.RUN, 0xC15E3D },
{ ToNRoundType.Eight_Pages, 0xFFFFFF },
{ ToNRoundType.Custom, 0x000000 }, // Ignored

{ ToNRoundType.Cold_Night, 0xA37BE4 },
};
Expand Down Expand Up @@ -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
}

Expand Down
48 changes: 42 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@
- 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.
- `Play Sound` Play a notification audio when a new save is detected.
- 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.
<details><summary>Preview Image</summary><p> <img src="Resources/settings.png" > </p></details>

### 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.
Expand All @@ -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
<details><summary>Parameter Names & Types</summary><p>
<pre>
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
</pre>
</p></details>

<details><summary>Round Type Values</summary><p>
<pre>
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
</pre>
</p></details>

# 📋 FAQ:

> ## How do I use this?
Expand All @@ -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.
Expand Down
15 changes: 12 additions & 3 deletions Utils/Discord/DSWebHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static class DSWebHook

static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };

static readonly Queue<Entry> EntryQueue = new Queue<Entry>();
static readonly Queue<KeyValuePair<bool, Entry>> EntryQueue = new Queue<KeyValuePair<bool, Entry>>();
static bool IsSending = false;

internal static void Send(Entry entry, bool ignoreDuplicate = false)
Expand All @@ -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<bool, Entry>(ignoreDuplicate, entry));
if (IsSending) return;

_ = Send(webhookUrl);
Expand All @@ -65,7 +65,11 @@ private static async Task Send(string webhookUrl)
{
while (EntryQueue.Count > 0)
{
Entry entry = EntryQueue.Dequeue();
KeyValuePair<bool, Entry> entryData = EntryQueue.Dequeue();

Entry entry = entryData.Value;
bool ignoreDuplicate = entryData.Key;

DateTime time = entry.Timestamp;

EmbedData.Description = string.Empty;
Expand Down Expand Up @@ -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();
Expand Down
125 changes: 125 additions & 0 deletions Utils/LilOSC.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
3 changes: 3 additions & 0 deletions Utils/LogWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private void LogTick(object? sender, EventArgs? e)
continue;
}

logContext.IsRecent = !isOlder;
logContext.Length = fileInfo.Length;
ParseLog(fileInfo, logContext, sender == null);

Expand Down Expand Up @@ -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; }
Expand Down
Loading

0 comments on commit 845af39

Please sign in to comment.