using System; using System.Numerics; using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using MareSynchronos.API.Data.Enum; using MareSynchronos.API.Data.Extensions; using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; using MareSynchronos.Services.Mediator; using MareSynchronos.UI.Handlers; using MareSynchronos.WebAPI; using MareSynchronos.Localization; namespace MareSynchronos.UI.Components; public class DrawGroupPair : DrawPairBase { protected readonly MareMediator _mediator; private readonly GroupPairFullInfoDto _fullInfoDto; private readonly GroupFullInfoDto _group; private readonly CharaDataManager _charaDataManager; private static string L(string key, string fallback, params object[] args) { var safeArgs = args ?? Array.Empty(); return LocalizationService.Instance?.GetString(key, fallback, safeArgs) ?? string.Format(System.Globalization.CultureInfo.CurrentCulture, fallback, safeArgs); } public DrawGroupPair(string id, Pair entry, ApiController apiController, MareMediator mareMediator, GroupFullInfoDto group, GroupPairFullInfoDto fullInfoDto, UidDisplayHandler handler, UiSharedService uiSharedService, CharaDataManager charaDataManager) : base(id, entry, apiController, handler, uiSharedService) { _group = group; _fullInfoDto = fullInfoDto; _mediator = mareMediator; _charaDataManager = charaDataManager; } protected override void DrawLeftSide(float textPosY, float originalY) { var entryUID = _pair.UserData.AliasOrUID; var entryIsMod = _fullInfoDto.GroupPairStatusInfo.IsModerator(); var entryIsOwner = string.Equals(_pair.UserData.UID, _group.OwnerUID, StringComparison.Ordinal); var entryIsPinned = _fullInfoDto.GroupPairStatusInfo.IsPinned(); var presenceIcon = _pair.IsVisible ? FontAwesomeIcon.Eye : FontAwesomeIcon.CloudMoon; var presenceColor = (_pair.IsOnline || _pair.IsVisible) ? new Vector4(0.63f, 0.25f, 1f, 1f) : ImGuiColors.DalamudGrey; var presenceText = L("GroupPair.Presence.Offline", "{0} is offline", entryUID); ImGui.SetCursorPosY(textPosY); bool drewPrefixIcon = false; if (_pair.IsPaused) { presenceText = L("GroupPair.Presence.Paused", "{0} online status is unknown (paused)", entryUID); ImGui.PushFont(UiBuilder.IconFont); UiSharedService.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); ImGui.PopFont(); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Paused", "Pairing status with {0} is paused", entryUID)); drewPrefixIcon = true; } else { bool individuallyPaired = _pair.UserPair != null; var violet = new Vector4(0.63f, 0.25f, 1f, 1f); if (individuallyPaired && (_pair.IsOnline || _pair.IsVisible)) { ImGui.PushFont(UiBuilder.IconFont); UiSharedService.ColorText(FontAwesomeIcon.Moon.ToIconString(), violet); ImGui.PopFont(); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.IndividuallyPaired", "You are individually paired with {0}", entryUID)); drewPrefixIcon = true; } } if (drewPrefixIcon) ImGui.SameLine(); ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); UiSharedService.ColorText(presenceIcon.ToIconString(), presenceColor); ImGui.PopFont(); if (_pair.IsOnline && !_pair.IsVisible) presenceText = L("GroupPair.Presence.Online", "{0} is online", entryUID); else if (_pair.IsOnline && _pair.IsVisible) presenceText = L("GroupPair.Presence.Visible", "{0} is visible: {1}\nClick to target this player", entryUID, _pair.PlayerName); if (_pair.IsVisible) { if (ImGui.IsItemClicked()) { _mediator.Publish(new TargetPairMessage(_pair)); } if (_pair.LastAppliedDataBytes >= 0) { presenceText += UiSharedService.TooltipSeparator; presenceText += (!_pair.IsVisible ? L("GroupPair.Presence.LastPrefix", "(Last) ") : string.Empty) + L("GroupPair.Presence.ModsInfo", "Mods Info") + Environment.NewLine; presenceText += L("GroupPair.Presence.FilesSize", "Files Size: {0}", UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true)); if (_pair.LastAppliedApproximateVRAMBytes >= 0) { presenceText += Environment.NewLine + L("GroupPair.Presence.Vram", "Approx. VRAM Usage: {0}", UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true)); } if (_pair.LastAppliedDataTris >= 0) { var trisValue = _pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'", System.Globalization.CultureInfo.CurrentCulture) : _pair.LastAppliedDataTris.ToString(System.Globalization.CultureInfo.CurrentCulture); presenceText += Environment.NewLine + L("GroupPair.Presence.Tris", "Triangle Count (excl. Vanilla): {0}", trisValue); } } } UiSharedService.AttachToolTip(presenceText); if (entryIsOwner) { ImGui.SameLine(); ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); ImGui.TextUnformatted(FontAwesomeIcon.Crown.ToIconString()); ImGui.PopFont(); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Owner", "User is owner of this Syncshell")); } else if (entryIsMod) { ImGui.SameLine(); ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); ImGui.TextUnformatted(FontAwesomeIcon.UserShield.ToIconString()); ImGui.PopFont(); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Moderator", "User is moderator of this Syncshell")); } else if (entryIsPinned) { ImGui.SameLine(); ImGui.SetCursorPosY(textPosY); ImGui.PushFont(UiBuilder.IconFont); ImGui.TextUnformatted(FontAwesomeIcon.Thumbtack.ToIconString()); ImGui.PopFont(); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.Pinned", "User is pinned in this Syncshell")); } } protected override float DrawRightSide(float textPosY, float originalY) { var entryUID = _fullInfoDto.UserAliasOrUID; var entryIsMod = _fullInfoDto.GroupPairStatusInfo.IsModerator(); var entryIsOwner = string.Equals(_pair.UserData.UID, _group.OwnerUID, StringComparison.Ordinal); var entryIsPinned = _fullInfoDto.GroupPairStatusInfo.IsPinned(); var userIsOwner = string.Equals(_group.OwnerUID, _apiController.UID, StringComparison.OrdinalIgnoreCase); var userIsModerator = _group.GroupUserInfo.IsModerator(); var soundsDisabled = _fullInfoDto.GroupUserPermissions.IsDisableSounds(); var animDisabled = _fullInfoDto.GroupUserPermissions.IsDisableAnimations(); var vfxDisabled = _fullInfoDto.GroupUserPermissions.IsDisableVFX(); var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false); var individualAnimDisabled = (_pair.UserPair?.OwnPermissions.IsDisableAnimations() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableAnimations() ?? false); var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false); bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData); bool showInfo = (individualAnimDisabled || individualSoundsDisabled || animDisabled || soundsDisabled); bool showPlus = _pair.UserPair == null; bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused; var spacing = ImGui.GetStyle().ItemSpacing.X; var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle : ((soundsDisabled || animDisabled || vfxDisabled) ? FontAwesomeIcon.InfoCircle : FontAwesomeIcon.None); var runningIconWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X; var infoIconWidth = UiSharedService.GetIconSize(permIcon).X; var plusButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X; var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X; var pos = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() + spacing - (showShared ? (runningIconWidth + spacing) : 0) - (showInfo ? (infoIconWidth + spacing) : 0) - (showPlus ? (plusButtonWidth + spacing) : 0) - (showBars ? (barButtonWidth + spacing) : 0); ImGui.SameLine(pos); if (showShared) { _uiSharedService.IconText(FontAwesomeIcon.Running); UiSharedService.AttachToolTip(L("GroupPair.Tooltip.SharedData", "This user has shared {0} Character Data Sets with you.", sharedData!.Count) + UiSharedService.TooltipSeparator + L("GroupPair.Tooltip.SharedData.OpenHub", "Click to open the Character Data Hub and show the entries.")); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { _mediator.Publish(new OpenCharaDataHubWithFilterMessage(_pair.UserData)); } ImGui.SameLine(); } if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) { ImGui.SetCursorPosY(textPosY); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow); _uiSharedService.IconText(permIcon); ImGui.PopStyleColor(); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); ImGui.TextUnformatted(L("GroupPair.Tooltip.IndividualHeader", "Individual User permissions")); if (individualSoundsDisabled) { var userSoundsText = L("GroupPair.Tooltip.SoundWith", "Sound sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.VolumeOff); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userSoundsText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}", _pair.UserPair!.OwnPermissions.IsDisableSounds() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"), _pair.UserPair!.OtherPermissions.IsDisableSounds() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"))); } if (individualAnimDisabled) { var userAnimText = L("GroupPair.Tooltip.AnimWith", "Animation sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Stop); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userAnimText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}", _pair.UserPair!.OwnPermissions.IsDisableAnimations() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"), _pair.UserPair!.OtherPermissions.IsDisableAnimations() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"))); } if (individualVFXDisabled) { var userVFXText = L("GroupPair.Tooltip.VfxWith", "VFX sync disabled with {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Circle); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userVFXText); ImGui.NewLine(); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(L("GroupPair.Tooltip.YouThey", "You: {0}, They: {1}", _pair.UserPair!.OwnPermissions.IsDisableVFX() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"), _pair.UserPair!.OtherPermissions.IsDisableVFX() ? L("GroupPair.Toggle.Disabled", "Disabled") : L("GroupPair.Toggle.Enabled", "Enabled"))); } ImGui.EndTooltip(); } ImGui.SameLine(); } else if ((animDisabled || soundsDisabled || vfxDisabled)) { ImGui.SetCursorPosY(textPosY); _uiSharedService.IconText(permIcon); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); ImGui.TextUnformatted(L("GroupPair.Tooltip.SyncshellHeader", "Syncshell User permissions")); if (soundsDisabled) { var userSoundsText = L("GroupPair.Tooltip.SoundBy", "Sound sync disabled by {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.VolumeOff); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userSoundsText); } if (animDisabled) { var userAnimText = L("GroupPair.Tooltip.AnimBy", "Animation sync disabled by {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Stop); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userAnimText); } if (vfxDisabled) { var userVFXText = L("GroupPair.Tooltip.VfxBy", "VFX sync disabled by {0}", _pair.UserData.AliasOrUID); _uiSharedService.IconText(FontAwesomeIcon.Circle); ImGui.SameLine(40 * ImGuiHelpers.GlobalScale); ImGui.TextUnformatted(userVFXText); } ImGui.EndTooltip(); } ImGui.SameLine(); } if (showPlus) { ImGui.SetCursorPosY(originalY); if (_uiSharedService.IconButton(FontAwesomeIcon.Plus)) { _ = _apiController.UserAddPair(new UserDto(new(_pair.UserData.UID))); } UiSharedService.AttachToolTip(L("GroupPair.Popup.PairIndividually", "Pair with {0} individually", entryUID)); ImGui.SameLine(); } if (showBars) { ImGui.SetCursorPosY(originalY); if (_uiSharedService.IconButton(FontAwesomeIcon.Bars)) { ImGui.OpenPopup("Popup"); } } if (ImGui.BeginPopup("Popup")) { if ((userIsModerator || userIsOwner) && !(entryIsMod || entryIsOwner)) { var pinText = entryIsPinned ? "Unpin user" : "Pin user"; if (_uiSharedService.IconTextButton(FontAwesomeIcon.Thumbtack, pinText)) { ImGui.CloseCurrentPopup(); var userInfo = _fullInfoDto.GroupPairStatusInfo ^ GroupUserInfo.IsPinned; _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(_fullInfoDto.Group, _fullInfoDto.User, userInfo)); } UiSharedService.AttachToolTip("Pin this user to the Syncshell. Pinned users will not be deleted in case of a manually initiated Syncshell clean"); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Trash, "Remove user") && UiSharedService.CtrlPressed()) { ImGui.CloseCurrentPopup(); _ = _apiController.GroupRemoveUser(_fullInfoDto); } UiSharedService.AttachToolTip("Hold CTRL and click to remove user " + (_pair.UserData.AliasOrUID) + " from Syncshell"); if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserSlash, "Ban User")) { ImGui.CloseCurrentPopup(); _mediator.Publish(new OpenBanUserPopupMessage(_pair, _group)); } UiSharedService.AttachToolTip("Ban user from this Syncshell"); } if (userIsOwner) { string modText = entryIsMod ? "Demod user" : "Mod user"; if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserShield, modText) && UiSharedService.CtrlPressed()) { ImGui.CloseCurrentPopup(); var userInfo = _fullInfoDto.GroupPairStatusInfo ^ GroupUserInfo.IsModerator; _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(_fullInfoDto.Group, _fullInfoDto.User, userInfo)); } UiSharedService.AttachToolTip("Hold CTRL to change the moderator status for " + (_fullInfoDto.UserAliasOrUID) + Environment.NewLine + "Moderators can kick, ban/unban, pin/unpin users and clear the Syncshell."); if (_uiSharedService.IconTextButton(FontAwesomeIcon.Crown, "Transfer Ownership") && UiSharedService.CtrlPressed() && UiSharedService.ShiftPressed()) { ImGui.CloseCurrentPopup(); _ = _apiController.GroupChangeOwnership(_fullInfoDto); } UiSharedService.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (_fullInfoDto.UserAliasOrUID) + Environment.NewLine + "WARNING: This action is irreversible."); } if (userIsOwner || (userIsModerator && !(entryIsMod || entryIsOwner))) ImGui.Separator(); if (_pair.IsVisible) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.Eye, "Target player")) { _mediator.Publish(new TargetPairMessage(_pair)); ImGui.CloseCurrentPopup(); } } if (!_pair.IsPaused) { if (_uiSharedService.IconTextButton(FontAwesomeIcon.User, "Open Profile")) { _displayHandler.OpenProfile(_pair); ImGui.CloseCurrentPopup(); } } if (_pair.IsVisible) { #if DEBUG if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Open Analysis")) { _displayHandler.OpenAnalysis(_pair); ImGui.CloseCurrentPopup(); } #endif if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data")) { _pair.ApplyLastReceivedData(forced: true); ImGui.CloseCurrentPopup(); } UiSharedService.AttachToolTip("This reapplies the last received character data to this character"); } ImGui.EndPopup(); } return pos - spacing; } }