Skip to content

Commit

Permalink
Merge branch 'feature/chatbox-improvements' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
LucHeart committed Jun 4, 2024
2 parents 8c5f7e6 + e084de3 commit 6fa72ed
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 180 deletions.
27 changes: 5 additions & 22 deletions ShockOsc/Backend/BackendHubManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public sealed class BackendHubManager
private readonly ILogger<BackendHubManager> _logger;
private readonly ConfigManager _configManager;
private readonly OpenShockHubClient _openShockHubClient;
private readonly OscClient _oscClient;
private readonly ChatboxService _chatboxService;
private readonly ShockOscData _dataLayer;
private readonly OscHandler _oscHandler;

Expand All @@ -27,14 +27,14 @@ public sealed class BackendHubManager
public BackendHubManager(ILogger<BackendHubManager> logger,
ConfigManager configManager,
OpenShockHubClient openShockHubClient,
OscClient oscClient,
ChatboxService chatboxService,
ShockOscData dataLayer,
OscHandler oscHandler)
{
_logger = logger;
_configManager = configManager;
_openShockHubClient = openShockHubClient;
_oscClient = oscClient;
_chatboxService = chatboxService;
_dataLayer = dataLayer;
_oscHandler = oscHandler;

Expand Down Expand Up @@ -140,25 +140,8 @@ private async Task RemoteActivateShocker(ControlLogSender sender, ControlLog log
"Received remote {Type} for \"{ShockerName}\" at {Intensity}%:{Duration}s by {SenderCustomName} [{Sender}]",
log.Type, log.Shocker.Name, log.Intensity, inSeconds, sender.CustomName, sender.Name);

var template = _configManager.Config.Chatbox.Types[log.Type];
if (_configManager.Config.Chatbox.Enabled &&
_configManager.Config.Chatbox.DisplayRemoteControl && template.Enabled)
{
// Chatbox message remote
var dat = new
{
ShockerName = log.Shocker.Name,
Intensity = log.Intensity,
Duration = log.Duration,
DurationSeconds = inSeconds,
Name = sender.Name,
CustomName = sender.CustomName
};

var msg =
$"{_configManager.Config.Chatbox.Prefix}{Smart.Format(sender.CustomName == null ? template.Remote : template.RemoteWithCustomName, dat)}";
await _oscClient.SendChatboxMessage(msg);
}
await _chatboxService.SendRemoteControlMessage(log.Shocker.Name, sender.Name, sender.CustomName, log.Intensity,
log.Duration, log.Type);

var configGroupsAffected = _configManager.Config.Groups
.Where(s => s.Value.Shockers.Any(x => x == log.Shocker.Id)).Select(x => x.Key).ToArray();
Expand Down
1 change: 0 additions & 1 deletion ShockOsc/Config/BehaviourConf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public sealed class BehaviourConf
{
public bool RandomIntensity { get; set; }
public bool RandomDuration { get; set; }
public uint RandomDurationStep { get; set; } = 1000;
public JsonRange DurationRange { get; set; } = new JsonRange { Min = 1000, Max = 5000 };
public JsonRange IntensityRange { get; set; } = new JsonRange { Min = 1, Max = 30 };
public byte FixedIntensity { get; set; } = 50;
Expand Down
15 changes: 13 additions & 2 deletions ShockOsc/Config/ChatboxConf.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
using OpenShock.SDK.CSharp.Models;
using System.Text.Json.Serialization;
using OpenShock.SDK.CSharp.Models;

namespace OpenShock.ShockOsc.Config;

public sealed class ChatboxConf
{
public bool Enabled { get; set; } = true;
public string Prefix { get; set; } = "[ShockOsc] ";
public string Prefix { get; set; } = "[ShockOSC] ";
public bool DisplayRemoteControl { get; set; } = true;

public bool TimeoutEnabled { get; set; } = true;
public uint Timeout { get; set; } = 5000;

[JsonIgnore]
public TimeSpan TimeoutTimeSpan
{
get => TimeSpan.FromMilliseconds(Timeout);
set => Timeout = (uint)value.TotalMilliseconds;
}

public HoscyMessageType HoscyType { get; set; } = HoscyMessageType.Message;

public string IgnoredKillSwitchActive { get; set; } = "Ignoring Shock, kill switch is active";
Expand Down
1 change: 0 additions & 1 deletion ShockOsc/Config/Group.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public sealed class Group

public bool OverrideDuration { get; set; }
public bool RandomDuration { get; set; }
public uint RandomDurationStep { get; set; } = 1000;
public JsonRange DurationRange { get; set; } = new JsonRange { Min = 1000, Max = 5000 };
public uint FixedDuration { get; set; } = 2000;

Expand Down
145 changes: 145 additions & 0 deletions ShockOsc/Services/ChatboxService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Threading.Channels;
using OpenShock.SDK.CSharp.Models;
using OpenShock.ShockOsc.Config;
using OpenShock.ShockOsc.Utils;
using SmartFormat;
using Timer = System.Timers.Timer;

namespace OpenShock.ShockOsc.Services;

/// <summary>
/// Handle chatbox interactions and behaviour
/// </summary>
public sealed class ChatboxService : IAsyncDisposable
{
private readonly ConfigManager _configManager;
private readonly OscClient _oscClient;
private readonly ILogger<ChatboxService> _logger;
private readonly System.Threading.Timer _clearTimer;

private readonly CancellationTokenSource _cts = new();

private readonly Channel<Message> _messageChannel = Channel.CreateBounded<Message>(new BoundedChannelOptions(4)
{
SingleReader = true,
FullMode = BoundedChannelFullMode.DropOldest
});

public ChatboxService(ConfigManager configManager, OscClient oscClient, ILogger<ChatboxService> logger)
{
_configManager = configManager;
_oscClient = oscClient;
_logger = logger;

_clearTimer = new System.Threading.Timer(ClearChatbox);

OsTask.Run(MessageLoop);
}

private async void ClearChatbox(object? state)
{
try
{
await _oscClient.SendChatboxMessage(string.Empty);
_logger.LogTrace("Cleared chatbox");
}
catch (Exception e)
{
_logger.LogError(e, "Failed to send clear chatbox");
}
}

public async ValueTask SendLocalControlMessage(string name, byte intensity, uint duration, ControlType type)
{
if (!_configManager.Config.Chatbox.Enabled) return;

if (!_configManager.Config.Chatbox.Types.TryGetValue(type, out var template))
{
_logger.LogError("No message template found for control type {ControlType}", type);
return;
}

if (!template.Enabled) return;

// Chatbox message local
var dat = new
{
GroupName = name,
ShockerName = name,
Intensity = intensity,
Duration = duration,
DurationSeconds = duration.DurationInSecondsString()
};

var msg = $"{_configManager.Config.Chatbox.Prefix}{Smart.Format(template.Local, dat)}";

await _messageChannel.Writer.WriteAsync(new Message(msg, _configManager.Config.Chatbox.TimeoutTimeSpan));
}

public async ValueTask SendRemoteControlMessage(string shockerName, string senderName, string? customName,
byte intensity, uint duration, ControlType type)
{
if (!_configManager.Config.Chatbox.Enabled || !_configManager.Config.Chatbox.DisplayRemoteControl) return;

if (!_configManager.Config.Chatbox.Types.TryGetValue(type, out var template))
{
_logger.LogError("No message template found for control type {ControlType}", type);
return;
}

if (!template.Enabled) return;

// Chatbox message remote
var dat = new
{
ShockerName = shockerName,
Intensity = intensity,
Duration = duration,
DurationSeconds = duration.DurationInSecondsString(),
Name = senderName,
CustomName = customName
};

var templateToUse = customName == null ? template.Remote : template.RemoteWithCustomName;

var msg = $"{_configManager.Config.Chatbox.Prefix}{Smart.Format(templateToUse, dat)}";

await _messageChannel.Writer.WriteAsync(new Message(msg, _configManager.Config.Chatbox.TimeoutTimeSpan));
}

private async Task MessageLoop()
{
await foreach (var message in _messageChannel.Reader.ReadAllAsync())
{
await _oscClient.SendChatboxMessage(message.Text);

if(_configManager.Config.Osc.Hoscy) continue;
// We dont need to worry about timeouts if we're using hoscy
if(_configManager.Config.Chatbox.TimeoutEnabled) _clearTimer.Change(message.Timeout, Timeout.InfiniteTimeSpan);
await Task.Delay(1250); // VRChat chatbox rate limit
}
}

private bool _disposed;

public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;

await _clearTimer.DisposeAsync();

await _cts.CancelAsync();
_cts.Dispose();

GC.SuppressFinalize(this);
}

~ChatboxService()
{
if (_disposed) return;
DisposeAsync().AsTask().Wait();
}
}

public record Message(string Text, TimeSpan Timeout);
7 changes: 3 additions & 4 deletions ShockOsc/Services/OscClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,14 @@ public void CreateGameConnection(IPAddress ipAddress, ushort receivePort, ushort

public ValueTask SendGameMessage(string address, params object?[]?arguments)
{
arguments ??= Array.Empty<object>();
arguments ??= [];
return _gameSenderChannel.Writer.WriteAsync(new OscMessage(address, arguments));
}

public ValueTask SendChatboxMessage(string message)
{
if(!_configManager.Config.Chatbox.Enabled) return ValueTask.CompletedTask;
if (_configManager.Config.Osc.Hoscy) return _hoscySenderChannel.Writer.WriteAsync(new OscMessage("/hoscy/message", message));

if (_configManager.Config.Osc.Hoscy) return _hoscySenderChannel.Writer.WriteAsync(new OscMessage(
$"/hoscy/{_configManager.Config.Chatbox.HoscyType.ToString().ToLowerInvariant()}", message));
return _gameSenderChannel.Writer.WriteAsync(new OscMessage("/chatbox/input", message, true));
}

Expand Down
Loading

0 comments on commit 6fa72ed

Please sign in to comment.