2025-08-22 02:19:48 +01:00
using Dalamud.Bindings.ImGui ;
using Dalamud.Game.Text ;
using Dalamud.Interface ;
using Dalamud.Interface.Colors ;
using Dalamud.Interface.Utility ;
using Dalamud.Interface.Utility.Raii ;
using Dalamud.Utility ;
using MareSynchronos.API.Data ;
using MareSynchronos.API.Data.Comparer ;
using MareSynchronos.FileCache ;
using MareSynchronos.Interop.Ipc ;
using MareSynchronos.MareConfiguration ;
using MareSynchronos.MareConfiguration.Models ;
using MareSynchronos.PlayerData.Handlers ;
using MareSynchronos.PlayerData.Pairs ;
using MareSynchronos.Services ;
using MareSynchronos.Services.Mediator ;
using MareSynchronos.Services.ServerConfiguration ;
using MareSynchronos.WebAPI ;
using MareSynchronos.WebAPI.Files ;
using MareSynchronos.WebAPI.Files.Models ;
using MareSynchronos.WebAPI.SignalR.Utils ;
using Microsoft.Extensions.Logging ;
using System.Collections.Concurrent ;
using System.Globalization ;
using System.Numerics ;
using System.Text.Json ;
namespace MareSynchronos.UI ;
public class SettingsUi : WindowMediatorSubscriberBase
{
private readonly ApiController _apiController ;
private readonly IpcManager _ipcManager ;
private readonly IpcProvider _ipcProvider ;
private readonly CacheMonitor _cacheMonitor ;
private readonly DalamudUtilService _dalamudUtilService ;
private readonly MareConfigService _configService ;
private readonly ConcurrentDictionary < GameObjectHandler , Dictionary < string , FileDownloadStatus > > _currentDownloads = new ( ) ;
private readonly FileCompactor _fileCompactor ;
private readonly FileUploadManager _fileTransferManager ;
private readonly FileTransferOrchestrator _fileTransferOrchestrator ;
private readonly FileCacheManager _fileCacheManager ;
private readonly PairManager _pairManager ;
private readonly ChatService _chatService ;
private readonly GuiHookService _guiHookService ;
private readonly PerformanceCollectorService _performanceCollector ;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService ;
private readonly PlayerPerformanceService _playerPerformanceService ;
private readonly AccountRegistrationService _registerService ;
private readonly ServerConfigurationManager _serverConfigurationManager ;
private readonly UiSharedService _uiShared ;
private bool _deleteAccountPopupModalShown = false ;
private string _lastTab = string . Empty ;
private bool? _notesSuccessfullyApplied = null ;
private bool _overwriteExistingLabels = false ;
private bool _readClearCache = false ;
private CancellationTokenSource ? _validationCts ;
private Task < List < FileCacheEntity > > ? _validationTask ;
private bool _wasOpen = false ;
private readonly IProgress < ( int , int , FileCacheEntity ) > _validationProgress ;
private ( int , int , FileCacheEntity ) _currentProgress ;
private bool _registrationInProgress = false ;
private bool _registrationSuccess = false ;
private string? _registrationMessage ;
public SettingsUi ( ILogger < SettingsUi > logger ,
UiSharedService uiShared , MareConfigService configService ,
PairManager pairManager , ChatService chatService , GuiHookService guiHookService ,
ServerConfigurationManager serverConfigurationManager ,
PlayerPerformanceConfigService playerPerformanceConfigService , PlayerPerformanceService playerPerformanceService ,
MareMediator mediator , PerformanceCollectorService performanceCollector ,
FileUploadManager fileTransferManager ,
FileTransferOrchestrator fileTransferOrchestrator ,
FileCacheManager fileCacheManager ,
FileCompactor fileCompactor , ApiController apiController ,
IpcManager ipcManager , IpcProvider ipcProvider , CacheMonitor cacheMonitor ,
2025-08-22 21:26:51 +01:00
DalamudUtilService dalamudUtilService , AccountRegistrationService registerService ) : base ( logger , mediator , "Snowcloak Settings" , performanceCollector )
2025-08-22 02:19:48 +01:00
{
_configService = configService ;
_pairManager = pairManager ;
_chatService = chatService ;
_guiHookService = guiHookService ;
_serverConfigurationManager = serverConfigurationManager ;
_playerPerformanceConfigService = playerPerformanceConfigService ;
_playerPerformanceService = playerPerformanceService ;
_performanceCollector = performanceCollector ;
_fileTransferManager = fileTransferManager ;
_fileTransferOrchestrator = fileTransferOrchestrator ;
_fileCacheManager = fileCacheManager ;
_apiController = apiController ;
_ipcManager = ipcManager ;
_ipcProvider = ipcProvider ;
_cacheMonitor = cacheMonitor ;
_dalamudUtilService = dalamudUtilService ;
_registerService = registerService ;
_fileCompactor = fileCompactor ;
_uiShared = uiShared ;
AllowClickthrough = false ;
AllowPinning = false ;
_validationProgress = new Progress < ( int , int , FileCacheEntity ) > ( v = > _currentProgress = v ) ;
SizeConstraints = new WindowSizeConstraints ( )
{
MinimumSize = new Vector2 ( 600 , 400 ) ,
MaximumSize = new Vector2 ( 600 , 2000 ) ,
} ;
Mediator . Subscribe < OpenSettingsUiMessage > ( this , ( _ ) = > Toggle ( ) ) ;
Mediator . Subscribe < SwitchToIntroUiMessage > ( this , ( _ ) = > IsOpen = false ) ;
Mediator . Subscribe < CutsceneStartMessage > ( this , ( _ ) = > UiSharedService_GposeStart ( ) ) ;
Mediator . Subscribe < CutsceneEndMessage > ( this , ( _ ) = > UiSharedService_GposeEnd ( ) ) ;
Mediator . Subscribe < CharacterDataCreatedMessage > ( this , ( msg ) = > LastCreatedCharacterData = msg . CharacterData ) ;
Mediator . Subscribe < DownloadStartedMessage > ( this , ( msg ) = > _currentDownloads [ msg . DownloadId ] = msg . DownloadStatus ) ;
Mediator . Subscribe < DownloadFinishedMessage > ( this , ( msg ) = > _currentDownloads . TryRemove ( msg . DownloadId , out _ ) ) ;
}
public CharacterData ? LastCreatedCharacterData { private get ; set ; }
private ApiController ApiController = > _uiShared . ApiController ;
protected override void DrawInternal ( )
{
_ = _uiShared . DrawOtherPluginState ( ) ;
DrawSettingsContent ( ) ;
}
public override void OnClose ( )
{
_uiShared . EditTrackerPosition = false ;
base . OnClose ( ) ;
}
private void DrawBlockedTransfers ( )
{
_lastTab = "BlockedTransfers" ;
UiSharedService . ColorTextWrapped ( "Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. " +
"If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. " +
"Ask your paired friend to send you the mod in question through other means or acquire the mod yourself." ,
ImGuiColors . DalamudGrey ) ;
if ( ImGui . BeginTable ( "TransfersTable" , 2 , ImGuiTableFlags . SizingStretchProp ) )
{
ImGui . TableSetupColumn (
$"Hash/Filename" ) ;
ImGui . TableSetupColumn ( $"Forbidden by" ) ;
ImGui . TableHeadersRow ( ) ;
foreach ( var item in _fileTransferOrchestrator . ForbiddenTransfers )
{
ImGui . TableNextColumn ( ) ;
if ( item is UploadFileTransfer transfer )
{
ImGui . TextUnformatted ( transfer . LocalFile ) ;
}
else
{
ImGui . TextUnformatted ( item . Hash ) ;
}
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( item . ForbiddenBy ) ;
}
ImGui . EndTable ( ) ;
}
}
private void DrawCurrentTransfers ( )
{
_lastTab = "Transfers" ;
_uiShared . BigText ( "Transfer Settings" ) ;
int maxParallelDownloads = _configService . Current . ParallelDownloads ;
int downloadSpeedLimit = _configService . Current . DownloadSpeedLimitInBytes ;
ImGui . AlignTextToFramePadding ( ) ;
ImGui . TextUnformatted ( "Global Download Speed Limit" ) ;
ImGui . SameLine ( ) ;
ImGui . SetNextItemWidth ( 100 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . InputInt ( "###speedlimit" , ref downloadSpeedLimit ) )
{
_configService . Current . DownloadSpeedLimitInBytes = downloadSpeedLimit ;
_configService . Save ( ) ;
Mediator . Publish ( new DownloadLimitChangedMessage ( ) ) ;
}
ImGui . SameLine ( ) ;
ImGui . SetNextItemWidth ( 100 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "###speed" , [ DownloadSpeeds . Bps , DownloadSpeeds . KBps , DownloadSpeeds . MBps ] ,
( s ) = > s switch
{
DownloadSpeeds . Bps = > "Byte/s" ,
DownloadSpeeds . KBps = > "KB/s" ,
DownloadSpeeds . MBps = > "MB/s" ,
_ = > throw new NotSupportedException ( )
} , ( s ) = >
{
_configService . Current . DownloadSpeedType = s ;
_configService . Save ( ) ;
Mediator . Publish ( new DownloadLimitChangedMessage ( ) ) ;
} , _configService . Current . DownloadSpeedType ) ;
ImGui . SameLine ( ) ;
ImGui . AlignTextToFramePadding ( ) ;
ImGui . TextUnformatted ( "0 = No limit/infinite" ) ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . SliderInt ( "Maximum Parallel Downloads" , ref maxParallelDownloads , 1 , 10 ) )
{
_configService . Current . ParallelDownloads = maxParallelDownloads ;
_configService . Save ( ) ;
}
ImGui . Separator ( ) ;
_uiShared . BigText ( "Transfer UI" ) ;
bool showTransferWindow = _configService . Current . ShowTransferWindow ;
if ( ImGui . Checkbox ( "Show separate transfer window" , ref showTransferWindow ) )
{
_configService . Current . ShowTransferWindow = showTransferWindow ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( $"The download window will show the current progress of outstanding downloads.{Environment.NewLine}{Environment.NewLine}" +
$"What do W/Q/P/D stand for?{Environment.NewLine}W = Waiting for Slot (see Maximum Parallel Downloads){Environment.NewLine}" +
$"Q = Queued on Server, waiting for queue ready signal{Environment.NewLine}" +
$"P = Processing download (aka downloading){Environment.NewLine}" +
$"D = Decompressing download" ) ;
if ( ! _configService . Current . ShowTransferWindow ) ImGui . BeginDisabled ( ) ;
ImGui . Indent ( ) ;
bool editTransferWindowPosition = _uiShared . EditTrackerPosition ;
if ( ImGui . Checkbox ( "Edit Transfer Window position" , ref editTransferWindowPosition ) )
{
_uiShared . EditTrackerPosition = editTransferWindowPosition ;
}
ImGui . Unindent ( ) ;
if ( ! _configService . Current . ShowTransferWindow ) ImGui . EndDisabled ( ) ;
bool showTransferBars = _configService . Current . ShowTransferBars ;
if ( ImGui . Checkbox ( "Show transfer bars rendered below players" , ref showTransferBars ) )
{
_configService . Current . ShowTransferBars = showTransferBars ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will render a progress bar during the download at the feet of the player you are downloading from." ) ;
if ( ! showTransferBars ) ImGui . BeginDisabled ( ) ;
ImGui . Indent ( ) ;
bool transferBarShowText = _configService . Current . TransferBarsShowText ;
if ( ImGui . Checkbox ( "Show Download Text" , ref transferBarShowText ) )
{
_configService . Current . TransferBarsShowText = transferBarShowText ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Shows download text (amount of MiB downloaded) in the transfer bars" ) ;
int transferBarWidth = _configService . Current . TransferBarsWidth ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . SliderInt ( "Transfer Bar Width" , ref transferBarWidth , 0 , 500 ) )
{
if ( transferBarWidth < 10 )
transferBarWidth = 10 ;
_configService . Current . TransferBarsWidth = transferBarWidth ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Width of the displayed transfer bars (will never be less wide than the displayed text)" ) ;
int transferBarHeight = _configService . Current . TransferBarsHeight ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . SliderInt ( "Transfer Bar Height" , ref transferBarHeight , 0 , 50 ) )
{
if ( transferBarHeight < 2 )
transferBarHeight = 2 ;
_configService . Current . TransferBarsHeight = transferBarHeight ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Height of the displayed transfer bars (will never be less tall than the displayed text)" ) ;
bool showUploading = _configService . Current . ShowUploading ;
if ( ImGui . Checkbox ( "Show 'Uploading' text below players that are currently uploading" , ref showUploading ) )
{
_configService . Current . ShowUploading = showUploading ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will render an 'Uploading' text at the feet of the player that is in progress of uploading data." ) ;
ImGui . Unindent ( ) ;
if ( ! showUploading ) ImGui . BeginDisabled ( ) ;
ImGui . Indent ( ) ;
bool showUploadingBigText = _configService . Current . ShowUploadingBigText ;
if ( ImGui . Checkbox ( "Large font for 'Uploading' text" , ref showUploadingBigText ) )
{
_configService . Current . ShowUploadingBigText = showUploadingBigText ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will render an 'Uploading' text in a larger font." ) ;
ImGui . Unindent ( ) ;
if ( ! showUploading ) ImGui . EndDisabled ( ) ;
if ( ! showTransferBars ) ImGui . EndDisabled ( ) ;
ImGui . Separator ( ) ;
_uiShared . BigText ( "Current Transfers" ) ;
if ( ImGui . BeginTabBar ( "TransfersTabBar" ) )
{
if ( ApiController . ServerState is ServerState . Connected & & ImGui . BeginTabItem ( "Transfers" ) )
{
ImGui . TextUnformatted ( "Uploads" ) ;
if ( ImGui . BeginTable ( "UploadsTable" , 3 ) )
{
ImGui . TableSetupColumn ( "File" ) ;
ImGui . TableSetupColumn ( "Uploaded" ) ;
ImGui . TableSetupColumn ( "Size" ) ;
ImGui . TableHeadersRow ( ) ;
foreach ( var transfer in _fileTransferManager . CurrentUploads . ToArray ( ) )
{
var color = UiSharedService . UploadColor ( ( transfer . Transferred , transfer . Total ) ) ;
var col = ImRaii . PushColor ( ImGuiCol . Text , color ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( transfer . Hash ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( UiSharedService . ByteToString ( transfer . Transferred ) ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( UiSharedService . ByteToString ( transfer . Total ) ) ;
col . Dispose ( ) ;
ImGui . TableNextRow ( ) ;
}
ImGui . EndTable ( ) ;
}
ImGui . Separator ( ) ;
ImGui . TextUnformatted ( "Downloads" ) ;
if ( ImGui . BeginTable ( "DownloadsTable" , 4 ) )
{
ImGui . TableSetupColumn ( "User" ) ;
ImGui . TableSetupColumn ( "Server" ) ;
ImGui . TableSetupColumn ( "Files" ) ;
ImGui . TableSetupColumn ( "Download" ) ;
ImGui . TableHeadersRow ( ) ;
foreach ( var transfer in _currentDownloads . ToArray ( ) )
{
var userName = transfer . Key . Name ;
foreach ( var entry in transfer . Value )
{
var color = UiSharedService . UploadColor ( ( entry . Value . TransferredBytes , entry . Value . TotalBytes ) ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( userName ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( entry . Key ) ;
var col = ImRaii . PushColor ( ImGuiCol . Text , color ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( entry . Value . TransferredFiles + "/" + entry . Value . TotalFiles ) ;
ImGui . TableNextColumn ( ) ;
ImGui . TextUnformatted ( UiSharedService . ByteToString ( entry . Value . TransferredBytes ) + "/" + UiSharedService . ByteToString ( entry . Value . TotalBytes ) ) ;
ImGui . TableNextColumn ( ) ;
col . Dispose ( ) ;
ImGui . TableNextRow ( ) ;
}
}
ImGui . EndTable ( ) ;
}
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Blocked Transfers" ) )
{
DrawBlockedTransfers ( ) ;
ImGui . EndTabItem ( ) ;
}
ImGui . EndTabBar ( ) ;
}
}
private static readonly List < ( XivChatType , string ) > _syncshellChatTypes = [
( XivChatType . None , "(use global setting)" ) ,
( XivChatType . Debug , "Debug" ) ,
( XivChatType . Echo , "Echo" ) ,
( XivChatType . StandardEmote , "Standard Emote" ) ,
( XivChatType . CustomEmote , "Custom Emote" ) ,
( XivChatType . SystemMessage , "System Message" ) ,
( XivChatType . SystemError , "System Error" ) ,
( XivChatType . GatheringSystemMessage , "Gathering Message" ) ,
( XivChatType . ErrorMessage , "Error message" ) ,
] ;
private void DrawChatConfig ( )
{
_lastTab = "Chat" ;
_uiShared . BigText ( "Chat Settings" ) ;
var disableSyncshellChat = _configService . Current . DisableSyncshellChat ;
if ( ImGui . Checkbox ( "Disable chat globally" , ref disableSyncshellChat ) )
{
_configService . Current . DisableSyncshellChat = disableSyncshellChat ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Global setting to disable chat for all syncshells." ) ;
using var pushDisableGlobal = ImRaii . Disabled ( disableSyncshellChat ) ;
var uiColors = _dalamudUtilService . UiColors . Value ;
int globalChatColor = _configService . Current . ChatColor ;
if ( globalChatColor ! = 0 & & ! uiColors . ContainsKey ( globalChatColor ) )
{
globalChatColor = 0 ;
_configService . Current . ChatColor = 0 ;
_configService . Save ( ) ;
}
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawColorCombo ( "Chat text color" , Enumerable . Concat ( [ 0 ] , uiColors . Keys ) ,
i = > i switch
{
0 = > ( uiColors [ ChatService . DefaultColor ] . Dark , "Plugin Default" ) ,
_ = > ( uiColors [ i ] . Dark , $"[{i}] Sample Text" )
} ,
i = > {
_configService . Current . ChatColor = i ;
_configService . Save ( ) ;
} , globalChatColor ) ;
int globalChatType = _configService . Current . ChatLogKind ;
int globalChatTypeIdx = _syncshellChatTypes . FindIndex ( x = > globalChatType = = ( int ) x . Item1 ) ;
if ( globalChatTypeIdx = = - 1 )
globalChatTypeIdx = 0 ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "Chat channel" , Enumerable . Range ( 1 , _syncshellChatTypes . Count - 1 ) , i = > $"{_syncshellChatTypes[i].Item2}" ,
i = > {
if ( _configService . Current . ChatLogKind = = ( int ) _syncshellChatTypes [ i ] . Item1 )
return ;
_configService . Current . ChatLogKind = ( int ) _syncshellChatTypes [ i ] . Item1 ;
_chatService . PrintChannelExample ( $"Selected channel: {_syncshellChatTypes[i].Item2}" ) ;
_configService . Save ( ) ;
} , globalChatTypeIdx ) ;
_uiShared . DrawHelpText ( "FFXIV chat channel to output chat messages on." ) ;
ImGui . SetWindowFontScale ( 0.6f ) ;
_uiShared . BigText ( "\"Chat 2\" Plugin Integration" ) ;
ImGui . SetWindowFontScale ( 1.0f ) ;
var extraChatTags = _configService . Current . ExtraChatTags ;
if ( ImGui . Checkbox ( "Tag messages as ExtraChat" , ref extraChatTags ) )
{
_configService . Current . ExtraChatTags = extraChatTags ;
if ( ! extraChatTags )
_configService . Current . ExtraChatAPI = false ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "If enabled, messages will be filtered under the category \"ExtraChat channels: All\".\n\nThis works even if ExtraChat is also installed and enabled." ) ;
ImGui . Separator ( ) ;
_uiShared . BigText ( "Syncshell Settings" ) ;
if ( ! ApiController . ServerAlive )
{
ImGui . TextUnformatted ( "Connect to the server to configure individual syncshell settings." ) ;
return ;
}
if ( _pairManager . Groups . Count = = 0 )
{
ImGui . TextUnformatted ( "Once you join a syncshell you can configure its chat settings here." ) ;
return ;
}
foreach ( var group in _pairManager . Groups . OrderBy ( k = > k . Key . GID , StringComparer . Ordinal ) )
{
var gid = group . Key . GID ;
using var pushId = ImRaii . PushId ( gid ) ;
var shellConfig = _serverConfigurationManager . GetShellConfigForGid ( gid ) ;
var shellNumber = shellConfig . ShellNumber ;
var shellEnabled = shellConfig . Enabled ;
var shellName = _serverConfigurationManager . GetNoteForGid ( gid ) ? ? group . Key . AliasOrGID ;
if ( shellEnabled )
shellName = $"[{shellNumber}] {shellName}" ;
ImGui . SetWindowFontScale ( 0.6f ) ;
_uiShared . BigText ( shellName ) ;
ImGui . SetWindowFontScale ( 1.0f ) ;
using var pushIndent = ImRaii . PushIndent ( ) ;
if ( ImGui . Checkbox ( $"Enable chat for this syncshell##{gid}" , ref shellEnabled ) )
{
// If there is an active group with the same syncshell number, pick a new one
int nextNumber = 1 ;
bool conflict = false ;
foreach ( var otherGroup in _pairManager . Groups )
{
if ( gid . Equals ( otherGroup . Key . GID , StringComparison . Ordinal ) ) continue ;
var otherShellConfig = _serverConfigurationManager . GetShellConfigForGid ( otherGroup . Key . GID ) ;
if ( otherShellConfig . Enabled & & otherShellConfig . ShellNumber = = shellNumber )
conflict = true ;
nextNumber = Math . Max ( nextNumber , otherShellConfig . ShellNumber ) + 1 ;
}
if ( conflict )
shellConfig . ShellNumber = nextNumber ;
shellConfig . Enabled = shellEnabled ;
_serverConfigurationManager . SaveShellConfigForGid ( gid , shellConfig ) ;
}
using var pushDisabled = ImRaii . Disabled ( ! shellEnabled ) ;
ImGui . SetNextItemWidth ( 50 * ImGuiHelpers . GlobalScale ) ;
// _uiShared.DrawCombo() remembers the selected option -- we don't want that, because the value can change
if ( ImGui . BeginCombo ( "Syncshell number##{gid}" , $"{shellNumber}" ) )
{
// Same hard-coded number in CommandManagerService
for ( int i = 1 ; i < = ChatService . CommandMaxNumber ; + + i )
{
if ( ImGui . Selectable ( $"{i}" , i = = shellNumber ) )
{
// Find an active group with the same syncshell number as selected, and swap it
// This logic can leave duplicate IDs present in the config but its not critical
foreach ( var otherGroup in _pairManager . Groups )
{
if ( gid . Equals ( otherGroup . Key . GID , StringComparison . Ordinal ) ) continue ;
var otherShellConfig = _serverConfigurationManager . GetShellConfigForGid ( otherGroup . Key . GID ) ;
if ( otherShellConfig . Enabled & & otherShellConfig . ShellNumber = = i )
{
otherShellConfig . ShellNumber = shellNumber ;
_serverConfigurationManager . SaveShellConfigForGid ( otherGroup . Key . GID , otherShellConfig ) ;
break ;
}
}
shellConfig . ShellNumber = i ;
_serverConfigurationManager . SaveShellConfigForGid ( gid , shellConfig ) ;
}
}
ImGui . EndCombo ( ) ;
}
if ( shellConfig . Color ! = 0 & & ! uiColors . ContainsKey ( shellConfig . Color ) )
{
shellConfig . Color = 0 ;
_serverConfigurationManager . SaveShellConfigForGid ( gid , shellConfig ) ;
}
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawColorCombo ( $"Chat text color##{gid}" , Enumerable . Concat ( [ 0 ] , uiColors . Keys ) ,
i = > i switch
{
0 = > ( uiColors [ globalChatColor > 0 ? globalChatColor : ChatService . DefaultColor ] . Dark , "(use global setting)" ) ,
_ = > ( uiColors [ i ] . Dark , $"[{i}] Sample Text" )
} ,
i = > {
shellConfig . Color = i ;
_serverConfigurationManager . SaveShellConfigForGid ( gid , shellConfig ) ;
} , shellConfig . Color ) ;
int shellChatTypeIdx = _syncshellChatTypes . FindIndex ( x = > shellConfig . LogKind = = ( int ) x . Item1 ) ;
if ( shellChatTypeIdx = = - 1 )
shellChatTypeIdx = 0 ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( $"Chat channel##{gid}" , Enumerable . Range ( 0 , _syncshellChatTypes . Count ) , i = > $"{_syncshellChatTypes[i].Item2}" ,
i = > {
shellConfig . LogKind = ( int ) _syncshellChatTypes [ i ] . Item1 ;
_serverConfigurationManager . SaveShellConfigForGid ( gid , shellConfig ) ;
} , shellChatTypeIdx ) ;
_uiShared . DrawHelpText ( "Override the FFXIV chat channel used for this syncshell." ) ;
}
}
private void DrawAdvanced ( )
{
_lastTab = "Advanced" ;
_uiShared . BigText ( "Advanced" ) ;
bool mareApi = _configService . Current . MareAPI ;
2025-08-27 04:22:09 -04:00
if ( ImGui . Checkbox ( "Enable Snowcloak Sync API" , ref mareApi ) )
2025-08-22 02:19:48 +01:00
{
_configService . Current . MareAPI = mareApi ;
_configService . Save ( ) ;
_ipcProvider . HandleMareImpersonation ( ) ;
}
2025-08-27 04:22:09 -04:00
_uiShared . DrawHelpText ( "Enables handling of the Snowcloak Sync API. This currently includes:\n\n" +
2025-08-22 02:19:48 +01:00
" - MCDF loading support for other plugins\n" +
" - Blocking Moodles applications to paired users\n\n" +
2025-08-27 04:22:09 -04:00
"If the Snowcloak Sync plugin is loaded while this option is enabled, control of its API will be relinquished." ) ;
2025-08-22 02:19:48 +01:00
using ( _ = ImRaii . PushIndent ( ) )
{
ImGui . SameLine ( 300.0f * ImGuiHelpers . GlobalScale ) ;
if ( _ipcProvider . ImpersonationActive )
{
2025-08-27 04:22:09 -04:00
UiSharedService . ColorTextWrapped ( "Snowcloak API active!" , ImGuiColors . HealerGreen ) ;
2025-08-22 02:19:48 +01:00
}
else
{
if ( ! mareApi )
2025-08-27 04:22:09 -04:00
UiSharedService . ColorTextWrapped ( "Snowcloak API inactive: Option is disabled" , ImGuiColors . DalamudYellow ) ;
2025-08-22 02:19:48 +01:00
else if ( _ipcProvider . MarePluginEnabled )
2025-08-27 04:22:09 -04:00
UiSharedService . ColorTextWrapped ( "Snowcloak API inactive: Snowcloak plugin is loaded" , ImGuiColors . DalamudYellow ) ;
2025-08-22 02:19:48 +01:00
else
2025-08-27 04:22:09 -04:00
UiSharedService . ColorTextWrapped ( "Snowcloak API inactive: Unknown reason" , ImGuiColors . DalamudRed ) ;
2025-08-22 02:19:48 +01:00
}
}
bool logEvents = _configService . Current . LogEvents ;
if ( ImGui . Checkbox ( "Log Event Viewer data to disk" , ref logEvents ) )
{
_configService . Current . LogEvents = logEvents ;
_configService . Save ( ) ;
}
ImGui . SameLine ( 300.0f * ImGuiHelpers . GlobalScale ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . NotesMedical , "Open Event Viewer" ) )
{
Mediator . Publish ( new UiToggleMessage ( typeof ( EventViewerUI ) ) ) ;
}
bool holdCombatApplication = _configService . Current . HoldCombatApplication ;
if ( ImGui . Checkbox ( "Hold application during combat" , ref holdCombatApplication ) )
{
if ( ! holdCombatApplication )
Mediator . Publish ( new CombatOrPerformanceEndMessage ( ) ) ;
_configService . Current . HoldCombatApplication = holdCombatApplication ;
_configService . Save ( ) ;
}
bool serializedApplications = _configService . Current . SerialApplication ;
if ( ImGui . Checkbox ( "Serialized player applications" , ref serializedApplications ) )
{
_configService . Current . SerialApplication = serializedApplications ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Experimental - May reduce issues in crowded areas" ) ;
ImGui . Separator ( ) ;
_uiShared . BigText ( "Debug" ) ;
#if DEBUG
if ( LastCreatedCharacterData ! = null & & ImGui . TreeNode ( "Last created character data" ) )
{
foreach ( var l in JsonSerializer . Serialize ( LastCreatedCharacterData , new JsonSerializerOptions ( ) { WriteIndented = true } ) . Split ( '\n' ) )
{
ImGui . TextUnformatted ( $"{l}" ) ;
}
ImGui . TreePop ( ) ;
}
#endif
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Copy , "[DEBUG] Copy Last created Character Data to clipboard" ) )
{
if ( LastCreatedCharacterData ! = null )
{
ImGui . SetClipboardText ( JsonSerializer . Serialize ( LastCreatedCharacterData , new JsonSerializerOptions ( ) { WriteIndented = true } ) ) ;
}
else
{
ImGui . SetClipboardText ( "ERROR: No created character data, cannot copy." ) ;
}
}
UiSharedService . AttachToolTip ( "Use this when reporting mods being rejected from the server." ) ;
_uiShared . DrawCombo ( "Log Level" , Enum . GetValues < LogLevel > ( ) , ( l ) = > l . ToString ( ) , ( l ) = >
{
_configService . Current . LogLevel = l ;
_configService . Save ( ) ;
} , _configService . Current . LogLevel ) ;
bool logPerformance = _configService . Current . LogPerformance ;
if ( ImGui . Checkbox ( "Log Performance Counters" , ref logPerformance ) )
{
_configService . Current . LogPerformance = logPerformance ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Enabling this can incur a (slight) performance impact. Enabling this for extended periods of time is not recommended." ) ;
using ( ImRaii . Disabled ( ! logPerformance ) )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . StickyNote , "Print Performance Stats to /xllog" ) )
{
_performanceCollector . PrintPerformanceStats ( ) ;
}
ImGui . SameLine ( ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . StickyNote , "Print Performance Stats (last 60s) to /xllog" ) )
{
_performanceCollector . PrintPerformanceStats ( 60 ) ;
}
}
if ( ImGui . TreeNode ( "Active Character Blocks" ) )
{
var onlinePairs = _pairManager . GetOnlineUserPairs ( ) ;
foreach ( var pair in onlinePairs )
{
if ( pair . IsApplicationBlocked )
{
ImGui . TextUnformatted ( pair . PlayerName ) ;
ImGui . SameLine ( ) ;
ImGui . TextUnformatted ( string . Join ( ", " , pair . HoldApplicationReasons ) ) ;
}
}
}
}
private void DrawFileStorageSettings ( )
{
_lastTab = "FileCache" ;
_uiShared . BigText ( "Export MCDF" ) ;
ImGuiHelpers . ScaledDummy ( 10 ) ;
UiSharedService . ColorTextWrapped ( "Exporting MCDF has moved." , ImGuiColors . DalamudYellow ) ;
ImGuiHelpers . ScaledDummy ( 5 ) ;
UiSharedService . TextWrapped ( "It is now found in the Main UI under \"Character Data Hub\"" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Running , "Open Character Data Hub" ) )
{
Mediator . Publish ( new UiToggleMessage ( typeof ( CharaDataHubUi ) ) ) ;
}
ImGui . Separator ( ) ;
_uiShared . BigText ( "Storage" ) ;
2025-08-22 21:26:51 +01:00
UiSharedService . TextWrapped ( "Snowcloak stores downloaded files from paired people permanently. This is to improve loading performance and requiring less downloads. " +
2025-08-22 02:19:48 +01:00
"The storage governs itself by clearing data beyond the set storage size. Please set the storage size accordingly. It is not necessary to manually clear the storage." ) ;
_uiShared . DrawFileScanState ( ) ;
ImGui . AlignTextToFramePadding ( ) ;
ImGui . TextUnformatted ( "Monitoring Penumbra Folder: " + ( _cacheMonitor . PenumbraWatcher ? . Path ? ? "Not monitoring" ) ) ;
if ( string . IsNullOrEmpty ( _cacheMonitor . PenumbraWatcher ? . Path ) )
{
ImGui . SameLine ( ) ;
using var id = ImRaii . PushId ( "penumbraMonitor" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . ArrowsToCircle , "Try to reinitialize Monitor" ) )
{
_cacheMonitor . StartPenumbraWatcher ( _ipcManager . Penumbra . ModDirectory ) ;
}
}
ImGui . AlignTextToFramePadding ( ) ;
2025-08-22 21:26:51 +01:00
ImGui . TextUnformatted ( "Monitoring Snowcloak Storage Folder: " + ( _cacheMonitor . MareWatcher ? . Path ? ? "Not monitoring" ) ) ;
2025-08-22 02:19:48 +01:00
if ( string . IsNullOrEmpty ( _cacheMonitor . MareWatcher ? . Path ) )
{
ImGui . SameLine ( ) ;
using var id = ImRaii . PushId ( "mareMonitor" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . ArrowsToCircle , "Try to reinitialize Monitor" ) )
{
_cacheMonitor . StartMareWatcher ( _configService . Current . CacheFolder ) ;
}
}
if ( _cacheMonitor . MareWatcher = = null | | _cacheMonitor . PenumbraWatcher = = null )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Play , "Resume Monitoring" ) )
{
_cacheMonitor . StartMareWatcher ( _configService . Current . CacheFolder ) ;
_cacheMonitor . StartPenumbraWatcher ( _ipcManager . Penumbra . ModDirectory ) ;
_cacheMonitor . InvokeScan ( ) ;
}
2025-08-22 21:26:51 +01:00
UiSharedService . AttachToolTip ( "Attempts to resume monitoring for both Penumbra and Snowcloak Storage. "
2025-08-22 02:19:48 +01:00
+ "Resuming the monitoring will also force a full scan to run." + Environment . NewLine
+ "If the button remains present after clicking it, consult /xllog for errors" ) ;
}
else
{
using ( ImRaii . Disabled ( ! UiSharedService . CtrlPressed ( ) ) )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Stop , "Stop Monitoring" ) )
{
_cacheMonitor . StopMonitoring ( ) ;
}
}
2025-08-22 21:26:51 +01:00
UiSharedService . AttachToolTip ( "Stops the monitoring for both Penumbra and Snowcloak Storage. "
+ "Do not stop the monitoring, unless you plan to move the Penumbra and Snowcloak Storage folders, to ensure correct functionality of Snowcloak." + Environment . NewLine
2025-08-22 02:19:48 +01:00
+ "If you stop the monitoring to move folders around, resume it after you are finished moving the files."
+ UiSharedService . TooltipSeparator + "Hold CTRL to enable this button" ) ;
}
_uiShared . DrawCacheDirectorySetting ( ) ;
ImGui . AlignTextToFramePadding ( ) ;
if ( _cacheMonitor . FileCacheSize > = 0 )
ImGui . TextUnformatted ( $"Currently utilized local storage: {_cacheMonitor.FileCacheSize / 1024.0 / 1024.0 / 1024.0:0.00} GiB" ) ;
else
ImGui . TextUnformatted ( $"Currently utilized local storage: Calculating..." ) ;
bool isLinux = _dalamudUtilService . IsWine ;
if ( ! isLinux )
ImGui . TextUnformatted ( $"Remaining space free on drive: {_cacheMonitor.FileCacheDriveFree / 1024.0 / 1024.0 / 1024.0:0.00} GiB" ) ;
bool useFileCompactor = _configService . Current . UseCompactor ;
if ( ! useFileCompactor & & ! isLinux )
{
2025-08-22 21:26:51 +01:00
UiSharedService . ColorTextWrapped ( "Hint: To free up space when using Snowcloak consider enabling the File Compactor" , ImGuiColors . DalamudYellow ) ;
2025-08-22 02:19:48 +01:00
}
if ( isLinux | | ! _cacheMonitor . StorageisNTFS ) ImGui . BeginDisabled ( ) ;
if ( ImGui . Checkbox ( "Use file compactor" , ref useFileCompactor ) )
{
_configService . Current . UseCompactor = useFileCompactor ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "The file compactor can massively reduce your saved files. It might incur a minor penalty on loading files on a slow CPU." + Environment . NewLine
+ "It is recommended to leave it enabled to save on space." ) ;
if ( ! _fileCompactor . MassCompactRunning )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . FileArchive , "Compact all files in storage" ) )
{
_ = Task . Run ( ( ) = >
{
_fileCompactor . CompactStorage ( compress : true ) ;
_cacheMonitor . RecalculateFileCacheSize ( CancellationToken . None ) ;
} ) ;
}
UiSharedService . AttachToolTip ( "This will run compression on all files in your current storage folder." + Environment . NewLine
+ "You do not need to run this manually if you keep the file compactor enabled." ) ;
ImGui . SameLine ( ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . File , "Decompact all files in storage" ) )
{
_ = Task . Run ( ( ) = >
{
_fileCompactor . CompactStorage ( compress : false ) ;
_cacheMonitor . RecalculateFileCacheSize ( CancellationToken . None ) ;
} ) ;
}
UiSharedService . AttachToolTip ( "This will run decompression on all files in your current storage folder." ) ;
}
else
{
UiSharedService . ColorText ( $"File compactor currently running ({_fileCompactor.Progress})" , ImGuiColors . DalamudYellow ) ;
}
if ( isLinux | | ! _cacheMonitor . StorageisNTFS )
{
ImGui . EndDisabled ( ) ;
ImGui . TextUnformatted ( "The file compactor is only available on Windows and NTFS drives." ) ;
}
ImGuiHelpers . ScaledDummy ( new Vector2 ( 10 , 10 ) ) ;
ImGui . Separator ( ) ;
UiSharedService . TextWrapped ( "File Storage validation can make sure that all files in your local storage folder are valid. " +
"Run the validation before you clear the Storage for no reason. " + Environment . NewLine +
"This operation, depending on how many files you have in your storage, can take a while and will be CPU and drive intensive." ) ;
using ( ImRaii . Disabled ( _validationTask ! = null & & ! _validationTask . IsCompleted ) )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Check , "Start File Storage Validation" ) )
{
_validationCts ? . Cancel ( ) ;
_validationCts ? . Dispose ( ) ;
_validationCts = new ( ) ;
var token = _validationCts . Token ;
_validationTask = Task . Run ( ( ) = > _fileCacheManager . ValidateLocalIntegrity ( _validationProgress , token ) ) ;
}
}
if ( _validationTask ! = null & & ! _validationTask . IsCompleted )
{
ImGui . SameLine ( ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Times , "Cancel" ) )
{
_validationCts ? . Cancel ( ) ;
}
}
if ( _validationTask ! = null )
{
using ( ImRaii . PushIndent ( 20f ) )
{
if ( _validationTask . IsCompleted )
{
UiSharedService . TextWrapped ( $"The storage validation has completed and removed {_validationTask.Result.Count} invalid files from storage." ) ;
}
else
{
UiSharedService . TextWrapped ( $"Storage validation is running: {_currentProgress.Item1}/{_currentProgress.Item2}" ) ;
UiSharedService . TextWrapped ( $"Current item: {_currentProgress.Item3.ResolvedFilepath}" ) ;
}
}
}
ImGui . Separator ( ) ;
ImGuiHelpers . ScaledDummy ( new Vector2 ( 10 , 10 ) ) ;
ImGui . TextUnformatted ( "To clear the local storage accept the following disclaimer" ) ;
ImGui . Indent ( ) ;
ImGui . Checkbox ( "##readClearCache" , ref _readClearCache ) ;
ImGui . SameLine ( ) ;
UiSharedService . TextWrapped ( "I understand that: " + Environment . NewLine + "- By clearing the local storage I put the file servers of my connected service under extra strain by having to redownload all data."
+ Environment . NewLine + "- This is not a step to try to fix sync issues."
+ Environment . NewLine + "- This can make the situation of not getting other players data worse in situations of heavy file server load." ) ;
if ( ! _readClearCache )
ImGui . BeginDisabled ( ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Trash , "Clear local storage" ) & & UiSharedService . CtrlPressed ( ) & & _readClearCache )
{
_ = Task . Run ( ( ) = >
{
foreach ( var file in Directory . GetFiles ( _configService . Current . CacheFolder ) )
{
File . Delete ( file ) ;
}
} ) ;
}
UiSharedService . AttachToolTip ( "You normally do not need to do this. THIS IS NOT SOMETHING YOU SHOULD BE DOING TO TRY TO FIX SYNC ISSUES." + Environment . NewLine
+ "This will solely remove all downloaded data from all players and will require you to re-download everything again." + Environment . NewLine
2025-08-22 21:26:51 +01:00
+ "Snowcloak's storage is self-clearing and will not surpass the limit you have set it to." + Environment . NewLine
2025-08-22 02:19:48 +01:00
+ "If you still think you need to do this hold CTRL while pressing the button." ) ;
if ( ! _readClearCache )
ImGui . EndDisabled ( ) ;
ImGui . Unindent ( ) ;
}
private void DrawGeneral ( )
{
if ( ! string . Equals ( _lastTab , "General" , StringComparison . OrdinalIgnoreCase ) )
{
_notesSuccessfullyApplied = null ;
}
_lastTab = "General" ;
_uiShared . BigText ( "Notes" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . StickyNote , "Export all your user notes to clipboard" ) )
{
ImGui . SetClipboardText ( UiSharedService . GetNotes ( _pairManager . DirectPairs . UnionBy ( _pairManager . GroupPairs . SelectMany ( p = > p . Value ) , p = > p . UserData , UserDataComparer . Instance ) . ToList ( ) ) ) ;
}
if ( _uiShared . IconTextButton ( FontAwesomeIcon . FileImport , "Import notes from clipboard" ) )
{
_notesSuccessfullyApplied = null ;
var notes = ImGui . GetClipboardText ( ) ;
_notesSuccessfullyApplied = _uiShared . ApplyNotesFromClipboard ( notes , _overwriteExistingLabels ) ;
}
ImGui . SameLine ( ) ;
ImGui . Checkbox ( "Overwrite existing notes" , ref _overwriteExistingLabels ) ;
_uiShared . DrawHelpText ( "If this option is selected all already existing notes for UIDs will be overwritten by the imported notes." ) ;
if ( _notesSuccessfullyApplied . HasValue & & _notesSuccessfullyApplied . Value )
{
UiSharedService . ColorTextWrapped ( "User Notes successfully imported" , ImGuiColors . HealerGreen ) ;
}
else if ( _notesSuccessfullyApplied . HasValue & & ! _notesSuccessfullyApplied . Value )
{
UiSharedService . ColorTextWrapped ( "Attempt to import notes from clipboard failed. Check formatting and try again" , ImGuiColors . DalamudRed ) ;
}
var openPopupOnAddition = _configService . Current . OpenPopupOnAdd ;
if ( ImGui . Checkbox ( "Open Notes Popup on user addition" , ref openPopupOnAddition ) )
{
_configService . Current . OpenPopupOnAdd = openPopupOnAddition ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs." ) ;
ImGui . Separator ( ) ;
_uiShared . BigText ( "UI" ) ;
var showCharacterNames = _configService . Current . ShowCharacterNames ;
var showVisibleSeparate = _configService . Current . ShowVisibleUsersSeparately ;
var showOfflineSeparate = _configService . Current . ShowOfflineUsersSeparately ;
var showProfiles = _configService . Current . ProfilesShow ;
var showNsfwProfiles = _configService . Current . ProfilesAllowNsfw ;
var profileDelay = _configService . Current . ProfileDelay ;
var profileOnRight = _configService . Current . ProfilePopoutRight ;
var enableRightClickMenu = _configService . Current . EnableRightClickMenus ;
var enableDtrEntry = _configService . Current . EnableDtrEntry ;
var showUidInDtrTooltip = _configService . Current . ShowUidInDtrTooltip ;
var preferNoteInDtrTooltip = _configService . Current . PreferNoteInDtrTooltip ;
var useColorsInDtr = _configService . Current . UseColorsInDtr ;
var dtrColorsDefault = _configService . Current . DtrColorsDefault ;
var dtrColorsNotConnected = _configService . Current . DtrColorsNotConnected ;
var dtrColorsPairsInRange = _configService . Current . DtrColorsPairsInRange ;
if ( ImGui . Checkbox ( "Enable Game Right Click Menu Entries" , ref enableRightClickMenu ) )
{
_configService . Current . EnableRightClickMenus = enableRightClickMenu ;
_configService . Save ( ) ;
}
2025-08-22 21:26:51 +01:00
_uiShared . DrawHelpText ( "This will add Snowcloak related right click menu entries in the game UI on paired players." ) ;
2025-08-22 02:19:48 +01:00
if ( ImGui . Checkbox ( "Display status and visible pair count in Server Info Bar" , ref enableDtrEntry ) )
{
_configService . Current . EnableDtrEntry = enableDtrEntry ;
_configService . Save ( ) ;
}
2025-08-22 21:26:51 +01:00
_uiShared . DrawHelpText ( "This will add Snowcloak connection status and visible pair count in the Server Info Bar.\nYou can further configure this through your Dalamud Settings." ) ;
2025-08-22 02:19:48 +01:00
using ( ImRaii . Disabled ( ! enableDtrEntry ) )
{
using var indent = ImRaii . PushIndent ( ) ;
if ( ImGui . Checkbox ( "Show visible character's UID in tooltip" , ref showUidInDtrTooltip ) )
{
_configService . Current . ShowUidInDtrTooltip = showUidInDtrTooltip ;
_configService . Save ( ) ;
}
if ( ImGui . Checkbox ( "Prefer notes over player names in tooltip" , ref preferNoteInDtrTooltip ) )
{
_configService . Current . PreferNoteInDtrTooltip = preferNoteInDtrTooltip ;
_configService . Save ( ) ;
}
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "Server Info Bar style" , Enumerable . Range ( 0 , DtrEntry . NumStyles ) , ( i ) = > DtrEntry . RenderDtrStyle ( i , "123" ) ,
( i ) = >
{
_configService . Current . DtrStyle = i ;
_configService . Save ( ) ;
} , _configService . Current . DtrStyle ) ;
if ( ImGui . Checkbox ( "Color-code the Server Info Bar entry according to status" , ref useColorsInDtr ) )
{
_configService . Current . UseColorsInDtr = useColorsInDtr ;
_configService . Save ( ) ;
}
using ( ImRaii . Disabled ( ! useColorsInDtr ) )
{
using var indent2 = ImRaii . PushIndent ( ) ;
if ( InputDtrColors ( "Default" , ref dtrColorsDefault ) )
{
_configService . Current . DtrColorsDefault = dtrColorsDefault ;
_configService . Save ( ) ;
}
ImGui . SameLine ( ) ;
if ( InputDtrColors ( "Not Connected" , ref dtrColorsNotConnected ) )
{
_configService . Current . DtrColorsNotConnected = dtrColorsNotConnected ;
_configService . Save ( ) ;
}
ImGui . SameLine ( ) ;
if ( InputDtrColors ( "Pairs in Range" , ref dtrColorsPairsInRange ) )
{
_configService . Current . DtrColorsPairsInRange = dtrColorsPairsInRange ;
_configService . Save ( ) ;
}
}
}
var useNameColors = _configService . Current . UseNameColors ;
var nameColors = _configService . Current . NameColors ;
var autoPausedNameColors = _configService . Current . BlockedNameColors ;
if ( ImGui . Checkbox ( "Color nameplates of paired players" , ref useNameColors ) )
{
_configService . Current . UseNameColors = useNameColors ;
_configService . Save ( ) ;
_guiHookService . RequestRedraw ( ) ;
}
using ( ImRaii . Disabled ( ! useNameColors ) )
{
using var indent = ImRaii . PushIndent ( ) ;
if ( InputDtrColors ( "Character Name Color" , ref nameColors ) )
{
_configService . Current . NameColors = nameColors ;
_configService . Save ( ) ;
_guiHookService . RequestRedraw ( ) ;
}
ImGui . SameLine ( ) ;
if ( InputDtrColors ( "Blocked Character Color" , ref autoPausedNameColors ) )
{
_configService . Current . BlockedNameColors = autoPausedNameColors ;
_configService . Save ( ) ;
_guiHookService . RequestRedraw ( ) ;
}
}
if ( ImGui . Checkbox ( "Show separate Visible group" , ref showVisibleSeparate ) )
{
_configService . Current . ShowVisibleUsersSeparately = showVisibleSeparate ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will show all currently visible users in a special 'Visible' group in the main UI." ) ;
if ( ImGui . Checkbox ( "Show separate Offline group" , ref showOfflineSeparate ) )
{
_configService . Current . ShowOfflineUsersSeparately = showOfflineSeparate ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will show all currently offline users in a special 'Offline' group in the main UI." ) ;
if ( ImGui . Checkbox ( "Show player names" , ref showCharacterNames ) )
{
_configService . Current . ShowCharacterNames = showCharacterNames ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will show character names instead of UIDs when possible" ) ;
if ( ImGui . Checkbox ( "Show Profiles on Hover" , ref showProfiles ) )
{
Mediator . Publish ( new ClearProfileDataMessage ( ) ) ;
_configService . Current . ProfilesShow = showProfiles ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "This will show the configured user profile after a set delay" ) ;
ImGui . Indent ( ) ;
if ( ! showProfiles ) ImGui . BeginDisabled ( ) ;
if ( ImGui . Checkbox ( "Popout profiles on the right" , ref profileOnRight ) )
{
_configService . Current . ProfilePopoutRight = profileOnRight ;
_configService . Save ( ) ;
Mediator . Publish ( new CompactUiChange ( Vector2 . Zero , Vector2 . Zero ) ) ;
}
_uiShared . DrawHelpText ( "Will show profiles on the right side of the main UI" ) ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . SliderFloat ( "Hover Delay" , ref profileDelay , 1 , 10 ) )
{
_configService . Current . ProfileDelay = profileDelay ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Delay until the profile should be displayed" ) ;
if ( ! showProfiles ) ImGui . EndDisabled ( ) ;
ImGui . Unindent ( ) ;
if ( ImGui . Checkbox ( "Show profiles marked as NSFW" , ref showNsfwProfiles ) )
{
Mediator . Publish ( new ClearProfileDataMessage ( ) ) ;
_configService . Current . ProfilesAllowNsfw = showNsfwProfiles ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Will show profiles that have the NSFW tag enabled" ) ;
ImGui . Separator ( ) ;
var disableOptionalPluginWarnings = _configService . Current . DisableOptionalPluginWarnings ;
var onlineNotifs = _configService . Current . ShowOnlineNotifications ;
var onlineNotifsPairsOnly = _configService . Current . ShowOnlineNotificationsOnlyForIndividualPairs ;
var onlineNotifsNamedOnly = _configService . Current . ShowOnlineNotificationsOnlyForNamedPairs ;
_uiShared . BigText ( "Notifications" ) ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "Info Notification Display##settingsUi" , ( NotificationLocation [ ] ) Enum . GetValues ( typeof ( NotificationLocation ) ) , ( i ) = > i . ToString ( ) ,
( i ) = >
{
_configService . Current . InfoNotification = i ;
_configService . Save ( ) ;
} , _configService . Current . InfoNotification ) ;
_uiShared . DrawHelpText ( "The location where \"Info\" notifications will display."
+ Environment . NewLine + "'Nowhere' will not show any Info notifications"
+ Environment . NewLine + "'Chat' will print Info notifications in chat"
+ Environment . NewLine + "'Toast' will show Warning toast notifications in the bottom right corner"
+ Environment . NewLine + "'Both' will show chat as well as the toast notification" ) ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "Warning Notification Display##settingsUi" , ( NotificationLocation [ ] ) Enum . GetValues ( typeof ( NotificationLocation ) ) , ( i ) = > i . ToString ( ) ,
( i ) = >
{
_configService . Current . WarningNotification = i ;
_configService . Save ( ) ;
} , _configService . Current . WarningNotification ) ;
_uiShared . DrawHelpText ( "The location where \"Warning\" notifications will display."
+ Environment . NewLine + "'Nowhere' will not show any Warning notifications"
+ Environment . NewLine + "'Chat' will print Warning notifications in chat"
+ Environment . NewLine + "'Toast' will show Warning toast notifications in the bottom right corner"
+ Environment . NewLine + "'Both' will show chat as well as the toast notification" ) ;
ImGui . SetNextItemWidth ( 250 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawCombo ( "Error Notification Display##settingsUi" , ( NotificationLocation [ ] ) Enum . GetValues ( typeof ( NotificationLocation ) ) , ( i ) = > i . ToString ( ) ,
( i ) = >
{
_configService . Current . ErrorNotification = i ;
_configService . Save ( ) ;
} , _configService . Current . ErrorNotification ) ;
_uiShared . DrawHelpText ( "The location where \"Error\" notifications will display."
+ Environment . NewLine + "'Nowhere' will not show any Error notifications"
+ Environment . NewLine + "'Chat' will print Error notifications in chat"
+ Environment . NewLine + "'Toast' will show Error toast notifications in the bottom right corner"
+ Environment . NewLine + "'Both' will show chat as well as the toast notification" ) ;
if ( ImGui . Checkbox ( "Disable optional plugin warnings" , ref disableOptionalPluginWarnings ) )
{
_configService . Current . DisableOptionalPluginWarnings = disableOptionalPluginWarnings ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Enabling this will not show any \"Warning\" labeled messages for missing optional plugins." ) ;
if ( ImGui . Checkbox ( "Enable online notifications" , ref onlineNotifs ) )
{
_configService . Current . ShowOnlineNotifications = onlineNotifs ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Enabling this will show a small notification (type: Info) in the bottom right corner when pairs go online." ) ;
using ( ImRaii . Disabled ( ! onlineNotifs ) )
{
using var indent = ImRaii . PushIndent ( ) ;
if ( ImGui . Checkbox ( "Notify only for individual pairs" , ref onlineNotifsPairsOnly ) )
{
_configService . Current . ShowOnlineNotificationsOnlyForIndividualPairs = onlineNotifsPairsOnly ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Enabling this will only show online notifications (type: Info) for individual pairs." ) ;
if ( ImGui . Checkbox ( "Notify only for named pairs" , ref onlineNotifsNamedOnly ) )
{
_configService . Current . ShowOnlineNotificationsOnlyForNamedPairs = onlineNotifsNamedOnly ;
_configService . Save ( ) ;
}
_uiShared . DrawHelpText ( "Enabling this will only show online notifications (type: Info) for pairs where you have set an individual note." ) ;
}
}
private bool _perfUnapplied = false ;
private void DrawPerformance ( )
{
_uiShared . BigText ( "Performance Settings" ) ;
UiSharedService . TextWrapped ( "The configuration options here are to give you more informed warnings and automation when it comes to other performance-intensive synced players." ) ;
ImGui . Separator ( ) ;
bool recalculatePerformance = false ;
string? recalculatePerformanceUID = null ;
_uiShared . BigText ( "Global Configuration" ) ;
bool alwaysShrinkTextures = _playerPerformanceConfigService . Current . TextureShrinkMode = = TextureShrinkMode . Always ;
bool deleteOriginalTextures = _playerPerformanceConfigService . Current . TextureShrinkDeleteOriginal ;
using ( ImRaii . Disabled ( deleteOriginalTextures ) )
{
if ( ImGui . Checkbox ( "Shrink downloaded textures" , ref alwaysShrinkTextures ) )
{
if ( alwaysShrinkTextures )
_playerPerformanceConfigService . Current . TextureShrinkMode = TextureShrinkMode . Always ;
else
_playerPerformanceConfigService . Current . TextureShrinkMode = TextureShrinkMode . Never ;
_playerPerformanceConfigService . Save ( ) ;
recalculatePerformance = true ;
_cacheMonitor . ClearSubstStorage ( ) ;
}
}
_uiShared . DrawHelpText ( "Automatically shrinks texture resolution of synced players to reduce VRAM utilization." + UiSharedService . TooltipSeparator
+ "Texture Size Limit (DXT/BC5/BC7 Compressed): 2048x2048" + Environment . NewLine
+ "Texture Size Limit (A8R8G8B8 Uncompressed): 1024x1024" + UiSharedService . TooltipSeparator
+ "Enable to reduce lag in large crowds." + Environment . NewLine
+ "Disable this for higher quality during GPose." ) ;
using ( ImRaii . Disabled ( ! alwaysShrinkTextures | | _cacheMonitor . FileCacheSize < 0 ) )
{
using var indent = ImRaii . PushIndent ( ) ;
if ( ImGui . Checkbox ( "Delete original textures from disk" , ref deleteOriginalTextures ) )
{
_playerPerformanceConfigService . Current . TextureShrinkDeleteOriginal = deleteOriginalTextures ;
_playerPerformanceConfigService . Save ( ) ;
_ = Task . Run ( ( ) = >
{
_cacheMonitor . DeleteSubstOriginals ( ) ;
_cacheMonitor . RecalculateFileCacheSize ( CancellationToken . None ) ;
} ) ;
}
_uiShared . DrawHelpText ( "Deletes original, full-sized, textures from disk after downloading and shrinking." + UiSharedService . TooltipSeparator
+ "Caution!!! This will cause a re-download of all textures when the shrink option is disabled." ) ;
}
var totalVramBytes = _pairManager . GetOnlineUserPairs ( ) . Where ( p = > p . IsVisible & & p . LastAppliedApproximateVRAMBytes > 0 ) . Sum ( p = > p . LastAppliedApproximateVRAMBytes ) ;
ImGui . TextUnformatted ( "Current VRAM utilization by all nearby players:" ) ;
ImGui . SameLine ( ) ;
using ( ImRaii . PushColor ( ImGuiCol . Text , ImGuiColors . HealerGreen , totalVramBytes < 2.0 * 1024.0 * 1024.0 * 1024.0 ) )
using ( ImRaii . PushColor ( ImGuiCol . Text , ImGuiColors . DalamudYellow , totalVramBytes > = 4.0 * 1024.0 * 1024.0 * 1024.0 ) )
using ( ImRaii . PushColor ( ImGuiCol . Text , ImGuiColors . DalamudRed , totalVramBytes > = 6.0 * 1024.0 * 1024.0 * 1024.0 ) )
ImGui . TextUnformatted ( $"{totalVramBytes / 1024.0 / 1024.0 / 1024.0:0.00} GiB" ) ;
ImGui . Separator ( ) ;
_uiShared . BigText ( "Individual Limits" ) ;
bool autoPause = _playerPerformanceConfigService . Current . AutoPausePlayersExceedingThresholds ;
if ( ImGui . Checkbox ( "Automatically block players exceeding thresholds" , ref autoPause ) )
{
_playerPerformanceConfigService . Current . AutoPausePlayersExceedingThresholds = autoPause ;
_playerPerformanceConfigService . Save ( ) ;
recalculatePerformance = true ;
}
_uiShared . DrawHelpText ( "When enabled, it will automatically block the modded appearance of all players that exceed the thresholds defined below." + Environment . NewLine
+ "Will print a warning in chat when a player is blocked automatically." ) ;
using ( ImRaii . Disabled ( ! autoPause ) )
{
using var indent = ImRaii . PushIndent ( ) ;
var notifyDirectPairs = _playerPerformanceConfigService . Current . NotifyAutoPauseDirectPairs ;
var notifyGroupPairs = _playerPerformanceConfigService . Current . NotifyAutoPauseGroupPairs ;
if ( ImGui . Checkbox ( "Display auto-block warnings for individual pairs" , ref notifyDirectPairs ) )
{
_playerPerformanceConfigService . Current . NotifyAutoPauseDirectPairs = notifyDirectPairs ;
_playerPerformanceConfigService . Save ( ) ;
}
if ( ImGui . Checkbox ( "Display auto-block warnings for syncshell pairs" , ref notifyGroupPairs ) )
{
_playerPerformanceConfigService . Current . NotifyAutoPauseGroupPairs = notifyGroupPairs ;
_playerPerformanceConfigService . Save ( ) ;
}
var vramAuto = _playerPerformanceConfigService . Current . VRAMSizeAutoPauseThresholdMiB ;
var trisAuto = _playerPerformanceConfigService . Current . TrisAutoPauseThresholdThousands ;
ImGui . SetNextItemWidth ( 100 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . InputInt ( "Auto Block VRAM threshold" , ref vramAuto ) )
{
_playerPerformanceConfigService . Current . VRAMSizeAutoPauseThresholdMiB = vramAuto ;
_playerPerformanceConfigService . Save ( ) ;
_perfUnapplied = true ;
}
ImGui . SameLine ( ) ;
ImGui . Text ( "(MiB)" ) ;
_uiShared . DrawHelpText ( "When a loading in player and their VRAM usage exceeds this amount, automatically blocks the synced player." + UiSharedService . TooltipSeparator
+ "Default: 550 MiB" ) ;
ImGui . SetNextItemWidth ( 100 * ImGuiHelpers . GlobalScale ) ;
if ( ImGui . InputInt ( "Auto Block Triangle threshold" , ref trisAuto ) )
{
_playerPerformanceConfigService . Current . TrisAutoPauseThresholdThousands = trisAuto ;
_playerPerformanceConfigService . Save ( ) ;
_perfUnapplied = true ;
}
ImGui . SameLine ( ) ;
ImGui . Text ( "(thousand triangles)" ) ;
_uiShared . DrawHelpText ( "When a loading in player and their triangle count exceeds this amount, automatically blocks the synced player." + UiSharedService . TooltipSeparator
+ "Default: 375 thousand" ) ;
using ( ImRaii . Disabled ( ! _perfUnapplied ) )
{
if ( ImGui . Button ( "Apply Changes Now" ) )
{
recalculatePerformance = true ;
_perfUnapplied = false ;
}
}
}
#region Whitelist
ImGui . Separator ( ) ;
_uiShared . BigText ( "Whitelisted UIDs" ) ;
bool ignoreDirectPairs = _playerPerformanceConfigService . Current . IgnoreDirectPairs ;
if ( ImGui . Checkbox ( "Whitelist all individual pairs" , ref ignoreDirectPairs ) )
{
_playerPerformanceConfigService . Current . IgnoreDirectPairs = ignoreDirectPairs ;
_playerPerformanceConfigService . Save ( ) ;
recalculatePerformance = true ;
}
_uiShared . DrawHelpText ( "Individual pairs will never be affected by auto blocks." ) ;
ImGui . Dummy ( new Vector2 ( 5 ) ) ;
UiSharedService . TextWrapped ( "The entries in the list below will be not have auto block thresholds enforced." ) ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
var whitelistPos = ImGui . GetCursorPos ( ) ;
ImGui . SetCursorPosX ( 240 * ImGuiHelpers . GlobalScale ) ;
ImGui . InputText ( "##whitelistuid" , ref _uidToAddForIgnore , 20 ) ;
using ( ImRaii . Disabled ( string . IsNullOrEmpty ( _uidToAddForIgnore ) ) )
{
ImGui . SetCursorPosX ( 240 * ImGuiHelpers . GlobalScale ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Plus , "Add UID/Vanity ID to whitelist" ) )
{
if ( ! _serverConfigurationManager . IsUidWhitelisted ( _uidToAddForIgnore ) )
{
_serverConfigurationManager . AddWhitelistUid ( _uidToAddForIgnore ) ;
recalculatePerformance = true ;
recalculatePerformanceUID = _uidToAddForIgnore ;
}
_uidToAddForIgnore = string . Empty ;
}
}
ImGui . SetCursorPosX ( 240 * ImGuiHelpers . GlobalScale ) ;
_uiShared . DrawHelpText ( "Hint: UIDs are case sensitive.\nVanity IDs are also acceptable." ) ;
ImGui . Dummy ( new Vector2 ( 10 ) ) ;
var playerList = _serverConfigurationManager . Whitelist ;
if ( _selectedEntry > playerList . Count - 1 )
_selectedEntry = - 1 ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
ImGui . SetCursorPosY ( whitelistPos . Y ) ;
using ( var lb = ImRaii . ListBox ( "##whitelist" ) )
{
if ( lb )
{
for ( int i = 0 ; i < playerList . Count ; i + + )
{
bool shouldBeSelected = _selectedEntry = = i ;
if ( ImGui . Selectable ( playerList [ i ] + "##" + i , shouldBeSelected ) )
{
_selectedEntry = i ;
}
string? lastSeenName = _serverConfigurationManager . GetNameForUid ( playerList [ i ] ) ;
if ( lastSeenName ! = null )
{
ImGui . SameLine ( ) ;
_uiShared . IconText ( FontAwesomeIcon . InfoCircle ) ;
UiSharedService . AttachToolTip ( $"Last seen name: {lastSeenName}" ) ;
}
}
}
}
using ( ImRaii . Disabled ( _selectedEntry = = - 1 ) )
{
using var pushId = ImRaii . PushId ( "deleteSelectedWhitelist" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Trash , "Delete selected UID" ) )
{
_serverConfigurationManager . RemoveWhitelistUid ( _serverConfigurationManager . Whitelist [ _selectedEntry ] ) ;
if ( _selectedEntry > playerList . Count - 1 )
- - _selectedEntry ;
_playerPerformanceConfigService . Save ( ) ;
recalculatePerformance = true ;
}
}
#endregion Whitelist
#region Blacklist
ImGui . Separator ( ) ;
_uiShared . BigText ( "Blacklisted UIDs" ) ;
UiSharedService . TextWrapped ( "The entries in the list below will never have their characters displayed." ) ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
var blacklistPos = ImGui . GetCursorPos ( ) ;
ImGui . SetCursorPosX ( 240 * ImGuiHelpers . GlobalScale ) ;
ImGui . InputText ( "##uid" , ref _uidToAddForIgnoreBlacklist , 20 ) ;
using ( ImRaii . Disabled ( string . IsNullOrEmpty ( _uidToAddForIgnoreBlacklist ) ) )
{
ImGui . SetCursorPosX ( 240 * ImGuiHelpers . GlobalScale ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Plus , "Add UID/Vanity ID to blacklist" ) )
{
if ( ! _serverConfigurationManager . IsUidBlacklisted ( _uidToAddForIgnoreBlacklist ) )
{
_serverConfigurationManager . AddBlacklistUid ( _uidToAddForIgnoreBlacklist ) ;
recalculatePerformance = true ;
recalculatePerformanceUID = _uidToAddForIgnoreBlacklist ;
}
_uidToAddForIgnoreBlacklist = string . Empty ;
}
}
_uiShared . DrawHelpText ( "Hint: UIDs are case sensitive.\nVanity IDs are also acceptable." ) ;
ImGui . Dummy ( new Vector2 ( 10 ) ) ;
var blacklist = _serverConfigurationManager . Blacklist ;
if ( _selectedEntryBlacklist > blacklist . Count - 1 )
_selectedEntryBlacklist = - 1 ;
ImGui . SetNextItemWidth ( 200 * ImGuiHelpers . GlobalScale ) ;
ImGui . SetCursorPosY ( blacklistPos . Y ) ;
using ( var lb = ImRaii . ListBox ( "##blacklist" ) )
{
if ( lb )
{
for ( int i = 0 ; i < blacklist . Count ; i + + )
{
bool shouldBeSelected = _selectedEntryBlacklist = = i ;
if ( ImGui . Selectable ( blacklist [ i ] + "##BL" + i , shouldBeSelected ) )
{
_selectedEntryBlacklist = i ;
}
string? lastSeenName = _serverConfigurationManager . GetNameForUid ( blacklist [ i ] ) ;
if ( lastSeenName ! = null )
{
ImGui . SameLine ( ) ;
_uiShared . IconText ( FontAwesomeIcon . InfoCircle ) ;
UiSharedService . AttachToolTip ( $"Last seen name: {lastSeenName}" ) ;
}
}
}
}
using ( ImRaii . Disabled ( _selectedEntryBlacklist = = - 1 ) )
{
using var pushId = ImRaii . PushId ( "deleteSelectedBlacklist" ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Trash , "Delete selected UID" ) )
{
_serverConfigurationManager . RemoveBlacklistUid ( _serverConfigurationManager . Blacklist [ _selectedEntryBlacklist ] ) ;
if ( _selectedEntryBlacklist > blacklist . Count - 1 )
- - _selectedEntryBlacklist ;
_playerPerformanceConfigService . Save ( ) ;
recalculatePerformance = true ;
}
}
#endregion Blacklist
if ( recalculatePerformance )
Mediator . Publish ( new RecalculatePerformanceMessage ( recalculatePerformanceUID ) ) ;
}
private static bool InputDtrColors ( string label , ref DtrEntry . Colors colors )
{
using var id = ImRaii . PushId ( label ) ;
var innerSpacing = ImGui . GetStyle ( ) . ItemInnerSpacing . X ;
var foregroundColor = ConvertColor ( colors . Foreground ) ;
var glowColor = ConvertColor ( colors . Glow ) ;
var ret = ImGui . ColorEdit3 ( "###foreground" , ref foregroundColor , ImGuiColorEditFlags . NoInputs | ImGuiColorEditFlags . NoLabel | ImGuiColorEditFlags . Uint8 ) ;
if ( ImGui . IsItemHovered ( ) )
ImGui . SetTooltip ( "Foreground Color - Set to pure black (#000000) to use the default color" ) ;
ImGui . SameLine ( 0.0f , innerSpacing ) ;
ret | = ImGui . ColorEdit3 ( "###glow" , ref glowColor , ImGuiColorEditFlags . NoInputs | ImGuiColorEditFlags . NoLabel | ImGuiColorEditFlags . Uint8 ) ;
if ( ImGui . IsItemHovered ( ) )
ImGui . SetTooltip ( "Glow Color - Set to pure black (#000000) to use the default color" ) ;
ImGui . SameLine ( 0.0f , innerSpacing ) ;
ImGui . TextUnformatted ( label ) ;
if ( ret )
colors = new ( ConvertBackColor ( foregroundColor ) , ConvertBackColor ( glowColor ) ) ;
return ret ;
static Vector3 ConvertColor ( uint color )
= > unchecked ( new ( ( byte ) color / 255.0f , ( byte ) ( color > > 8 ) / 255.0f , ( byte ) ( color > > 16 ) / 255.0f ) ) ;
static uint ConvertBackColor ( Vector3 color )
= > byte . CreateSaturating ( color . X * 255.0f ) | ( ( uint ) byte . CreateSaturating ( color . Y * 255.0f ) < < 8 ) | ( ( uint ) byte . CreateSaturating ( color . Z * 255.0f ) < < 16 ) ;
}
private void DrawServerConfiguration ( )
{
_lastTab = "Service Settings" ;
if ( ApiController . ServerAlive )
{
_uiShared . BigText ( "Service Actions" ) ;
ImGuiHelpers . ScaledDummy ( new Vector2 ( 5 , 5 ) ) ;
if ( ImGui . Button ( "Delete account" ) )
{
_deleteAccountPopupModalShown = true ;
ImGui . OpenPopup ( "Delete your account?" ) ;
}
_uiShared . DrawHelpText ( "Completely deletes your currently connected account." ) ;
if ( ImGui . BeginPopupModal ( "Delete your account?" , ref _deleteAccountPopupModalShown , UiSharedService . PopupWindowFlags ) )
{
UiSharedService . TextWrapped (
"Your account and all associated files and data on the service will be deleted." ) ;
UiSharedService . TextWrapped ( "Your UID will be removed from all pairing lists." ) ;
ImGui . TextUnformatted ( "Are you sure you want to continue?" ) ;
ImGui . Separator ( ) ;
ImGui . Spacing ( ) ;
var buttonSize = ( ImGui . GetWindowContentRegionMax ( ) . X - ImGui . GetWindowContentRegionMin ( ) . X -
ImGui . GetStyle ( ) . ItemSpacing . X ) / 2 ;
if ( ImGui . Button ( "Delete account" , new Vector2 ( buttonSize , 0 ) ) )
{
_ = Task . Run ( ApiController . UserDelete ) ;
_deleteAccountPopupModalShown = false ;
Mediator . Publish ( new SwitchToIntroUiMessage ( ) ) ;
}
ImGui . SameLine ( ) ;
if ( ImGui . Button ( "Cancel##cancelDelete" , new Vector2 ( buttonSize , 0 ) ) )
{
_deleteAccountPopupModalShown = false ;
}
UiSharedService . SetScaledWindowSize ( 325 ) ;
ImGui . EndPopup ( ) ;
}
ImGui . Separator ( ) ;
}
_uiShared . BigText ( "Service & Character Settings" ) ;
var idx = _uiShared . DrawServiceSelection ( ) ;
var playerName = _dalamudUtilService . GetPlayerName ( ) ;
var playerWorldId = _dalamudUtilService . GetHomeWorldId ( ) ;
var worldData = _uiShared . WorldData . OrderBy ( u = > u . Value , StringComparer . Ordinal ) . ToDictionary ( k = > k . Key , k = > k . Value ) ;
string playerWorldName = worldData . GetValueOrDefault ( ( ushort ) playerWorldId , $"{playerWorldId}" ) ;
ImGuiHelpers . ScaledDummy ( new Vector2 ( 10 , 10 ) ) ;
var selectedServer = _serverConfigurationManager . GetServerByIndex ( idx ) ;
if ( selectedServer = = _serverConfigurationManager . CurrentServer )
{
if ( _apiController . IsConnected )
UiSharedService . ColorTextWrapped ( "For any changes to be applied to the current service you need to reconnect to the service." , ImGuiColors . DalamudYellow ) ;
}
if ( ImGui . BeginTabBar ( "serverTabBar" ) )
{
if ( ImGui . BeginTabItem ( "Character Assignments" ) )
{
if ( selectedServer . SecretKeys . Count > 0 )
{
float windowPadding = ImGui . GetStyle ( ) . WindowPadding . X ;
float itemSpacing = ImGui . GetStyle ( ) . ItemSpacing . X ;
float longestName = 0.0f ;
if ( selectedServer . Authentications . Count > 0 )
longestName = selectedServer . Authentications . Max ( p = > ImGui . CalcTextSize ( $"{p.CharacterName} @ Pandaemonium " ) . X ) ;
float iconWidth ;
using ( _ = _uiShared . IconFont . Push ( ) )
iconWidth = ImGui . CalcTextSize ( FontAwesomeIcon . Trash . ToIconString ( ) ) . X ;
UiSharedService . ColorTextWrapped ( "Characters listed here will connect with the specified secret key." , ImGuiColors . DalamudYellow ) ;
int i = 0 ;
foreach ( var item in selectedServer . Authentications . ToList ( ) )
{
using var charaId = ImRaii . PushId ( "selectedChara" + i ) ;
bool thisIsYou = string . Equals ( playerName , item . CharacterName , StringComparison . OrdinalIgnoreCase )
& & playerWorldId = = item . WorldId ;
if ( ! worldData . TryGetValue ( ( ushort ) item . WorldId , out string? worldPreview ) )
worldPreview = worldData . First ( ) . Value ;
_uiShared . IconText ( thisIsYou ? FontAwesomeIcon . Star : FontAwesomeIcon . None ) ;
if ( thisIsYou )
UiSharedService . AttachToolTip ( "Current character" ) ;
ImGui . SameLine ( windowPadding + iconWidth + itemSpacing ) ;
float beforeName = ImGui . GetCursorPosX ( ) ;
ImGui . TextUnformatted ( $"{item.CharacterName} @ {worldPreview}" ) ;
float afterName = ImGui . GetCursorPosX ( ) ;
ImGui . SameLine ( afterName + ( afterName - beforeName ) + longestName + itemSpacing ) ;
var secretKeyIdx = item . SecretKeyIdx ;
var keys = selectedServer . SecretKeys ;
if ( ! keys . TryGetValue ( secretKeyIdx , out var secretKey ) )
{
secretKey = new ( ) ;
}
var friendlyName = secretKey . FriendlyName ;
ImGui . SetNextItemWidth ( afterName - iconWidth - itemSpacing * 2 - windowPadding ) ;
string selectedKeyName = string . Empty ;
if ( selectedServer . SecretKeys . TryGetValue ( item . SecretKeyIdx , out var selectedKey ) )
selectedKeyName = selectedKey . FriendlyName ;
// _uiShared.DrawCombo() remembers the selected option -- we don't want that, because the value can change
if ( ImGui . BeginCombo ( $"##{item.CharacterName}{i}" , selectedKeyName ) )
{
foreach ( var key in selectedServer . SecretKeys )
{
if ( ImGui . Selectable ( $"{key.Value.FriendlyName}##{i}" , key . Key = = item . SecretKeyIdx )
& & key . Key ! = item . SecretKeyIdx )
{
item . SecretKeyIdx = key . Key ;
_serverConfigurationManager . Save ( ) ;
}
}
ImGui . EndCombo ( ) ;
}
ImGui . SameLine ( ) ;
if ( _uiShared . IconButton ( FontAwesomeIcon . Trash ) )
_serverConfigurationManager . RemoveCharacterFromServer ( idx , item ) ;
UiSharedService . AttachToolTip ( "Delete character assignment" ) ;
i + + ;
}
ImGui . Separator ( ) ;
using ( _ = ImRaii . Disabled ( selectedServer . Authentications . Exists ( c = >
string . Equals ( c . CharacterName , _uiShared . PlayerName , StringComparison . Ordinal )
& & c . WorldId = = _uiShared . WorldId
) ) )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . User , "Add current character" ) )
{
_serverConfigurationManager . AddCurrentCharacterToServer ( idx ) ;
}
ImGui . SameLine ( ) ;
}
}
else
{
UiSharedService . ColorTextWrapped ( "You need to add a Secret Key first before adding Characters." , ImGuiColors . DalamudYellow ) ;
}
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Secret Key Management" ) )
{
foreach ( var item in selectedServer . SecretKeys . ToList ( ) )
{
using var id = ImRaii . PushId ( "key" + item . Key ) ;
var friendlyName = item . Value . FriendlyName ;
if ( ImGui . InputText ( "Secret Key Display Name" , ref friendlyName , 255 ) )
{
item . Value . FriendlyName = friendlyName ;
_serverConfigurationManager . Save ( ) ;
}
var key = item . Value . Key ;
var keyInUse = selectedServer . Authentications . Exists ( p = > p . SecretKeyIdx = = item . Key ) ;
if ( keyInUse ) ImGui . PushStyleColor ( ImGuiCol . Text , ImGuiColors . DalamudGrey3 ) ;
if ( ImGui . InputText ( "Secret Key" , ref key , 64 , keyInUse ? ImGuiInputTextFlags . ReadOnly : default ) )
{
item . Value . Key = key ;
_serverConfigurationManager . Save ( ) ;
}
if ( keyInUse ) ImGui . PopStyleColor ( ) ;
bool thisIsYou = selectedServer . Authentications . Any ( a = >
a . SecretKeyIdx = = item . Key
& & string . Equals ( a . CharacterName , _uiShared . PlayerName , StringComparison . OrdinalIgnoreCase )
& & a . WorldId = = playerWorldId
) ;
bool disableAssignment = thisIsYou | | item . Value . Key . IsNullOrEmpty ( ) ;
using ( _ = ImRaii . Disabled ( disableAssignment ) )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . User , "Assign current character" ) )
{
var currentAssignment = selectedServer . Authentications . Find ( a = >
string . Equals ( a . CharacterName , _uiShared . PlayerName , StringComparison . OrdinalIgnoreCase )
& & a . WorldId = = playerWorldId
) ;
if ( currentAssignment = = null )
{
selectedServer . Authentications . Add ( new Authentication ( )
{
CharacterName = playerName ,
WorldId = playerWorldId ,
SecretKeyIdx = item . Key
} ) ;
}
else
{
currentAssignment . SecretKeyIdx = item . Key ;
}
}
if ( ! disableAssignment )
UiSharedService . AttachToolTip ( $"Use this secret key for {playerName} @ {playerWorldName}" ) ;
}
ImGui . SameLine ( ) ;
using var disableDelete = ImRaii . Disabled ( keyInUse ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Trash , "Delete Secret Key" ) & & UiSharedService . CtrlPressed ( ) )
{
selectedServer . SecretKeys . Remove ( item . Key ) ;
_serverConfigurationManager . Save ( ) ;
}
if ( ! keyInUse )
UiSharedService . AttachToolTip ( "Hold CTRL to delete this secret key entry" ) ;
if ( keyInUse )
{
UiSharedService . ColorTextWrapped ( "This key is currently assigned to a character and cannot be edited or deleted." , ImGuiColors . DalamudYellow ) ;
}
if ( item . Key ! = selectedServer . SecretKeys . Keys . LastOrDefault ( ) )
ImGui . Separator ( ) ;
}
ImGui . Separator ( ) ;
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Plus , "Add new Secret Key" ) )
{
selectedServer . SecretKeys . Add ( selectedServer . SecretKeys . Any ( ) ? selectedServer . SecretKeys . Max ( p = > p . Key ) + 1 : 0 , new SecretKey ( )
{
FriendlyName = "New Secret Key" ,
} ) ;
_serverConfigurationManager . Save ( ) ;
}
if ( true ) // Enable registration button for all servers
{
ImGui . SameLine ( ) ;
2025-08-22 21:26:51 +01:00
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Plus , "Register a new Snowcloak account" ) )
2025-08-22 02:19:48 +01:00
{
_registrationInProgress = true ;
_ = Task . Run ( async ( ) = > {
try
{
var reply = await _registerService . RegisterAccount ( CancellationToken . None ) . ConfigureAwait ( false ) ;
if ( ! reply . Success )
{
_logger . LogWarning ( "Registration failed: {err}" , reply . ErrorMessage ) ;
_registrationMessage = reply . ErrorMessage ;
if ( _registrationMessage . IsNullOrEmpty ( ) )
_registrationMessage = "An unknown error occured. Please try again later." ;
return ;
}
_registrationMessage = "New account registered.\nPlease keep a copy of your secret key in case you need to reset your plugins, or to use it on another PC." ;
_registrationSuccess = true ;
selectedServer . SecretKeys . Add ( selectedServer . SecretKeys . Any ( ) ? selectedServer . SecretKeys . Max ( p = > p . Key ) + 1 : 0 , new SecretKey ( )
{
FriendlyName = reply . UID + $" (registered {DateTime.Now:yyyy-MM-dd})" ,
Key = reply . SecretKey ? ? ""
} ) ;
_serverConfigurationManager . Save ( ) ;
}
catch ( Exception ex )
{
_logger . LogWarning ( ex , "Registration failed" ) ;
_registrationSuccess = false ;
_registrationMessage = "An unknown error occured. Please try again later." ;
}
finally
{
_registrationInProgress = false ;
}
} , CancellationToken . None ) ;
}
if ( _registrationInProgress )
{
ImGui . TextUnformatted ( "Sending request..." ) ;
}
else if ( ! _registrationMessage . IsNullOrEmpty ( ) )
{
if ( ! _registrationSuccess )
ImGui . TextColored ( ImGuiColors . DalamudYellow , _registrationMessage ) ;
else
ImGui . TextWrapped ( _registrationMessage ) ;
}
}
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Service Settings" ) )
{
var serverName = selectedServer . ServerName ;
var serverUri = selectedServer . ServerUri ;
2025-08-22 21:26:51 +01:00
var isMain = string . Equals ( serverName , ApiController . SnowcloakServer , StringComparison . OrdinalIgnoreCase ) ;
2025-08-22 02:19:48 +01:00
var flags = isMain ? ImGuiInputTextFlags . ReadOnly : ImGuiInputTextFlags . None ;
if ( ImGui . InputText ( "Service URI" , ref serverUri , 255 , flags ) )
{
selectedServer . ServerUri = serverUri ;
}
if ( isMain )
{
_uiShared . DrawHelpText ( "You cannot edit the URI of the main service." ) ;
}
if ( ImGui . InputText ( "Service Name" , ref serverName , 255 , flags ) )
{
selectedServer . ServerName = serverName ;
_serverConfigurationManager . Save ( ) ;
}
if ( isMain )
{
_uiShared . DrawHelpText ( "You cannot edit the name of the main service." ) ;
}
if ( ! isMain & & selectedServer ! = _serverConfigurationManager . CurrentServer )
{
if ( _uiShared . IconTextButton ( FontAwesomeIcon . Trash , "Delete Service" ) & & UiSharedService . CtrlPressed ( ) )
{
_serverConfigurationManager . DeleteServer ( selectedServer ) ;
}
_uiShared . DrawHelpText ( "Hold CTRL to delete this service" ) ;
}
ImGui . EndTabItem ( ) ;
}
ImGui . EndTabBar ( ) ;
}
}
private string _uidToAddForIgnore = string . Empty ;
private int _selectedEntry = - 1 ;
private string _uidToAddForIgnoreBlacklist = string . Empty ;
private int _selectedEntryBlacklist = - 1 ;
private void DrawSettingsContent ( )
{
if ( _apiController . ServerState is ServerState . Connected )
{
ImGui . TextUnformatted ( "Service " + _serverConfigurationManager . CurrentServer ! . ServerName + ":" ) ;
ImGui . SameLine ( ) ;
ImGui . TextColored ( ImGuiColors . ParsedGreen , "Available" ) ;
ImGui . SameLine ( ) ;
ImGui . TextUnformatted ( "(" ) ;
ImGui . SameLine ( ) ;
ImGui . TextColored ( ImGuiColors . ParsedGreen , _apiController . OnlineUsers . ToString ( CultureInfo . InvariantCulture ) ) ;
ImGui . SameLine ( ) ;
ImGui . TextUnformatted ( "Users Online" ) ;
ImGui . SameLine ( ) ;
ImGui . TextUnformatted ( ")" ) ;
}
ImGui . Separator ( ) ;
if ( ImGui . BeginTabBar ( "mainTabBar" ) )
{
if ( ImGui . BeginTabItem ( "General" ) )
{
DrawGeneral ( ) ;
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Performance" ) )
{
DrawPerformance ( ) ;
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Storage" ) )
{
DrawFileStorageSettings ( ) ;
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Transfers" ) )
{
DrawCurrentTransfers ( ) ;
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Service Settings" ) )
{
ImGui . BeginDisabled ( _registrationInProgress ) ;
DrawServerConfiguration ( ) ;
ImGui . EndTabItem ( ) ;
ImGui . EndDisabled ( ) ; // _registrationInProgress
}
if ( ImGui . BeginTabItem ( "Chat" ) )
{
DrawChatConfig ( ) ;
ImGui . EndTabItem ( ) ;
}
if ( ImGui . BeginTabItem ( "Advanced" ) )
{
DrawAdvanced ( ) ;
ImGui . EndTabItem ( ) ;
}
ImGui . EndTabBar ( ) ;
}
}
private void UiSharedService_GposeEnd ( )
{
IsOpen = _wasOpen ;
}
private void UiSharedService_GposeStart ( )
{
_wasOpen = IsOpen ;
IsOpen = false ;
}
}