From b6692fead410cd492ff7d496174b50b02cc5af59 Mon Sep 17 00:00:00 2001 From: Jesse Date: Fri, 4 Jul 2025 19:04:37 +0200 Subject: [PATCH] Chatbot async improvements (#446) * Add better chatbot handling by making them asynchronous Removes the need for having RunInTimespan as await Task.Delay now can handle this * Remove now unused classes * Handle commando's commands with ValueTask * Set values as not nullable, client sends all of these --- .../Callbacks/DialogueCallbacks.cs | 6 +- .../Controllers/DialogueController.cs | 19 +++-- .../Helpers/Dialogue/AbstractDialogChatBot.cs | 74 +++++++++---------- .../Helpers/Dialogue/Commando/IChatCommand.cs | 2 +- .../Dialogue/Commando/SptCommandoCommands.cs | 4 +- .../SptCommands/GiveCommand/GiveSptCommand.cs | 18 ++--- .../Commando/SptCommands/ISptCommand.cs | 2 +- .../ProfileCommand/ProfileSptCommand.cs | 14 ++-- .../TraderCommand/TraderSptCommand.cs | 10 +-- .../Helpers/Dialogue/IDialogueChatBot.cs | 7 +- .../Helpers/Dialogue/SptDialogueChatBot.cs | 12 ++- .../Models/Eft/Dialog/SendMessageRequest.cs | 8 +- .../Utils/Callbacks/TimeoutCallback.cs | 13 ---- .../SPTarkov.Server.Core/Utils/TimerUtil.cs | 29 -------- 14 files changed, 90 insertions(+), 128 deletions(-) delete mode 100644 Libraries/SPTarkov.Server.Core/Utils/Callbacks/TimeoutCallback.cs delete mode 100644 Libraries/SPTarkov.Server.Core/Utils/TimerUtil.cs diff --git a/Libraries/SPTarkov.Server.Core/Callbacks/DialogueCallbacks.cs b/Libraries/SPTarkov.Server.Core/Callbacks/DialogueCallbacks.cs index df3ea82b..b544a00b 100644 --- a/Libraries/SPTarkov.Server.Core/Callbacks/DialogueCallbacks.cs +++ b/Libraries/SPTarkov.Server.Core/Callbacks/DialogueCallbacks.cs @@ -199,15 +199,13 @@ public class DialogueCallbacks( /// Handle client/mail/msg/send /// /// - public virtual ValueTask SendMessage( + public virtual async ValueTask SendMessage( string url, SendMessageRequest request, string sessionID ) { - return new ValueTask( - _httpResponseUtil.GetBody(_dialogueController.SendMessage(sessionID, request)) - ); + return _httpResponseUtil.GetBody(await _dialogueController.SendMessage(sessionID, request)); } /// diff --git a/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs b/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs index ffe8eb52..3431d304 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/DialogueController.cs @@ -538,15 +538,22 @@ public class DialogueController( /// Session/Player id /// /// - public virtual string SendMessage(string sessionId, SendMessageRequest request) + public virtual async ValueTask SendMessage(string sessionId, SendMessageRequest request) { _mailSendService.SendPlayerMessageToNpc(sessionId, request.DialogId!, request.Text!); - return ( - _dialogueChatBots - .FirstOrDefault(cb => cb.GetChatBot().Id == request.DialogId) - ?.HandleMessage(sessionId, request) ?? request.DialogId - ) ?? string.Empty; + var chatBot = _dialogueChatBots.FirstOrDefault(cb => + cb.GetChatBot().Id == request.DialogId + ); + + if (chatBot is not null) + { + return await chatBot.HandleMessage(sessionId, request); + } + else + { + return string.Empty; + } } /// diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/AbstractDialogChatBot.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/AbstractDialogChatBot.cs index 172f8507..462351a7 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/AbstractDialogChatBot.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/AbstractDialogChatBot.cs @@ -3,7 +3,6 @@ using SPTarkov.Server.Core.Models.Eft.Dialog; using SPTarkov.Server.Core.Models.Eft.Profile; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Services; -using SPTarkov.Server.Core.Utils.Callbacks; namespace SPTarkov.Server.Core.Helpers.Dialogue; @@ -20,7 +19,7 @@ public abstract class AbstractDialogChatBot( public abstract UserDialogInfo GetChatBot(); - public string? HandleMessage(string sessionId, SendMessageRequest request) + public async ValueTask HandleMessage(string sessionId, SendMessageRequest request) { if ((request.Text ?? "").Length == 0) { @@ -37,14 +36,14 @@ public abstract class AbstractDialogChatBot( && commando.GetCommands().Contains(splitCommand[1]) ) { - return commando.Handle(splitCommand[1], GetChatBot(), sessionId, request); + return await commando.Handle(splitCommand[1], GetChatBot(), sessionId, request); } if ( string.Equals(splitCommand.FirstOrDefault(), "help", StringComparison.OrdinalIgnoreCase) ) { - return SendPlayerHelpMessage(sessionId, request); + return await SendPlayerHelpMessage(sessionId, request); } _mailSendService.SendUserMessageToPlayer( @@ -55,10 +54,13 @@ public abstract class AbstractDialogChatBot( null ); - return null; + return string.Empty; } - protected string? SendPlayerHelpMessage(string sessionId, SendMessageRequest request) + protected async ValueTask SendPlayerHelpMessage( + string sessionId, + SendMessageRequest request + ) { _mailSendService.SendUserMessageToPlayer( sessionId, @@ -67,40 +69,34 @@ public abstract class AbstractDialogChatBot( [], null ); - // due to BSG being dumb with messages we need a mandatory timeout between messages so they get out on the right order - TimeoutCallback.RunInTimespan( - () => - { - foreach (var chatCommand in _chatCommands.Values) - { - _mailSendService.SendUserMessageToPlayer( - sessionId, - GetChatBot(), - $"Commands available for \"{chatCommand.GetCommandPrefix()}\" prefix:", - [], - null - ); + foreach (var chatCommand in _chatCommands.Values) + { + // due to BSG being dumb with messages we need a mandatory timeout between messages so they get out on the right order + await Task.Delay(TimeSpan.FromSeconds(1)); - TimeoutCallback.RunInTimespan( - () => - { - foreach (var subCommand in chatCommand.GetCommands()) - { - _mailSendService.SendUserMessageToPlayer( - sessionId, - GetChatBot(), - $"Subcommand {subCommand}:\n{chatCommand.GetCommandHelp(subCommand)}", - [], - null - ); - } - }, - TimeSpan.FromSeconds(1) - ); - } - }, - TimeSpan.FromSeconds(1) - ); + _mailSendService.SendUserMessageToPlayer( + sessionId, + GetChatBot(), + $"Commands available for \"{chatCommand.GetCommandPrefix()}\" prefix:", + [], + null + ); + + await Task.Delay(TimeSpan.FromSeconds(1)); + + foreach (var subCommand in chatCommand.GetCommands()) + { + _mailSendService.SendUserMessageToPlayer( + sessionId, + GetChatBot(), + $"Subcommand {subCommand}:\n{chatCommand.GetCommandHelp(subCommand)}", + [], + null + ); + + await Task.Delay(TimeSpan.FromSeconds(1)); + } + } return request.DialogId; } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/IChatCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/IChatCommand.cs index e072bde6..9ae71c68 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/IChatCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/IChatCommand.cs @@ -8,7 +8,7 @@ public interface IChatCommand public string GetCommandPrefix(); public string GetCommandHelp(string command); public List GetCommands(); - public string Handle( + public ValueTask Handle( string command, UserDialogInfo commandHandler, string sessionId, diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommandoCommands.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommandoCommands.cs index cb94bf28..07c63547 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommandoCommands.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommandoCommands.cs @@ -50,14 +50,14 @@ public class SptCommandoCommands : IChatCommand return _sptCommands.Keys.ToList(); } - public string Handle( + public async ValueTask Handle( string command, UserDialogInfo commandHandler, string sessionId, SendMessageRequest request ) { - return _sptCommands[command].PerformAction(commandHandler, sessionId, request); + return await _sptCommands[command].PerformAction(commandHandler, sessionId, request); } public void RegisterSptCommandoCommand(ISptCommand command) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs index 1a75fad3..2a86244d 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.cs @@ -52,7 +52,7 @@ public class GiveSptCommand( + "give [locale] [\"item name\"] [quantity]\n\t\tEx: spt give fr \"figurine de chat\" 3"; } - public string PerformAction( + public ValueTask PerformAction( UserDialogInfo commandHandler, string sessionId, SendMessageRequest request @@ -65,7 +65,7 @@ public class GiveSptCommand( commandHandler, "Invalid use of give command. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } var result = _commandRegex.Match(request.Text); @@ -86,7 +86,7 @@ public class GiveSptCommand( commandHandler, "Invalid use of give command. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } _savedCommand.TryGetValue(sessionId, out var savedCommand); @@ -98,7 +98,7 @@ public class GiveSptCommand( commandHandler, "Invalid selection. Outside of bounds! Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } item = savedCommand.PotentialItemNames[locationSixValue - 1]; @@ -128,7 +128,7 @@ public class GiveSptCommand( commandHandler, "Invalid quantity! Must be 1 or higher. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } if (isItemName) @@ -197,7 +197,7 @@ public class GiveSptCommand( $"Could not find exact match. Closest are:\n{string.Join("\n", itemList)}\n\nUse 'spt give [above number]' to select one." ); - return request.DialogId; + return new ValueTask(request.DialogId); } } } @@ -224,7 +224,7 @@ public class GiveSptCommand( commandHandler, "That item could not be found. Please refine your request and try again." ); - return request.DialogId; + return new ValueTask(request.DialogId); } List itemsToSend = []; @@ -286,7 +286,7 @@ public class GiveSptCommand( "Too many items requested. Please lower the amount and try again." ); - return request.DialogId; + return new ValueTask(request.DialogId); } } } @@ -300,7 +300,7 @@ public class GiveSptCommand( itemsToSend ); - return request.DialogId; + return new ValueTask(request.DialogId); } /// diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ISptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ISptCommand.cs index d054e09b..a97fb9c8 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ISptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ISptCommand.cs @@ -7,7 +7,7 @@ public interface ISptCommand { public string GetCommand(); public string GetCommandHelp(); - public string PerformAction( + public ValueTask PerformAction( UserDialogInfo commandHandler, string sessionId, SendMessageRequest request diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ProfileCommand/ProfileSptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ProfileCommand/ProfileSptCommand.cs index eb6342db..3f8ff992 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ProfileCommand/ProfileSptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/ProfileCommand/ProfileSptCommand.cs @@ -42,7 +42,7 @@ public class ProfileSptCommand( + "spt profile skill metabolism 51"; } - public string PerformAction( + public ValueTask PerformAction( UserDialogInfo commandHandler, string sessionId, SendMessageRequest request @@ -58,7 +58,7 @@ public class ProfileSptCommand( commandHandler, "Invalid use of trader command. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } var result = _commandRegex.Match(request.Text); @@ -82,7 +82,7 @@ public class ProfileSptCommand( commandHandler, "Invalid use of profile command, the level was outside bounds: 1 to 70. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } profileChangeEvent = HandleLevelCommand(quantity); @@ -102,7 +102,7 @@ public class ProfileSptCommand( commandHandler, "Invalid use of profile command, the skill was not found. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } if (quantity is < 0 or > 51) @@ -112,7 +112,7 @@ public class ProfileSptCommand( commandHandler, "Invalid use of profile command, the skill level was outside bounds: 1 to 51. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } profileChangeEvent = HandleSkillCommand(enumSkill, quantity); @@ -129,7 +129,7 @@ public class ProfileSptCommand( commandHandler, $"If you are reading this, this is bad. Please report this to SPT staff with a screenshot. Command: {command}." ); - return request.DialogId; + return new ValueTask(request.DialogId); } _mailSendService.SendSystemMessageToPlayer( @@ -149,7 +149,7 @@ public class ProfileSptCommand( [profileChangeEvent] ); - return request.DialogId; + return new ValueTask(request.DialogId); } protected ProfileChangeEvent HandleSkillCommand(SkillTypes? skill, int level) diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/TraderCommand/TraderSptCommand.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/TraderCommand/TraderSptCommand.cs index 8293ee4f..77607304 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/TraderCommand/TraderSptCommand.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/Commando/SptCommands/TraderCommand/TraderSptCommand.cs @@ -34,7 +34,7 @@ public class TraderSptCommand( return "spt trader \n ======== \n Sets the reputation or money spent to the input quantity through the message system.\n\n\tspt trader [trader] rep [quantity]\n\t\tEx: spt trader prapor rep 2\n\n\tspt trader [trader] spend [quantity]\n\t\tEx: spt trader therapist spend 1000000"; } - public string PerformAction( + public ValueTask PerformAction( UserDialogInfo commandHandler, string sessionId, SendMessageRequest request @@ -47,7 +47,7 @@ public class TraderSptCommand( commandHandler, "Invalid use of trader command. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } var result = _commandRegex.Match(request.Text); @@ -75,7 +75,7 @@ public class TraderSptCommand( "Invalid use of trader command, the trader was not found. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } NotificationEventType profileChangeEventType; @@ -96,7 +96,7 @@ public class TraderSptCommand( "Invalid use of trader command, ProfileChangeEventType was not found. Use 'help' for more information." ); - return request.DialogId; + return new ValueTask(request.DialogId); } } @@ -117,7 +117,7 @@ public class TraderSptCommand( [CreateProfileChangeEvent(profileChangeEventType, quantity, dbTrader.Id)] ); - return request.DialogId; + return new ValueTask(request.DialogId); } protected ProfileChangeEvent CreateProfileChangeEvent( diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/IDialogueChatBot.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/IDialogueChatBot.cs index 866d0b62..d0c1a3f5 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/IDialogueChatBot.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/IDialogueChatBot.cs @@ -6,5 +6,10 @@ namespace SPTarkov.Server.Core.Helpers.Dialogue; public interface IDialogueChatBot { public UserDialogInfo GetChatBot(); - public string? HandleMessage(string sessionId, SendMessageRequest request); + + /// + /// Handles messages for the chatbot. If a message can't be handled, should be used. + /// + /// The response of the bot, or if the request could not be handled. + public ValueTask HandleMessage(string sessionId, SendMessageRequest request); } diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/SptDialogueChatBot.cs b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/SptDialogueChatBot.cs index a6f10e5c..0ea2a46d 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/SptDialogueChatBot.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Dialogue/SptDialogueChatBot.cs @@ -13,9 +13,7 @@ namespace SPTarkov.Server.Core.Helpers.Dialogue; [Injectable] public class SptDialogueChatBot( - ISptLogger _logger, MailSendService _mailSendService, - IEnumerable _chatCommands, ConfigServer _configServer, ProfileHelper _profileHelper, IEnumerable chatMessageHandlers @@ -42,7 +40,7 @@ public class SptDialogueChatBot( }; } - public string? HandleMessage(string sessionId, SendMessageRequest request) + public ValueTask HandleMessage(string sessionId, SendMessageRequest request) { var sender = _profileHelper.GetPmcProfile(sessionId); var sptFriendUser = GetChatBot(); @@ -57,7 +55,7 @@ public class SptDialogueChatBot( { handler.Process(sessionId, sptFriendUser, sender, request); - return request.DialogId; + return new ValueTask(request.DialogId); } _mailSendService.SendUserMessageToPlayer( @@ -68,7 +66,7 @@ public class SptDialogueChatBot( null ); - return request.DialogId; + return new ValueTask(request.DialogId); } protected static List ChatMessageHandlerSetup( @@ -86,7 +84,7 @@ public class SptDialogueChatBot( return "Unknown command."; } - protected string? SendPlayerHelpMessage(string sessionId, SendMessageRequest request) + protected ValueTask SendPlayerHelpMessage(string sessionId, SendMessageRequest request) { _mailSendService.SendUserMessageToPlayer( sessionId, @@ -96,6 +94,6 @@ public class SptDialogueChatBot( null ); - return request.DialogId; + return new ValueTask(request.DialogId); } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Eft/Dialog/SendMessageRequest.cs b/Libraries/SPTarkov.Server.Core/Models/Eft/Dialog/SendMessageRequest.cs index 3ddfd946..61e61d4e 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Eft/Dialog/SendMessageRequest.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Eft/Dialog/SendMessageRequest.cs @@ -10,14 +10,14 @@ public record SendMessageRequest : IRequestData public Dictionary? ExtensionData { get; set; } [JsonPropertyName("dialogId")] - public string? DialogId { get; set; } + public required string DialogId { get; set; } [JsonPropertyName("type")] - public MessageType? Type { get; set; } + public required MessageType Type { get; set; } [JsonPropertyName("text")] - public string? Text { get; set; } + public required string Text { get; set; } [JsonPropertyName("replyTo")] - public string? ReplyTo { get; set; } + public required string ReplyTo { get; set; } } diff --git a/Libraries/SPTarkov.Server.Core/Utils/Callbacks/TimeoutCallback.cs b/Libraries/SPTarkov.Server.Core/Utils/Callbacks/TimeoutCallback.cs deleted file mode 100644 index a3578bdd..00000000 --- a/Libraries/SPTarkov.Server.Core/Utils/Callbacks/TimeoutCallback.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace SPTarkov.Server.Core.Utils.Callbacks; - -public static class TimeoutCallback -{ - public static Task RunInTimespan(Action action, TimeSpan timeSpan) - { - return Task.Factory.StartNew(() => - { - Thread.Sleep(timeSpan); - action(); - }); - } -} diff --git a/Libraries/SPTarkov.Server.Core/Utils/TimerUtil.cs b/Libraries/SPTarkov.Server.Core/Utils/TimerUtil.cs deleted file mode 100644 index bb73a12f..00000000 --- a/Libraries/SPTarkov.Server.Core/Utils/TimerUtil.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Diagnostics; -using SPTarkov.DI.Annotations; - -namespace SPTarkov.Server.Core.Utils; - -[Injectable] -public class TimerUtil -{ - protected readonly Stopwatch _stopwatch; - - public TimerUtil() - { - _stopwatch = new Stopwatch(); - _stopwatch.Start(); - } - - public int Stop(string unit = "sec") - { - _stopwatch.Stop(); - var timePassed = _stopwatch.Elapsed; - - return unit switch - { - "ns" => timePassed.Nanoseconds, - "ms" => timePassed.Milliseconds, - _ => timePassed.Seconds, - }; - } -}