using Core.Annotations; using Core.Helpers; using Core.Helpers.Dialogue; using Core.Models.Eft.Dialog; using Core.Models.Eft.Profile; using Core.Models.Enums; using Core.Models.Spt.Config; using Core.Models.Utils; using Core.Servers; using Core.Services; using Core.Utils; namespace Core.Controllers; [Injectable] public class DialogueController( ISptLogger _logger, TimeUtil _timeUtil, DialogueHelper _dialogueHelper, ProfileHelper _profileHelper, ConfigServer _configServer, SaveServer _saveServer, LocalisationService _localisationService, MailSendService _mailSendService, IEnumerable dialogueChatBots ) { protected CoreConfig _coreConfig = _configServer.GetConfig(); protected List _dialogueChatBots = dialogueChatBots.ToList(); /// /// /// /// public void RegisterChatBot(IDialogueChatBot chatBot) // TODO: this is in with the helper types { if (_dialogueChatBots.Any(cb => cb.GetChatBot().Id == chatBot.GetChatBot().Id)) { _logger.Error(_localisationService.GetText("dialog-chatbot_id_already_exists", chatBot.GetChatBot().Id)); } _dialogueChatBots.Add(chatBot); } /// /// Handle onUpdate spt event /// public void Update() { var profiles = _saveServer.GetProfiles(); foreach (var kvp in profiles) { RemoveExpiredItemsFromMessages(kvp.Key); } } /// /// Handle client/friend/list /// /// session id /// GetFriendListDataResponse public GetFriendListDataResponse GetFriendList(string sessionId) { // Add all chatbots to the friends list var friends = GetActiveChatBots(); // Add any friends the user has after the chatbots var profile = _profileHelper.GetFullProfile(sessionId); if (profile?.FriendProfileIds is not null) { foreach (var friendId in profile.FriendProfileIds) { var friendProfile = _profileHelper.GetChatRoomMemberFromSessionId(friendId); if (friendProfile is not null) { friends.Add( new UserDialogInfo { Id = friendProfile.Id, Aid = friendProfile.Aid, Info = friendProfile.Info, } ); } } } return new GetFriendListDataResponse { Friends = friends, Ignore = [], InIgnoreList = [] }; } private List GetActiveChatBots() { var activeBots = new List(); var chatBotConfig = _coreConfig.Features.ChatbotFeatures; foreach (var bot in _dialogueChatBots) { var botData = bot.GetChatBot(); if (chatBotConfig.EnabledBots.ContainsKey(botData.Id)) { activeBots.Add(botData); } } return activeBots; } /// /// Handle client/mail/dialog/list /// Create array holding trader dialogs and mail interactions with player /// Set the content of the dialogue on the list tab. /// /// Session Id /// list of dialogs public List GenerateDialogueList(string sessionId) { var data = new List(); foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) { data.Add(GetDialogueInfo(dialogueId.Key, sessionId)); } return data; } /// /// Get the content of a dialogue /// /// Dialog id /// Session Id /// DialogueInfo public DialogueInfo GetDialogueInfo( string? dialogueId, string sessionId) { var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); var dialogue = dialogs.GetValueOrDefault(dialogueId); var result = new DialogueInfo { Id = dialogueId, Type = dialogue.Type ?? MessageType.NPC_TRADER, Message = _dialogueHelper.GetMessagePreview(dialogue), New = dialogue.New, AttachmentsNew = dialogue.AttachmentsNew, Pinned = dialogue.Pinned, Users = GetDialogueUsers(dialogue, dialogue.Type.Value, sessionId), }; return result; } /// /// Get the users involved in a dialog (player + other party) /// /// The dialog to check for users /// What type of message is being sent /// Player id /// UserDialogInfo list public List GetDialogueUsers( Dialogue dialog, MessageType messageType, string sessionId) { var profile = _saveServer.GetProfile(sessionId); // User to user messages are special in that they need the player to exist in them, add if they don't if (messageType == MessageType.USER_MESSAGE && dialog.Users.All(userDialog => userDialog.Id != profile.CharacterData.PmcData.SessionId)) { // Nullguard dialog.Users ??= []; dialog.Users.Add( new UserDialogInfo { Id = profile.CharacterData.PmcData.SessionId, Aid = profile.CharacterData.PmcData.Aid, Info = new UserDialogDetails { Level = profile.CharacterData.PmcData.Info.Level, Nickname = profile.CharacterData.PmcData.Info.Nickname, Side = profile.CharacterData.PmcData.Info.Side, MemberCategory = profile.CharacterData.PmcData.Info.MemberCategory, SelectedMemberCategory = profile.CharacterData.PmcData.Info.SelectedMemberCategory, }, } ); } return dialog.Users; } /// /// Handle client/mail/dialog/view /// Handle player clicking 'messenger' and seeing all the messages they've received /// Set the content of the dialogue on the details panel, showing all the messages /// for the specified dialogue. /// /// Get dialog request /// Session id /// GetMailDialogViewResponseData object public GetMailDialogViewResponseData GenerateDialogueView( GetMailDialogViewRequestData request, string sessionId) { var dialogueId = request.DialogId; var fullProfile = _saveServer.GetProfile(sessionId); var dialogue = GetDialogByIdFromProfile(fullProfile, request); // Dialog was opened, remove the little [1] on screen dialogue.New = 0; // Set number of new attachments, but ignore those that have expired. dialogue.AttachmentsNew = GetUnreadMessagesWithAttachmentsCount(sessionId, dialogueId); return new GetMailDialogViewResponseData { Messages = dialogue.Messages, Profiles = GetProfilesForMail(fullProfile, dialogue.Users), HasMessagesWithRewards = MessagesHaveUncollectedRewards(dialogue.Messages), }; } /// /// Get dialog from player profile, create if doesn't exist /// /// Player profile /// get dialog request /// Dialogue private Dialogue GetDialogByIdFromProfile( SptProfile profile, GetMailDialogViewRequestData request) { if (!profile.DialogueRecords.ContainsKey(request.DialogId)) { profile.DialogueRecords[request.DialogId] = new Dialogue { Id = request.DialogId, AttachmentsNew = 0, Pinned = false, Messages = [], New = 0, Type = request.Type, }; if (request.Type == MessageType.USER_MESSAGE) { var dialogue = profile.DialogueRecords[request.DialogId]; dialogue.Users = []; var chatBot = _dialogueChatBots.FirstOrDefault((cb) => cb.GetChatBot().Id == request.DialogId); if (chatBot is not null) { dialogue.Users ??= []; dialogue.Users.Add(chatBot.GetChatBot()); } } } return profile.DialogueRecords[request.DialogId]; } /// /// Get the users involved in a mail between two entities /// /// Player profile /// The participants of the mail /// UserDialogInfo list private List GetProfilesForMail(SptProfile fullProfile, List? userDialogs) { List result = []; if (userDialogs is null) { // Nothing to add return result; } result.AddRange(userDialogs); if (result.All(userDialog => userDialog.Id != fullProfile.ProfileInfo.ProfileId)) { // Player doesn't exist, add them in before returning var pmcProfile = fullProfile.CharacterData.PmcData; result.Add( new UserDialogInfo { Id = fullProfile.ProfileInfo.ProfileId, Aid = fullProfile.ProfileInfo.Aid, Info = new UserDialogDetails { Nickname = pmcProfile.Info.Nickname, Side = pmcProfile.Info.Side, Level = pmcProfile.Info.Level, MemberCategory = pmcProfile.Info.MemberCategory, SelectedMemberCategory = pmcProfile.Info.SelectedMemberCategory, } } ); } return result; } /// /// Get a count of messages with attachments from a particular dialog /// /// Session id /// Dialog id /// Count of messages with attachments private int GetUnreadMessagesWithAttachmentsCount( string sessionId, string dialogueId) { var newAttachmentCount = 0; var activeMessages = GetActiveMessagesFromDialog(sessionId, dialogueId); foreach (var message in activeMessages) { if (message.HasRewards.GetValueOrDefault(false) && !message.RewardCollected.GetValueOrDefault(false)) { newAttachmentCount++; } } return newAttachmentCount; } /** * Get messages from a specific dialog that have items not expired * @param sessionId Session id * @param dialogueId Dialog to get mail attachments from * @returns Message array */ protected List GetActiveMessagesFromDialog(string sessionId, string dialogueId) { var timeNow = _timeUtil.GetTimeStamp(); var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); return dialogs[dialogueId].Messages.Where((message) => timeNow < message.DateTime + (message.MaxStorageTime ?? 0)).ToList(); } /// /// Does list have messages with uncollected rewards (includes expired rewards) /// /// Messages to check /// true if uncollected rewards found private bool MessagesHaveUncollectedRewards(List messages) { return messages.Any((message) => (message.Items?.Data?.Count ?? 0) > 0); } /// /// Handle client/mail/dialog/remove /// Remove an entire dialog with an entity (trader/user) /// /// id of the dialog to remove /// Player id public void RemoveDialogue( string? dialogueId, string sessionId) { throw new NotImplementedException(); } /// /// Handle client/mail/dialog/pin && Handle client/mail/dialog/unpin /// /// /// /// public void SetDialoguePin( string? dialogueId, bool shouldPin, string sessionId) { throw new NotImplementedException(); } /// /// Handle client/mail/dialog/read /// Set a dialog to be read (no number alert/attachment alert) /// /// Dialog ids to set as read /// Player profile id public void SetRead( List? dialogueIds, string sessionId) { throw new NotImplementedException(); } /// /// Handle client/mail/dialog/getAllAttachments /// Get all uncollected items attached to mail in a particular dialog /// /// Dialog to get mail attachments from /// Session id /// GetAllAttachmentsResponse or null if dialogue doesnt exist public GetAllAttachmentsResponse? GetAllAttachments( string dialogueId, string sessionId) { throw new NotImplementedException(); } /// /// handle client/mail/msg/send /// /// /// /// public string 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 ); } /// /// Get messages from a specific dialog that have items not expired /// /// Session id /// Dialog to get mail attachments from /// message list private List GetActiveMessagesFromDialogue( string sessionId, string dialogueId) { throw new NotImplementedException(); } /// /// Return list of messages with uncollected items (includes expired) /// /// Messages to parse /// messages with items to collect private List GetMessageWithAttachments(List messages) { throw new NotImplementedException(); } /// /// Delete expired items from all messages in player profile. triggers when updating traders. /// /// Session id private void RemoveExpiredItemsFromMessages(string sessionId) { foreach (var dialogueId in _dialogueHelper.GetDialogsForProfile(sessionId)) { RemoveExpiredItemsFromMessage(sessionId, dialogueId.Key); } } /// /// Removes expired items from a message in player profile /// /// Session id /// Dialog id private void RemoveExpiredItemsFromMessage( string sessionId, string dialogueId) { var dialogs = _dialogueHelper.GetDialogsForProfile(sessionId); if (!dialogs.TryGetValue(dialogueId, out var dialog)) { return; } foreach (var message in dialog.Messages) { if (MessageHasExpired(message)) { message.Items = new MessageItems(); } } } /** * Has a dialog message expired * @param message Message to check expiry of * @returns true or false */ protected bool MessageHasExpired(Message message) { return _timeUtil.GetTimeStamp() > message.DateTime + (message.MaxStorageTime ?? 0); } public FriendRequestSendResponse SendFriendRequest(string sessionId, FriendRequestData request) { throw new NotImplementedException(); } public void DeleteFriend(string sessionID, DeleteFriendRequest request) { throw new NotImplementedException(); } }