diff --git a/AssettoServer/Server/EventArgs.cs b/AssettoServer/Server/EventArgs.cs
index e19b1682..2b10e51c 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,18 @@ public CarListResponseSendingEventArgs(CarListResponse packet)
Packet = packet;
}
}
+
+public class PluginDataEventArgs : EventArgs
+{
+ public required PluginDataType DataType { get; init; }
+ public required string Plugin { get; init; }
+ public required string Name { 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
new file mode 100644
index 00000000..7614fd10
--- /dev/null
+++ b/AssettoServer/Server/Plugin/PluginDataManager.cs
@@ -0,0 +1,20 @@
+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? PluginEvent;
+
+ public void SendPluginEvent(EntryCar entryCar, PluginDataEventArgs eventArgs)
+ => PluginEvent?.Invoke(entryCar, eventArgs);
+}
+
+public enum PluginDataType
+{
+ Points,
+ Time,
+ EventWin
+}
diff --git a/AssettoServer/Startup.cs b/AssettoServer/Startup.cs
index b0247d73..6f89e305 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/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 f1a09986..5904c7da 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.SendPluginEvent(Leader, new PluginDataEventArgs
+ {
+ Plugin = GetType().Namespace!,
+ Name = GetType().Name,
+ 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..8c195d46 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,42 @@ 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 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);
- _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)
+ {
+ _pluginDataManager.SendPluginEvent(winnerCar.EntryCar, new PluginDataEventArgs
+ {
+ Plugin = GetType().Namespace!,
+ Name = GetType().Name,
+ 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,
+ DataType = PluginDataType.EventWin
+ });
break;
}
}