From 9f9ca99dfb25562b15c3e34ac44335a0e2abf860 Mon Sep 17 00:00:00 2001
From: thisguyStan <53304086+thisguyStan@users.noreply.github.com>
Date: Sat, 26 Apr 2025 23:33:11 +0200
Subject: [PATCH 1/3] First idea of how to implement data sharing for plugins
with scores
---
AssettoServer/Server/EventArgs.cs | 10 ++++++++
.../Server/Plugin/PluginDataManager.cs | 23 +++++++++++++++++++
AssettoServer/Startup.cs | 1 +
RaceChallengePlugin/Race.cs | 13 +++++++++--
4 files changed, 45 insertions(+), 2 deletions(-)
create mode 100644 AssettoServer/Server/Plugin/PluginDataManager.cs
diff --git a/AssettoServer/Server/EventArgs.cs b/AssettoServer/Server/EventArgs.cs
index e19b1682..801ed1ed 100644
--- a/AssettoServer/Server/EventArgs.cs
+++ b/AssettoServer/Server/EventArgs.cs
@@ -3,6 +3,7 @@
using System.Numerics;
using System.Text;
using AssettoServer.Network.Tcp;
+using AssettoServer.Server.Plugin;
using AssettoServer.Shared.Network.Packets.Outgoing;
using AssettoServer.Shared.Network.Packets.Outgoing.Handshake;
using AssettoServer.Shared.Network.Packets.Shared;
@@ -146,3 +147,12 @@ public CarListResponseSendingEventArgs(CarListResponse packet)
Packet = packet;
}
}
+
+public class PluginDataEventArgs : EventArgs
+{
+ public required PluginDataType DataType { get; init; }
+ public required string Source { get; init; }
+ public string? Description { get; init; }
+ public int Value { get; init; }
+ public EntryCar? Opponent { get; init; }
+}
diff --git a/AssettoServer/Server/Plugin/PluginDataManager.cs b/AssettoServer/Server/Plugin/PluginDataManager.cs
new file mode 100644
index 00000000..67cfbbe9
--- /dev/null
+++ b/AssettoServer/Server/Plugin/PluginDataManager.cs
@@ -0,0 +1,23 @@
+namespace AssettoServer.Server.Plugin;
+
+public class PluginDataManager
+{
+ ///
+ /// Fires when a shared event is initiated through a plugin.
+ /// e.g. A player gets a new PB time or finishes a race challenge.
+ ///
+ public event EventHandler? SharedData;
+
+ public void SendSharedCarData(EntryCar entryCar, PluginDataEventArgs eventArgs)
+ => SharedData?.Invoke(entryCar, eventArgs);
+}
+
+public enum PluginDataType
+{
+ PointsScored,
+ PointsPersonalBest,
+ TimeCompleted,
+ TimePersonalBest,
+ RatingUpdated,
+ EventWon
+}
diff --git a/AssettoServer/Startup.cs b/AssettoServer/Startup.cs
index 7c2d287c..7e851d17 100644
--- a/AssettoServer/Startup.cs
+++ b/AssettoServer/Startup.cs
@@ -78,6 +78,7 @@ public void ConfigureContainer(ContainerBuilder builder)
builder.RegisterType().AsSelf().SingleInstance();
builder.RegisterType().AsSelf().SingleInstance();
builder.RegisterType().AsSelf().SingleInstance();
+ builder.RegisterType().AsSelf().SingleInstance();
builder.RegisterType().AsSelf().SingleInstance();
builder.RegisterType().As();
builder.RegisterType().AsSelf().SingleInstance();
diff --git a/RaceChallengePlugin/Race.cs b/RaceChallengePlugin/Race.cs
index f1a09986..18328bf5 100644
--- a/RaceChallengePlugin/Race.cs
+++ b/RaceChallengePlugin/Race.cs
@@ -1,7 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using AssettoServer.Server;
-using AssettoServer.Shared.Network.Packets.Shared;
+using AssettoServer.Server.Plugin;
using Serilog;
namespace RaceChallengePlugin;
@@ -24,16 +24,18 @@ public class Race
private readonly SessionManager _sessionManager;
private readonly EntryCarManager _entryCarManager;
private readonly RaceChallengePlugin _plugin;
+ private readonly PluginDataManager _pluginDataManager;
public delegate Race Factory(EntryCar challenger, EntryCar challenged, bool lineUpRequired = true);
- public Race(EntryCar challenger, EntryCar challenged, SessionManager sessionManager, EntryCarManager entryCarManager, RaceChallengePlugin plugin, bool lineUpRequired = true)
+ public Race(EntryCar challenger, EntryCar challenged, SessionManager sessionManager, EntryCarManager entryCarManager, RaceChallengePlugin plugin, PluginDataManager pluginDataManager, bool lineUpRequired = true)
{
Challenger = challenger;
Challenged = challenged;
_sessionManager = sessionManager;
_entryCarManager = entryCarManager;
_plugin = plugin;
+ _pluginDataManager = pluginDataManager;
LineUpRequired = lineUpRequired;
ChallengerName = Challenger.Client?.Name!;
@@ -219,6 +221,13 @@ private void FinishRace()
_entryCarManager.BroadcastChat($"{winnerName} just beat {loserName} in a race.");
Log.Information("{WinnerName} just beat {LoserName} in a race", winnerName, loserName);
+ _pluginDataManager.SendSharedCarData(Leader, new PluginDataEventArgs
+ {
+ Source = GetType().Namespace!,
+ Description = "Race Challenge",
+ DataType = PluginDataType.EventWon,
+ Opponent = Follower
+ });
}
}
From 0e481eadf82b2b1217c4e7132316a36767abe541 Mon Sep 17 00:00:00 2001
From: thisguyStan <53304086+thisguyStan@users.noreply.github.com>
Date: Sat, 28 Jun 2025 15:23:51 +0200
Subject: [PATCH 2/3] Added plugin event to tag mode
---
AssettoServer/Server/EventArgs.cs | 11 +++++-
.../Server/Plugin/PluginDataManager.cs | 15 +++-----
RaceChallengePlugin/Race.cs | 7 ++--
TagModePlugin/EntryCarTagMode.cs | 16 ++++----
TagModePlugin/TagSession.cs | 38 ++++++++++++++++---
5 files changed, 60 insertions(+), 27 deletions(-)
diff --git a/AssettoServer/Server/EventArgs.cs b/AssettoServer/Server/EventArgs.cs
index 801ed1ed..8a303e38 100644
--- a/AssettoServer/Server/EventArgs.cs
+++ b/AssettoServer/Server/EventArgs.cs
@@ -151,8 +151,15 @@ public CarListResponseSendingEventArgs(CarListResponse packet)
public class PluginDataEventArgs : EventArgs
{
public required PluginDataType DataType { get; init; }
- public required string Source { get; init; }
+ public required string Plugin { get; init; }
+ public required string Name { get; init; }
public string? Description { get; init; }
- public int Value { get; init; }
+
+ ///
+ /// Points: self-explanatory
+ /// Time: Time in milliseconds
+ /// EventWin: Can be left as 0
+ ///
+ public long Value { get; init; }
public EntryCar? Opponent { get; init; }
}
diff --git a/AssettoServer/Server/Plugin/PluginDataManager.cs b/AssettoServer/Server/Plugin/PluginDataManager.cs
index 67cfbbe9..7614fd10 100644
--- a/AssettoServer/Server/Plugin/PluginDataManager.cs
+++ b/AssettoServer/Server/Plugin/PluginDataManager.cs
@@ -6,18 +6,15 @@ public class PluginDataManager
/// Fires when a shared event is initiated through a plugin.
/// e.g. A player gets a new PB time or finishes a race challenge.
///
- public event EventHandler? SharedData;
+ public event EventHandler? PluginEvent;
- public void SendSharedCarData(EntryCar entryCar, PluginDataEventArgs eventArgs)
- => SharedData?.Invoke(entryCar, eventArgs);
+ public void SendPluginEvent(EntryCar entryCar, PluginDataEventArgs eventArgs)
+ => PluginEvent?.Invoke(entryCar, eventArgs);
}
public enum PluginDataType
{
- PointsScored,
- PointsPersonalBest,
- TimeCompleted,
- TimePersonalBest,
- RatingUpdated,
- EventWon
+ Points,
+ Time,
+ EventWin
}
diff --git a/RaceChallengePlugin/Race.cs b/RaceChallengePlugin/Race.cs
index 18328bf5..312d9238 100644
--- a/RaceChallengePlugin/Race.cs
+++ b/RaceChallengePlugin/Race.cs
@@ -221,11 +221,12 @@ private void FinishRace()
_entryCarManager.BroadcastChat($"{winnerName} just beat {loserName} in a race.");
Log.Information("{WinnerName} just beat {LoserName} in a race", winnerName, loserName);
- _pluginDataManager.SendSharedCarData(Leader, new PluginDataEventArgs
+ _pluginDataManager.SendPluginEvent(Leader, new PluginDataEventArgs
{
- Source = GetType().Namespace!,
+ Plugin = GetType().Namespace!,
+ Name = GetType().Name,
Description = "Race Challenge",
- DataType = PluginDataType.EventWon,
+ DataType = PluginDataType.EventWin,
Opponent = Follower
});
}
diff --git a/TagModePlugin/EntryCarTagMode.cs b/TagModePlugin/EntryCarTagMode.cs
index 33133dae..4bbae675 100644
--- a/TagModePlugin/EntryCarTagMode.cs
+++ b/TagModePlugin/EntryCarTagMode.cs
@@ -10,15 +10,15 @@ public class EntryCarTagMode
private readonly EntryCarManager _entryCarManager;
private readonly TagModeConfiguration _configuration;
private readonly TagModePlugin _plugin;
- private readonly EntryCar _entryCar;
+ public readonly EntryCar EntryCar;
public bool IsTagged { get; private set; } = false;
- public bool IsConnected => _entryCar.Client is { HasSentFirstUpdate: true };
+ public bool IsConnected => EntryCar.Client is { HasSentFirstUpdate: true };
public Color CurrentColor { get; private set; } = Color.Empty;
public EntryCarTagMode(EntryCar entryCar, EntryCarManager entryCarManager, TagModeConfiguration configuration, TagModePlugin plugin)
{
- _entryCar = entryCar;
+ EntryCar = entryCar;
_entryCarManager = entryCarManager;
_configuration = configuration;
_plugin = plugin;
@@ -57,20 +57,20 @@ public void OnCollision(CollisionEventArgs args)
if (!targetCar.IsTagged) return;
SetTagged();
- _plugin.CurrentSession.LastCaught = _entryCar;
+ _plugin.CurrentSession.LastCaught = EntryCar;
}
public void SetTagged(bool val = true)
{
- if (_entryCar.Client == null) return;
+ if (EntryCar.Client == null) return;
IsTagged = val;
if (!IsTagged) return;
UpdateColor(_plugin.TaggedColor);
- _entryCar.Client.SendChatMessage("You are now a tagger.");
- _entryCar.Logger.Information("{Player} is now a tagger", _entryCar.Client.Name);
+ EntryCar.Client.SendChatMessage("You are now a tagger.");
+ EntryCar.Logger.Information("{Player} is now a tagger", EntryCar.Client.Name);
}
public void UpdateColor(Color color, bool disconnect = false)
@@ -79,7 +79,7 @@ public void UpdateColor(Color color, bool disconnect = false)
var packet = new TagModeColorPacket
{
Color = color,
- SessionId = _entryCar.SessionId,
+ SessionId = EntryCar.SessionId,
Disconnect = disconnect
};
_entryCarManager.BroadcastPacket(packet);
diff --git a/TagModePlugin/TagSession.cs b/TagModePlugin/TagSession.cs
index 1e1addfb..80fbe857 100644
--- a/TagModePlugin/TagSession.cs
+++ b/TagModePlugin/TagSession.cs
@@ -1,6 +1,6 @@
using System.Drawing;
using AssettoServer.Server;
-using Microsoft.Net.Http.Headers;
+using AssettoServer.Server.Plugin;
using Serilog;
namespace TagModePlugin;
@@ -14,7 +14,8 @@ public class TagSession
private bool IsCancelled { get; set; }
public bool HasEnded { get; private set; }
private long StartTimeMilliseconds { get; set; }
-
+
+ private readonly PluginDataManager _pluginDataManager;
private readonly SessionManager _sessionManager;
private readonly EntryCarManager _entryCarManager;
private readonly TagModePlugin _plugin;
@@ -23,12 +24,14 @@ public class TagSession
public delegate TagSession Factory(EntryCar initialTagger);
public TagSession(EntryCar initialTagger,
+ PluginDataManager pluginDataManager,
SessionManager sessionManager,
EntryCarManager entryCarManager,
TagModePlugin plugin,
TagModeConfiguration configuration)
{
InitialTagger = LastCaught = initialTagger;
+ _pluginDataManager = pluginDataManager;
_sessionManager = sessionManager;
_entryCarManager = entryCarManager;
_plugin = plugin;
@@ -105,16 +108,41 @@ private async Task FinishSession()
switch (_configuration.EnableEndlessMode)
{
case false:
- var winners = _plugin.Instances.Any(car => car.Value is { IsTagged: false, IsConnected: true }) ? "Runners" : "Taggers";
+ var winners = _plugin.Instances.Where(car => car.Value is
+ {
+ IsTagged: false,
+ IsConnected: true
+ }).ToDictionary();
+
+ var winnerName = winners.Count != 0 ? "Runners" : "Taggers";
+ _entryCarManager.BroadcastChat($"The {winnerName} just won this game of tag.");
+ Log.Information("The {Winners} just won this game of tag", winnerName);
- _entryCarManager.BroadcastChat($"The {winners} just won this game of tag.");
- Log.Information("The {Winners} just won this game of tag", winners);
+ foreach (var winnerCar in winners.Values)
+ {
+ _pluginDataManager.SendPluginEvent(winnerCar.EntryCar, new PluginDataEventArgs
+ {
+ Plugin = GetType().Namespace!,
+ Name = GetType().Name,
+ Description = "Tag mode time limited",
+ DataType = PluginDataType.EventWin
+ });
+ }
break;
case true:
var winner = LastCaught.Client?.Name ?? $"Car #{LastCaught.SessionId}";
_entryCarManager.BroadcastChat($"'{winner}' just won this game of tag.");
Log.Information("{Winner} just won this game of tag", winner);
+
+
+ _pluginDataManager.SendPluginEvent(LastCaught, new PluginDataEventArgs
+ {
+ Plugin = GetType().Namespace!,
+ Name = GetType().Name,
+ Description = "Tag mode endless",
+ DataType = PluginDataType.EventWin
+ });
break;
}
}
From d9485c50a8e65da89cbca13cd5bedf228a15bafc Mon Sep 17 00:00:00 2001
From: thisguyStan <53304086+thisguyStan@users.noreply.github.com>
Date: Sat, 28 Jun 2025 18:17:24 +0200
Subject: [PATCH 3/3] Added example consumer
---
AssettoServer/Server/EventArgs.cs | 1 -
DiscordAuditPlugin/Discord.cs | 59 +++++++++++++++++++++-
DiscordAuditPlugin/DiscordConfiguration.cs | 2 +
RaceChallengePlugin/Race.cs | 1 -
TagModePlugin/TagSession.cs | 19 +++----
5 files changed, 70 insertions(+), 12 deletions(-)
diff --git a/AssettoServer/Server/EventArgs.cs b/AssettoServer/Server/EventArgs.cs
index 8a303e38..2b10e51c 100644
--- a/AssettoServer/Server/EventArgs.cs
+++ b/AssettoServer/Server/EventArgs.cs
@@ -153,7 +153,6 @@ public class PluginDataEventArgs : EventArgs
public required PluginDataType DataType { get; init; }
public required string Plugin { get; init; }
public required string Name { get; init; }
- public string? Description { get; init; }
///
/// Points: self-explanatory
diff --git a/DiscordAuditPlugin/Discord.cs b/DiscordAuditPlugin/Discord.cs
index 17654d0f..9fb632e7 100644
--- a/DiscordAuditPlugin/Discord.cs
+++ b/DiscordAuditPlugin/Discord.cs
@@ -3,6 +3,7 @@
using AssettoServer.Network.Tcp;
using AssettoServer.Server;
using AssettoServer.Server.Configuration;
+using AssettoServer.Server.Plugin;
using AssettoServer.Shared.Discord;
using AssettoServer.Shared.Network.Packets.Outgoing;
using CSharpDiscordWebhook.NET.Discord;
@@ -18,8 +19,13 @@ public class Discord
private DiscordWebhook? AuditHook { get; }
private DiscordWebhook? ChatHook { get; }
+ private DiscordWebhook? PluginEventHook { get; }
- public Discord(DiscordConfiguration configuration, EntryCarManager entryCarManager, ACServerConfiguration serverConfiguration, ChatService chatService)
+ public Discord(DiscordConfiguration configuration,
+ EntryCarManager entryCarManager,
+ PluginDataManager pluginDataManager,
+ ACServerConfiguration serverConfiguration,
+ ChatService chatService)
{
_serverNameSanitized = DiscordUtils.SanitizeUsername(serverConfiguration.Server.Name);
_configuration = configuration;
@@ -49,6 +55,16 @@ public Discord(DiscordConfiguration configuration, EntryCarManager entryCarManag
chatService.MessageReceived += OnChatMessageReceived;
}
+
+ if (!string.IsNullOrEmpty(_configuration.PluginEventUrl))
+ {
+ PluginEventHook = new DiscordWebhook
+ {
+ Uri = new Uri(_configuration.PluginEventUrl)
+ };
+
+ pluginDataManager.PluginEvent += OnPluginEvent;
+ }
}
private void OnClientConnected(ACTcpClient sender, EventArgs args)
@@ -232,4 +248,45 @@ private DiscordMessage PrepareAuditMessage(
return message;
}
+
+ private void OnPluginEvent(EntryCar sender, PluginDataEventArgs args)
+ {
+ if (sender.Client == null) return;
+
+ string title;
+ switch (args.DataType)
+ {
+ case PluginDataType.Points:
+ title = $":joystick: Scored {args.Value} points";
+ break;
+ case PluginDataType.Time:
+ title = $":stopwatch: Time of {TimeSpan.FromMilliseconds(args.Value)}";
+ break;
+ case PluginDataType.EventWin:
+ title = ":trophy: Winner of Event";
+ break;
+ default:
+ return;
+ }
+
+ Task.Run(async () =>
+ {
+ try
+ {
+ await PluginEventHook!.SendAsync(PrepareAuditMessage(
+ title,
+ _serverNameSanitized,
+ sender.Client.Guid,
+ sender.Client.Name,
+ $"Event {args.Name} from plugin {args.Plugin}",
+ Color.Blue,
+ null
+ ));
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Error in Discord webhook");
+ }
+ });
+ }
}
diff --git a/DiscordAuditPlugin/DiscordConfiguration.cs b/DiscordAuditPlugin/DiscordConfiguration.cs
index 0c216660..8480282d 100644
--- a/DiscordAuditPlugin/DiscordConfiguration.cs
+++ b/DiscordAuditPlugin/DiscordConfiguration.cs
@@ -12,6 +12,8 @@ public class DiscordConfiguration
public string? AuditUrl { get; init; }
[YamlMember(Description = "Discord webhook URL for chat messages")]
public string? ChatUrl { get; init; }
+ [YamlMember(Description = "Discord webhook URL for plugin events")]
+ public string? PluginEventUrl { get; init; }
[YamlMember(Description = "Set this to true if the Discord username of the bot should be the AC server name")]
public bool ChatMessageIncludeServerName { get; init; } = false;
[YamlMember(Description = "Set this to true if the audit message should include the Steam ID")]
diff --git a/RaceChallengePlugin/Race.cs b/RaceChallengePlugin/Race.cs
index 312d9238..5904c7da 100644
--- a/RaceChallengePlugin/Race.cs
+++ b/RaceChallengePlugin/Race.cs
@@ -225,7 +225,6 @@ private void FinishRace()
{
Plugin = GetType().Namespace!,
Name = GetType().Name,
- Description = "Race Challenge",
DataType = PluginDataType.EventWin,
Opponent = Follower
});
diff --git a/TagModePlugin/TagSession.cs b/TagModePlugin/TagSession.cs
index 80fbe857..8c195d46 100644
--- a/TagModePlugin/TagSession.cs
+++ b/TagModePlugin/TagSession.cs
@@ -108,23 +108,26 @@ private async Task FinishSession()
switch (_configuration.EnableEndlessMode)
{
case false:
- var winners = _plugin.Instances.Where(car => car.Value is
- {
- IsTagged: false,
- IsConnected: true
- }).ToDictionary();
+ var anyUntaggedLeft = _plugin.Instances.Any(car => car.Value is
+ {
+ IsTagged: false,
+ IsConnected: true
+ });
+ var winners = _plugin.Instances.Where(x => x.Value is
+ {
+ IsConnected: true
+ } car && car.IsTagged == !anyUntaggedLeft).Select(x => x.Value).ToList();
var winnerName = winners.Count != 0 ? "Runners" : "Taggers";
_entryCarManager.BroadcastChat($"The {winnerName} just won this game of tag.");
Log.Information("The {Winners} just won this game of tag", winnerName);
- foreach (var winnerCar in winners.Values)
+ foreach (var winnerCar in winners)
{
_pluginDataManager.SendPluginEvent(winnerCar.EntryCar, new PluginDataEventArgs
{
Plugin = GetType().Namespace!,
Name = GetType().Name,
- Description = "Tag mode time limited",
DataType = PluginDataType.EventWin
});
}
@@ -135,12 +138,10 @@ private async Task FinishSession()
_entryCarManager.BroadcastChat($"'{winner}' just won this game of tag.");
Log.Information("{Winner} just won this game of tag", winner);
-
_pluginDataManager.SendPluginEvent(LastCaught, new PluginDataEventArgs
{
Plugin = GetType().Namespace!,
Name = GetType().Name,
- Description = "Tag mode endless",
DataType = PluginDataType.EventWin
});
break;