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; } }