using System.Diagnostics.CodeAnalysis; using Content.Shared.Administration; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.Network; using Robust.Shared.Player; namespace Content.Server.Administration; /// /// This handles the server portion of quick dialogs, including opening them. /// public sealed partial class QuickDialogSystem : EntitySystem { [Dependency] private readonly IPlayerManager _playerManager = default!; /// /// Contains the success/cancel actions for a dialog. /// private readonly Dictionary okAction, Action cancelAction)> _openDialogs = new(); private readonly Dictionary> _openDialogsByUser = new(); private int _nextDialogId = 1; /// public override void Initialize() { _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; SubscribeNetworkEvent(Handler); } public override void Shutdown() { base.Shutdown(); _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged; } private void Handler(QuickDialogResponseEvent msg, EntitySessionEventArgs args) { if (!_openDialogs.ContainsKey(msg.DialogId) || !_openDialogsByUser[args.SenderSession.UserId].Contains(msg.DialogId)) { args.SenderSession.ConnectedClient.Disconnect($"Replied with invalid quick dialog data with id {msg.DialogId}."); return; } switch (msg.ButtonPressed) { case QuickDialogButtonFlag.OkButton: _openDialogs[msg.DialogId].okAction.Invoke(msg); break; case QuickDialogButtonFlag.CancelButton: _openDialogs[msg.DialogId].cancelAction.Invoke(); break; default: throw new ArgumentOutOfRangeException(); } _openDialogs.Remove(msg.DialogId); _openDialogsByUser[args.SenderSession.UserId].Remove(msg.DialogId); } private int GetDialogId() { return _nextDialogId++; } private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { if (e.NewStatus != SessionStatus.Disconnected && e.NewStatus != SessionStatus.Zombie) return; var user = e.Session.UserId; if (!_openDialogsByUser.ContainsKey(user)) return; foreach (var dialogId in _openDialogsByUser[user]) { _openDialogs[dialogId].cancelAction.Invoke(); _openDialogs.Remove(dialogId); } _openDialogsByUser.Remove(user); } private void OpenDialogInternal(ICommonSession session, string title, List entries, QuickDialogButtonFlag buttons, Action okAction, Action cancelAction) { var did = GetDialogId(); RaiseNetworkEvent( new QuickDialogOpenEvent( title, entries, did, buttons), session ); _openDialogs.Add(did, (okAction, cancelAction)); if (!_openDialogsByUser.ContainsKey(session.UserId)) _openDialogsByUser.Add(session.UserId, new List()); _openDialogsByUser[session.UserId].Add(did); } private bool TryParseQuickDialog(QuickDialogEntryType entryType, string input, [NotNullWhen(true)] out T? output) { switch (entryType) { case QuickDialogEntryType.Integer: { var result = int.TryParse(input, out var val); output = (T?) (object?) val; return result; } case QuickDialogEntryType.Float: { var result = float.TryParse(input, out var val); output = (T?) (object?) val; return result; } case QuickDialogEntryType.ShortText: { if (input.Length > 100) { output = default; return false; } output = (T?) (object?) input; return output is not null; } case QuickDialogEntryType.LongText: { if (input.Length > 2000) { output = default; return false; } //It's verrrry likely that this will be longstring var longString = (LongString) input; output = (T?) (object?) longString; return output is not null; } default: throw new ArgumentOutOfRangeException(nameof(entryType), entryType, null); } } private QuickDialogEntryType TypeToEntryType(Type T) { if (T == typeof(int) || T == typeof(uint) || T == typeof(long) || T == typeof(ulong)) return QuickDialogEntryType.Integer; if (T == typeof(float) || T == typeof(double)) return QuickDialogEntryType.Float; if (T == typeof(string)) // People are more likely to notice the input box is too short than they are to notice it's too long. return QuickDialogEntryType.ShortText; if (T == typeof(LongString)) return QuickDialogEntryType.LongText; throw new ArgumentException($"Tried to open a dialog with unsupported type {T}."); } } /// /// A type used with quick dialogs to indicate you want a large entry window for text and not a short one. /// /// The string retrieved. public record struct LongString(string String) { public static implicit operator string(LongString longString) { return longString.String; } public static explicit operator LongString(string s) { return new(s); } }