using Dalamud.Game.Gui.ContextMenu; using Dalamud.Game.Text.SeStringHandling; using LightlessSync.API.Data; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.User; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using LightlessSync.UI; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; namespace LightlessSync.PlayerData.Pairs; /// /// ui wrapper around a pair connection /// public class Pair { private readonly PairLedger _pairLedger; private readonly ILogger _logger; private readonly LightlessMediator _mediator; private readonly ServerConfigurationManager _serverConfigurationManager; private readonly Lazy _apiController; private const int _lightlessPrefixColor = 708; public Pair( ILogger logger, UserFullPairDto userPair, PairLedger pairLedger, LightlessMediator mediator, ServerConfigurationManager serverConfigurationManager, Lazy apiController) { _logger = logger; UserPair = userPair; _pairLedger = pairLedger; _mediator = mediator; _serverConfigurationManager = serverConfigurationManager; _apiController = apiController; } private PairUniqueIdentifier PairIdent => UniqueIdent; private IPairHandlerAdapter? TryGetHandler() { return _pairLedger.GetHandler(PairIdent); } private PairConnection? TryGetConnection() { return _pairLedger.TryGetEntry(PairIdent, out var entry) && entry is not null ? entry.Connection : null; } public bool HasCachedPlayer => TryGetHandler() is not null; public IndividualPairStatus IndividualPairStatus => UserPair.IndividualPairStatus; public bool IsDirectlyPaired => IndividualPairStatus != IndividualPairStatus.None; public bool IsOneSidedPair => IndividualPairStatus == IndividualPairStatus.OneSided; public bool IsOnline => TryGetConnection()?.IsOnline ?? false; public bool IsPaired => IndividualPairStatus == IndividualPairStatus.Bidirectional || UserPair.Groups.Any(); public bool IsPaused => UserPair.OwnPermissions.IsPaused(); public bool IsVisible => _pairLedger.IsPairVisible(PairIdent); public CharacterData? LastReceivedCharacterData => TryGetHandler()?.LastReceivedCharacterData; public string? PlayerName => TryGetHandler()?.PlayerName ?? UserPair.User.AliasOrUID; public long LastAppliedDataBytes => TryGetHandler()?.LastAppliedDataBytes ?? -1; public long LastAppliedDataTris => TryGetHandler()?.LastAppliedDataTris ?? -1; public long LastAppliedApproximateVRAMBytes => TryGetHandler()?.LastAppliedApproximateVRAMBytes ?? -1; public long LastAppliedApproximateEffectiveVRAMBytes => TryGetHandler()?.LastAppliedApproximateEffectiveVRAMBytes ?? -1; public string Ident => TryGetHandler()?.Ident ?? TryGetConnection()?.Ident ?? string.Empty; public uint PlayerCharacterId => TryGetHandler()?.PlayerCharacterId ?? uint.MaxValue; public PairUniqueIdentifier UniqueIdent => new(UserData.UID); public UserData UserData => UserPair.User; public UserFullPairDto UserPair { get; set; } public void AddContextMenu(IMenuOpenedArgs args) { var handler = TryGetHandler(); if (handler is null) { return; } if (args.Target is not MenuTargetDefault target || target.TargetObjectId != handler.PlayerCharacterId || IsPaused) { return; } UiSharedService.AddContextMenuItem(args, name: "Open Profile", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () => { _mediator.Publish(new ProfileOpenStandaloneMessage(this)); return Task.CompletedTask; }); UiSharedService.AddContextMenuItem(args, name: "Reapply last data", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () => { ApplyLastReceivedData(forced: true); return Task.CompletedTask; }); UiSharedService.AddContextMenuItem(args, name: "Change Permissions", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () => { _mediator.Publish(new OpenPermissionWindow(this)); return Task.CompletedTask; }); UiSharedService.AddContextMenuItem(args, name: "Cycle pause state", prefixChar: 'L', colorMenuItem: _lightlessPrefixColor, onClick: () => { TriggerCyclePause(); return Task.CompletedTask; }); } public void ApplyData(OnlineUserCharaDataDto data) { _logger.LogTrace("Character data received for {Uid}; handler will process via registry.", UserData.UID); } private void TriggerCyclePause() { _ = _apiController.Value.CyclePauseAsync(this); } public void ApplyLastReceivedData(bool forced = false) { var handler = TryGetHandler(); if (handler is null) { _logger.LogTrace("ApplyLastReceivedData skipped for {Uid}: handler missing.", UserData.UID); return; } handler.ApplyLastReceivedData(forced); } public void CreateCachedPlayer(OnlineUserIdentDto? dto = null) { var handler = TryGetHandler(); if (handler is null) { _logger.LogTrace("CreateCachedPlayer skipped for {Uid}: handler unavailable.", UserData.UID); return; } if (!handler.Initialized) { handler.Initialize(); } } public string? GetNote() { return _serverConfigurationManager.GetNoteForUid(UserData.UID); } public string GetPlayerNameHash() { return TryGetHandler()?.PlayerNameHash ?? string.Empty; } public bool HasAnyConnection() { return UserPair.Groups.Any() || UserPair.IndividualPairStatus != IndividualPairStatus.None; } public void MarkOffline(bool wait = true) { _logger.LogTrace("MarkOffline invoked for {Uid} (wait: {Wait}). New registry handles handler disposal.", UserData.UID, wait); } public void SetNote(string note) { _serverConfigurationManager.SetNoteForUid(UserData.UID, note); } internal void SetIsUploading() { var handler = TryGetHandler(); if (handler is null) { return; } handler.SetUploading(true); } public PairDebugInfo GetDebugInfo() { var handler = TryGetHandler(); if (handler is null) { return PairDebugInfo.Empty; } return new PairDebugInfo( true, handler.Initialized, handler.IsVisible, handler.ScheduledForDeletion, handler.LastDataReceivedAt, handler.LastApplyAttemptAt, handler.LastSuccessfulApplyAt, handler.LastFailureReason, handler.LastBlockingConditions, handler.IsApplying, handler.IsDownloading, handler.PendingDownloadCount, handler.ForbiddenDownloadCount); } }