mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-17 02:31:28 -07:00
Revamp Bloxstrap game messaging, now BloxstrapRPC
support for nested and dynamically-typed json fields, and games now have greater liberty over how the rich presence can be configured
This commit is contained in:
parent
cb4c813b8a
commit
c8c49a2921
@ -17,6 +17,7 @@ global using System.Threading.Tasks;
|
||||
global using Bloxstrap.Enums;
|
||||
global using Bloxstrap.Extensions;
|
||||
global using Bloxstrap.Models;
|
||||
global using Bloxstrap.Models.BloxstrapRPC;
|
||||
global using Bloxstrap.Models.Attributes;
|
||||
global using Bloxstrap.Models.RobloxApi;
|
||||
global using Bloxstrap.UI;
|
||||
|
@ -11,7 +11,7 @@
|
||||
private const string GameJoinedEntry = "[FLog::Network] serverId:";
|
||||
private const string GameDisconnectedEntry = "[FLog::Network] Time to disconnect replication data:";
|
||||
private const string GameTeleportingEntry = "[FLog::SingleSurfaceApp] initiateTeleport";
|
||||
private const string GameMessageEntry = "[FLog::Output] [SendBloxstrapMessage]";
|
||||
private const string GameMessageEntry = "[FLog::Output] [BloxstrapRPC]";
|
||||
|
||||
private const string GameJoiningEntryPattern = @"! Joining game '([0-9a-f\-]{36})' place ([0-9]+) at ([0-9\.]+)";
|
||||
private const string GameJoiningUDMUXPattern = @"UDMUX Address = ([0-9\.]+), Port = [0-9]+ \| RCC Server Address = ([0-9\.]+), Port = [0-9]+";
|
||||
@ -24,7 +24,7 @@
|
||||
public event EventHandler<string>? OnLogEntry;
|
||||
public event EventHandler? OnGameJoin;
|
||||
public event EventHandler? OnGameLeave;
|
||||
public event EventHandler<GameMessage>? OnGameMessage;
|
||||
public event EventHandler<Message>? OnRPCMessage;
|
||||
|
||||
private Dictionary<string, string> GeolocationCache = new();
|
||||
|
||||
@ -233,13 +233,13 @@
|
||||
else if (entry.Contains(GameMessageEntry))
|
||||
{
|
||||
string messagePlain = entry.Substring(entry.IndexOf(GameMessageEntry) + GameMessageEntry.Length + 1);
|
||||
GameMessage? message;
|
||||
Message? message;
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Received message: '{messagePlain}'");
|
||||
|
||||
try
|
||||
{
|
||||
message = JsonSerializer.Deserialize<GameMessage>(messagePlain);
|
||||
message = JsonSerializer.Deserialize<Message>(messagePlain);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@ -259,7 +259,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
OnGameMessage?.Invoke(this, message);
|
||||
OnRPCMessage?.Invoke(this, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,21 +7,20 @@ namespace Bloxstrap.Integrations
|
||||
private readonly DiscordRpcClient _rpcClient = new("1005469189907173486");
|
||||
private readonly ActivityWatcher _activityWatcher;
|
||||
|
||||
private RichPresence? _currentPresence;
|
||||
private DiscordRPC.RichPresence? _currentPresence;
|
||||
private bool _visible = true;
|
||||
private string? _initialStatus;
|
||||
private long _currentUniverseId;
|
||||
private DateTime? _timeStartedUniverse;
|
||||
|
||||
public DiscordRichPresence(ActivityWatcher activityWatcher)
|
||||
{
|
||||
const string LOG_IDENT = "DiscordRichPresence::<construct>";
|
||||
const string LOG_IDENT = "DiscordRichPresence::DiscordRichPresence";
|
||||
|
||||
_activityWatcher = activityWatcher;
|
||||
|
||||
_activityWatcher.OnGameJoin += (_, _) => Task.Run(() => SetCurrentGame());
|
||||
_activityWatcher.OnGameLeave += (_, _) => Task.Run(() => SetCurrentGame());
|
||||
_activityWatcher.OnGameMessage += (_, message) => OnGameMessage(message);
|
||||
_activityWatcher.OnRPCMessage += (_, message) => ProcessRPCMessage(message);
|
||||
|
||||
_rpcClient.OnReady += (_, e) =>
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Received ready from user {e.User} ({e.User.ID})");
|
||||
@ -45,71 +44,12 @@ namespace Bloxstrap.Integrations
|
||||
_rpcClient.Initialize();
|
||||
}
|
||||
|
||||
public void OnGameMessage(GameMessage message)
|
||||
public void ProcessRPCMessage(Message message)
|
||||
{
|
||||
switch (message.Command)
|
||||
{
|
||||
case "SetPresenceStatus":
|
||||
SetStatus(message.Data);
|
||||
break;
|
||||
const string LOG_IDENT = "DiscordRichPresence::ProcessRPCMessage";
|
||||
|
||||
case "SetPresenceTimestamp":
|
||||
SetTimestamp(message.Data);
|
||||
break;
|
||||
|
||||
case "SetPresenceIcon":
|
||||
SetIcon(message.Data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#region Game message commands
|
||||
public void SetStatus(string status)
|
||||
{
|
||||
const string LOG_IDENT = "DiscordRichPresence::SetStatus";
|
||||
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Setting status to '{status}'");
|
||||
|
||||
if (_currentPresence is null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Presence is not set, aborting");
|
||||
if (message.Command != "SetRichPresence")
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.Length > 128)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Status cannot be longer than 128 characters, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_initialStatus is null)
|
||||
_initialStatus = _currentPresence.State;
|
||||
|
||||
string finalStatus;
|
||||
|
||||
if (String.IsNullOrEmpty(status))
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Status is empty, reverting to initial status");
|
||||
finalStatus = _initialStatus;
|
||||
}
|
||||
else
|
||||
{
|
||||
finalStatus = status;
|
||||
}
|
||||
|
||||
if (_currentPresence.State == finalStatus)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Status is unchanged, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentPresence.State = finalStatus;
|
||||
UpdatePresence();
|
||||
}
|
||||
|
||||
public void SetTimestamp(string data)
|
||||
{
|
||||
const string LOG_IDENT = "DiscordRichPresence::SetTimestamp";
|
||||
|
||||
if (_currentPresence is null)
|
||||
{
|
||||
@ -117,62 +57,66 @@ namespace Bloxstrap.Integrations
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(data))
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Clearing timestamp");
|
||||
Models.BloxstrapRPC.RichPresence? presence;
|
||||
|
||||
_currentPresence.Timestamps.Start = null;
|
||||
_currentPresence.Timestamps.End = null;
|
||||
try
|
||||
{
|
||||
presence = message.Data.Deserialize<Models.BloxstrapRPC.RichPresence>();
|
||||
}
|
||||
else
|
||||
catch (Exception)
|
||||
{
|
||||
var parameters = data.Split(';');
|
||||
|
||||
if (parameters.Length >= 1 && ulong.TryParse(parameters[0], out ulong startTimestamp))
|
||||
_currentPresence.Timestamps.StartUnixMilliseconds = startTimestamp * 1000;
|
||||
|
||||
if (parameters.Length >= 2 && ulong.TryParse(parameters[1], out ulong endTimestamp))
|
||||
_currentPresence.Timestamps.EndUnixMilliseconds = endTimestamp * 1000;
|
||||
}
|
||||
|
||||
UpdatePresence();
|
||||
}
|
||||
|
||||
public void SetIcon(string data)
|
||||
{
|
||||
const string LOG_IDENT = "DiscordRichPresence::SetIcon";
|
||||
|
||||
if (_currentPresence is null)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Presence is not set, aborting");
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization threw an exception)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(data))
|
||||
if (presence is null)
|
||||
{
|
||||
_currentPresence.Assets.SmallImageKey = "roblox";
|
||||
_currentPresence.Assets.SmallImageText = "Roblox";
|
||||
App.Logger.WriteLine(LOG_IDENT, "Failed to parse message! (JSON deserialization returned null)");
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
if (presence.Details is not null)
|
||||
{
|
||||
var parameters = data.Split(';');
|
||||
if (presence.Details.Length > 128)
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Details cannot be longer than 128 characters");
|
||||
else
|
||||
_currentPresence.Details = presence.Details;
|
||||
}
|
||||
|
||||
if (parameters.Length < 2 || !ulong.TryParse(parameters[0], out ulong assetId))
|
||||
return;
|
||||
if (presence.State is not null)
|
||||
{
|
||||
if (presence.State.Length > 128)
|
||||
App.Logger.WriteLine(LOG_IDENT, $"State cannot be longer than 128 characters");
|
||||
else
|
||||
_currentPresence.State = presence.State;
|
||||
}
|
||||
|
||||
if (parameters[1].Length > 128)
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, $"Icon text cannot be longer than 128 characters, aborting");
|
||||
return;
|
||||
}
|
||||
if (presence.TimestampStart is not null)
|
||||
_currentPresence.Timestamps.StartUnixMilliseconds = presence.TimestampStart * 1000;
|
||||
|
||||
_currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={assetId}";
|
||||
_currentPresence.Assets.SmallImageText = parameters[1];
|
||||
if (presence.TimestampEnd is not null)
|
||||
_currentPresence.Timestamps.EndUnixMilliseconds = presence.TimestampEnd * 1000;
|
||||
|
||||
if (presence.SmallImage is not null)
|
||||
{
|
||||
if (presence.SmallImage.AssetId is not null)
|
||||
_currentPresence.Assets.SmallImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presence.SmallImage.AssetId}";
|
||||
|
||||
if (presence.SmallImage.HoverText is not null)
|
||||
_currentPresence.Assets.SmallImageText = presence.SmallImage.HoverText;
|
||||
}
|
||||
|
||||
if (presence.LargeImage is not null)
|
||||
{
|
||||
if (presence.LargeImage.AssetId is not null)
|
||||
_currentPresence.Assets.LargeImageKey = $"https://assetdelivery.roblox.com/v1/asset/?id={presence.LargeImage.AssetId}";
|
||||
|
||||
if (presence.LargeImage.HoverText is not null)
|
||||
_currentPresence.Assets.LargeImageText = presence.LargeImage.HoverText;
|
||||
}
|
||||
|
||||
UpdatePresence();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void SetVisibility(bool visible)
|
||||
{
|
||||
@ -194,7 +138,6 @@ namespace Bloxstrap.Integrations
|
||||
{
|
||||
App.Logger.WriteLine(LOG_IDENT, "Not in game, clearing presence");
|
||||
_currentPresence = null;
|
||||
_initialStatus = null;
|
||||
UpdatePresence();
|
||||
return true;
|
||||
}
|
||||
@ -271,7 +214,7 @@ namespace Bloxstrap.Integrations
|
||||
_ => $"by {universeDetails.Creator.Name}" + (universeDetails.Creator.HasVerifiedBadge ? " ☑️" : ""),
|
||||
};
|
||||
|
||||
_currentPresence = new RichPresence
|
||||
_currentPresence = new DiscordRPC.RichPresence
|
||||
{
|
||||
Details = $"Playing {universeDetails.Name}",
|
||||
State = status,
|
||||
|
10
Bloxstrap/Models/BloxstrapRPC/Message.cs
Normal file
10
Bloxstrap/Models/BloxstrapRPC/Message.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Bloxstrap.Models.BloxstrapRPC;
|
||||
|
||||
public class Message
|
||||
{
|
||||
[JsonPropertyName("command")]
|
||||
public string Command { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public JsonElement Data { get; set; }
|
||||
}
|
23
Bloxstrap/Models/BloxstrapRPC/RichPresence.cs
Normal file
23
Bloxstrap/Models/BloxstrapRPC/RichPresence.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Bloxstrap.Models.BloxstrapRPC
|
||||
{
|
||||
class RichPresence
|
||||
{
|
||||
[JsonPropertyName("details")]
|
||||
public string? Details { get; set; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public string? State { get; set; }
|
||||
|
||||
[JsonPropertyName("timestampStart")]
|
||||
public ulong? TimestampStart { get; set; }
|
||||
|
||||
[JsonPropertyName("timestampEnd")]
|
||||
public ulong? TimestampEnd { get; set; }
|
||||
|
||||
[JsonPropertyName("smallImage")]
|
||||
public RichPresenceImage? SmallImage { get; set; }
|
||||
|
||||
[JsonPropertyName("largeImage")]
|
||||
public RichPresenceImage? LargeImage { get; set; }
|
||||
}
|
||||
}
|
11
Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs
Normal file
11
Bloxstrap/Models/BloxstrapRPC/RichPresenceImage.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Bloxstrap.Models.BloxstrapRPC
|
||||
{
|
||||
class RichPresenceImage
|
||||
{
|
||||
[JsonPropertyName("assetId")]
|
||||
public ulong? AssetId { get; set; }
|
||||
|
||||
[JsonPropertyName("hoverText")]
|
||||
public string? HoverText { get; set; }
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
namespace Bloxstrap.Models
|
||||
{
|
||||
public class GameMessage
|
||||
{
|
||||
[JsonPropertyName("command")]
|
||||
public string Command { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public string Data { get; set; } = null!;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user