Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9b7eb47c0 |
@@ -6,7 +6,6 @@ using LightlessSync.Utils;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace LightlessSync.FileCache;
|
namespace LightlessSync.FileCache;
|
||||||
|
|
||||||
@@ -22,7 +21,6 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
private CancellationTokenSource _scanCancellationTokenSource = new();
|
private CancellationTokenSource _scanCancellationTokenSource = new();
|
||||||
private readonly CancellationTokenSource _periodicCalculationTokenSource = new();
|
private readonly CancellationTokenSource _periodicCalculationTokenSource = new();
|
||||||
public static readonly IImmutableList<string> AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk", ".kdb"];
|
public static readonly IImmutableList<string> AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk", ".kdb"];
|
||||||
private static readonly HashSet<string> AllowedFileExtensionSet = new(AllowedFileExtensions, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
public CacheMonitor(ILogger<CacheMonitor> logger, IpcManager ipcManager, LightlessConfigService configService,
|
public CacheMonitor(ILogger<CacheMonitor> logger, IpcManager ipcManager, LightlessConfigService configService,
|
||||||
FileCacheManager fileDbManager, LightlessMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil,
|
FileCacheManager fileDbManager, LightlessMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil,
|
||||||
@@ -165,7 +163,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Logger.LogTrace("Lightless FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
Logger.LogTrace("Lightless FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath);
|
||||||
|
|
||||||
if (!HasAllowedExtension(e.FullPath)) return;
|
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
||||||
|
|
||||||
lock (_watcherChanges)
|
lock (_watcherChanges)
|
||||||
{
|
{
|
||||||
@@ -209,7 +207,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
private void Fs_Changed(object sender, FileSystemEventArgs e)
|
private void Fs_Changed(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(e.FullPath)) return;
|
if (Directory.Exists(e.FullPath)) return;
|
||||||
if (!HasAllowedExtension(e.FullPath)) return;
|
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
||||||
|
|
||||||
if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created))
|
if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created))
|
||||||
return;
|
return;
|
||||||
@@ -233,7 +231,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
foreach (var file in directoryFiles)
|
foreach (var file in directoryFiles)
|
||||||
{
|
{
|
||||||
if (!HasAllowedExtension(file)) continue;
|
if (!AllowedFileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) continue;
|
||||||
var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase);
|
var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_watcherChanges.Remove(oldPath);
|
_watcherChanges.Remove(oldPath);
|
||||||
@@ -245,7 +243,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!HasAllowedExtension(e.FullPath)) return;
|
if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
|
||||||
|
|
||||||
lock (_watcherChanges)
|
lock (_watcherChanges)
|
||||||
{
|
{
|
||||||
@@ -265,17 +263,6 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
public FileSystemWatcher? PenumbraWatcher { get; private set; }
|
public FileSystemWatcher? PenumbraWatcher { get; private set; }
|
||||||
public FileSystemWatcher? LightlessWatcher { get; private set; }
|
public FileSystemWatcher? LightlessWatcher { get; private set; }
|
||||||
|
|
||||||
private static bool HasAllowedExtension(string path)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
|
||||||
return !string.IsNullOrEmpty(extension) && AllowedFileExtensionSet.Contains(extension);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LightlessWatcherExecution()
|
private async Task LightlessWatcherExecution()
|
||||||
{
|
{
|
||||||
_lightlessFswCts = _lightlessFswCts.CancelRecreate();
|
_lightlessFswCts = _lightlessFswCts.CancelRecreate();
|
||||||
@@ -619,7 +606,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
[
|
[
|
||||||
.. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
|
.. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
|
||||||
.AsParallel()
|
.AsParallel()
|
||||||
.Where(f => HasAllowedExtension(f)
|
.Where(f => AllowedFileExtensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase))
|
||||||
&& !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase)
|
&& !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase)
|
||||||
&& !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase)
|
&& !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase)
|
||||||
&& !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)),
|
&& !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)),
|
||||||
|
|||||||
@@ -372,9 +372,6 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
||||||
{
|
{
|
||||||
if (descriptor.IsInGpose)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!TryResolveObjectKind(descriptor, out var resolvedKind))
|
if (!TryResolveObjectKind(descriptor, out var resolvedKind))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace LightlessSync.Interop.Ipc;
|
|||||||
|
|
||||||
public sealed class IpcCallerBrio : IpcServiceBase
|
public sealed class IpcCallerBrio : IpcServiceBase
|
||||||
{
|
{
|
||||||
private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(0, 0, 0, 0));
|
private static readonly IpcServiceDescriptor BrioDescriptor = new("Brio", "Brio", new Version(3, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ILogger<IpcCallerBrio> _logger;
|
private readonly ILogger<IpcCallerBrio> _logger;
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
@@ -144,7 +144,7 @@ public sealed class IpcCallerBrio : IpcServiceBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var version = _apiVersion.Invoke();
|
var version = _apiVersion.Invoke();
|
||||||
return version.Breaking == 3 && version.Feature >= 0
|
return version.Item1 == 3 && version.Item2 >= 0
|
||||||
? IpcConnectionState.Available
|
? IpcConnectionState.Available
|
||||||
: IpcConnectionState.VersionMismatch;
|
: IpcConnectionState.VersionMismatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Dalamud.Plugin;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
using LightlessSync.Interop.Ipc.Framework;
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
@@ -12,7 +13,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase
|
|||||||
private static readonly IpcServiceDescriptor MoodlesDescriptor = new("Moodles", "Moodles", new Version(0, 0, 0, 0));
|
private static readonly IpcServiceDescriptor MoodlesDescriptor = new("Moodles", "Moodles", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<int> _moodlesApiVersion;
|
private readonly ICallGateSubscriber<int> _moodlesApiVersion;
|
||||||
private readonly ICallGateSubscriber<nint, object> _moodlesOnChange;
|
private readonly ICallGateSubscriber<IPlayerCharacter, object> _moodlesOnChange;
|
||||||
private readonly ICallGateSubscriber<nint, string> _moodlesGetStatus;
|
private readonly ICallGateSubscriber<nint, string> _moodlesGetStatus;
|
||||||
private readonly ICallGateSubscriber<nint, string, object> _moodlesSetStatus;
|
private readonly ICallGateSubscriber<nint, string, object> _moodlesSetStatus;
|
||||||
private readonly ICallGateSubscriber<nint, object> _moodlesRevertStatus;
|
private readonly ICallGateSubscriber<nint, object> _moodlesRevertStatus;
|
||||||
@@ -28,7 +29,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase
|
|||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
|
|
||||||
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
||||||
_moodlesOnChange = pi.GetIpcSubscriber<nint, object>("Moodles.StatusManagerModified");
|
_moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified");
|
||||||
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtrV2");
|
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtrV2");
|
||||||
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtrV2");
|
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtrV2");
|
||||||
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtrV2");
|
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtrV2");
|
||||||
@@ -38,9 +39,9 @@ public sealed class IpcCallerMoodles : IpcServiceBase
|
|||||||
CheckAPI();
|
CheckAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMoodlesChange(nint address)
|
private void OnMoodlesChange(IPlayerCharacter character)
|
||||||
{
|
{
|
||||||
_lightlessMediator.Publish(new MoodlesMessage(address));
|
_lightlessMediator.Publish(new MoodlesMessage(character.Address));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -106,7 +107,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _moodlesApiVersion.InvokeFunc() >= 4
|
return _moodlesApiVersion.InvokeFunc() == 3
|
||||||
? IpcConnectionState.Available
|
? IpcConnectionState.Available
|
||||||
: IpcConnectionState.VersionMismatch;
|
: IpcConnectionState.VersionMismatch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration.Configurations;
|
namespace LightlessSync.LightlessConfiguration.Configurations;
|
||||||
|
|
||||||
@@ -11,16 +10,7 @@ public sealed class ChatConfig : ILightlessConfiguration
|
|||||||
public bool ShowRulesOverlayOnOpen { get; set; } = true;
|
public bool ShowRulesOverlayOnOpen { get; set; } = true;
|
||||||
public bool ShowMessageTimestamps { get; set; } = true;
|
public bool ShowMessageTimestamps { get; set; } = true;
|
||||||
public float ChatWindowOpacity { get; set; } = .97f;
|
public float ChatWindowOpacity { get; set; } = .97f;
|
||||||
public bool FadeWhenUnfocused { get; set; } = false;
|
|
||||||
public float UnfocusedWindowOpacity { get; set; } = 0.6f;
|
|
||||||
public bool IsWindowPinned { get; set; } = false;
|
public bool IsWindowPinned { get; set; } = false;
|
||||||
public bool AutoOpenChatOnPluginLoad { get; set; } = false;
|
public bool AutoOpenChatOnPluginLoad { get; set; } = false;
|
||||||
public float ChatFontScale { get; set; } = 1.0f;
|
public float ChatFontScale { get; set; } = 1.0f;
|
||||||
public bool HideInCombat { get; set; } = false;
|
|
||||||
public bool HideInDuty { get; set; } = false;
|
|
||||||
public bool ShowWhenUiHidden { get; set; } = true;
|
|
||||||
public bool ShowInCutscenes { get; set; } = true;
|
|
||||||
public bool ShowInGpose { get; set; } = true;
|
|
||||||
public List<string> ChannelOrder { get; set; } = new();
|
|
||||||
public Dictionary<string, bool> PreferNotesForChannels { get; set; } = new(StringComparer.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
||||||
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
||||||
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
||||||
public VisiblePairSortMode VisiblePairSortMode { get; set; } = VisiblePairSortMode.Alphabetical;
|
public VisiblePairSortMode VisiblePairSortMode { get; set; } = VisiblePairSortMode.Default;
|
||||||
public OnlinePairSortMode OnlinePairSortMode { get; set; } = OnlinePairSortMode.Alphabetical;
|
|
||||||
public float ProfileDelay { get; set; } = 1.5f;
|
public float ProfileDelay { get; set; } = 1.5f;
|
||||||
public bool ProfilePopoutRight { get; set; } = false;
|
public bool ProfilePopoutRight { get; set; } = false;
|
||||||
public bool ProfilesAllowNsfw { get; set; } = false;
|
public bool ProfilesAllowNsfw { get; set; } = false;
|
||||||
|
|||||||
@@ -69,6 +69,58 @@ namespace LightlessSync;
|
|||||||
#pragma warning restore S125 // Sections of code should not be commented out
|
#pragma warning restore S125 // Sections of code should not be commented out
|
||||||
// thank you dark 🙏
|
// thank you dark 🙏
|
||||||
|
|
||||||
|
/*
|
||||||
|
---------
|
||||||
|
-------------------------
|
||||||
|
-------------- ##++++ -------------
|
||||||
|
----------+##+++++++++++++++++++###--------
|
||||||
|
+------ ####++++++++++++++++++++++++##### -----
|
||||||
|
------- #####++++++++++++++++++++++++++++++###### +++++
|
||||||
|
------+#####++++++++++++++++++++++++++++++++++########+++++
|
||||||
|
------######++++++++++++++++++++++++++++++++++++++#########++++
|
||||||
|
------######+++++++++++++++++++++++++++++++++++++##############+++
|
||||||
|
-----#######+++++++++++++++++++++++++++++++#######################+++
|
||||||
|
---- #######++++++++++++++++++++++++++++############################ ++
|
||||||
|
-----########+++++++++++++++++++++++++++##################################
|
||||||
|
----+########+++++++++++++++++++++++++#######################################
|
||||||
|
---- ########+++++++++++++++++++++++-+#########################################
|
||||||
|
-----#########++++++++++++++++++++--+###########################################
|
||||||
|
----#########+++++++++++++++++++---+#######+++++++++#############################
|
||||||
|
----##########+++++++++++++++++----###+--------.......---+####################### #
|
||||||
|
---###########+++++++++++++++-----#+------..................-#######################
|
||||||
|
----###########++++++++++++++----------..----..................-+####################
|
||||||
|
---+###########+++++++++++++--------.-+########+-................-###################
|
||||||
|
----############++++++++++++--------+##############+................+#################
|
||||||
|
----############++++---+---------.-#################+................-################
|
||||||
|
---+####+++++-------------------..###################+...............--###############
|
||||||
|
----+#############++-------------.+####################-...............--###############
|
||||||
|
----+##############++++++++------.+####################+...............---##############
|
||||||
|
----+##############+++++++++-----.+####################+...............---+#############
|
||||||
|
---++##############++++++++-------####################.................---#############
|
||||||
|
----+################++++++++-----+##################-......-..........---+############
|
||||||
|
----++################+++++++-------################-......+...........---+############
|
||||||
|
----++##################++++++--------+##########+-......-#-..........----+###########
|
||||||
|
--- ++##################+++++--------..--+++---........+#-.........------+###########
|
||||||
|
+++++++####################++----------.............-+#+-......-.--------+##########
|
||||||
|
+++ +++######################+---------..........+###+-......-----------+##########
|
||||||
|
++++-+++##########################+++------+++######-......--------.----##########
|
||||||
|
++++----+########################################+-.......------------+#########
|
||||||
|
++++ ----+####################################--.....----------------#########
|
||||||
|
+++ -----++###############################+------------------------########
|
||||||
|
+++ ------++###########++##+++++#+++----------------------------+#######
|
||||||
|
+++ ----------++########-----------...------------------------+#######
|
||||||
|
+++ -------------------------------------------------------#######
|
||||||
|
++# ---------------------------------------------------+######
|
||||||
|
## ------------------------------------------------######
|
||||||
|
### -------------------------------------------+#####
|
||||||
|
### --------------------------------------+#####
|
||||||
|
# -------------------------------####
|
||||||
|
----------------------++###
|
||||||
|
------------#
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Just a shitty fork, still with love.
|
||||||
|
|
||||||
public class LightlessPlugin : MediatorSubscriberBase, IHostedService
|
public class LightlessPlugin : MediatorSubscriberBase, IHostedService
|
||||||
{
|
{
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ public class PlayerDataFactory
|
|||||||
CharacterDataFragment fragment = objectKind == ObjectKind.Player ? new CharacterDataFragmentPlayer() : new();
|
CharacterDataFragment fragment = objectKind == ObjectKind.Player ? new CharacterDataFragmentPlayer() : new();
|
||||||
|
|
||||||
_logger.LogDebug("Building character data for {obj}", playerRelatedObject);
|
_logger.LogDebug("Building character data for {obj}", playerRelatedObject);
|
||||||
var logDebug = _logger.IsEnabled(LogLevel.Debug);
|
|
||||||
|
|
||||||
// wait until chara is not drawing and present so nothing spontaneously explodes
|
// wait until chara is not drawing and present so nothing spontaneously explodes
|
||||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct).ConfigureAwait(false);
|
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct).ConfigureAwait(false);
|
||||||
@@ -133,6 +132,11 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
Dictionary<string, List<ushort>>? boneIndices =
|
||||||
|
objectKind != ObjectKind.Player
|
||||||
|
? null
|
||||||
|
: await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetSkeletonBoneIndices(playerRelatedObject)).ConfigureAwait(false);
|
||||||
|
|
||||||
DateTime start = DateTime.UtcNow;
|
DateTime start = DateTime.UtcNow;
|
||||||
|
|
||||||
// penumbra call, it's currently broken
|
// penumbra call, it's currently broken
|
||||||
@@ -150,21 +154,11 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (logDebug)
|
_logger.LogDebug("== Static Replacements ==");
|
||||||
|
foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("== Static Replacements ==");
|
_logger.LogDebug("=> {repl}", replacement);
|
||||||
foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
ct.ThrowIfCancellationRequested();
|
||||||
{
|
|
||||||
_logger.LogDebug("=> {repl}", replacement);
|
|
||||||
ct.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement))
|
|
||||||
{
|
|
||||||
ct.ThrowIfCancellationRequested();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false);
|
await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false);
|
||||||
@@ -196,21 +190,11 @@ public class PlayerDataFactory
|
|||||||
var transientPaths = ManageSemiTransientData(objectKind);
|
var transientPaths = ManageSemiTransientData(objectKind);
|
||||||
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (logDebug)
|
_logger.LogDebug("== Transient Replacements ==");
|
||||||
|
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("== Transient Replacements ==");
|
_logger.LogDebug("=> {repl}", replacement);
|
||||||
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal))
|
fragment.FileReplacements.Add(replacement);
|
||||||
{
|
|
||||||
_logger.LogDebug("=> {repl}", replacement);
|
|
||||||
fragment.FileReplacements.Add(replacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)))
|
|
||||||
{
|
|
||||||
fragment.FileReplacements.Add(replacement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up all semi transient resources that don't have any file replacement (aka null resolve)
|
// clean up all semi transient resources that don't have any file replacement (aka null resolve)
|
||||||
@@ -268,26 +252,11 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
Dictionary<string, List<ushort>>? boneIndices = null;
|
|
||||||
var hasPapFiles = false;
|
|
||||||
if (objectKind == ObjectKind.Player)
|
|
||||||
{
|
|
||||||
hasPapFiles = fragment.FileReplacements.Any(f =>
|
|
||||||
!f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (hasPapFiles)
|
|
||||||
{
|
|
||||||
boneIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetSkeletonBoneIndices(playerRelatedObject)).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Player)
|
if (objectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (hasPapFiles)
|
await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false);
|
||||||
{
|
|
||||||
await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException e)
|
catch (OperationCanceledException e)
|
||||||
{
|
{
|
||||||
@@ -309,16 +278,12 @@ public class PlayerDataFactory
|
|||||||
{
|
{
|
||||||
if (boneIndices == null) return;
|
if (boneIndices == null) return;
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
foreach (var kvp in boneIndices)
|
||||||
{
|
{
|
||||||
foreach (var kvp in boneIndices)
|
_logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Any() ? kvp.Value.Max() : 0, string.Join(',', kvp.Value));
|
||||||
{
|
|
||||||
_logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Any() ? kvp.Value.Max() : 0, string.Join(',', kvp.Value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var maxPlayerBoneIndex = boneIndices.SelectMany(kvp => kvp.Value).DefaultIfEmpty().Max();
|
if (boneIndices.All(u => u.Value.Count == 0)) return;
|
||||||
if (maxPlayerBoneIndex <= 0) return;
|
|
||||||
|
|
||||||
int noValidationFailed = 0;
|
int noValidationFailed = 0;
|
||||||
foreach (var file in fragment.FileReplacements.Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList())
|
foreach (var file in fragment.FileReplacements.Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList())
|
||||||
@@ -338,13 +303,12 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
_logger.LogDebug("Verifying bone indices for {path}, found {x} skeletons", file.ResolvedPath, skeletonIndices.Count);
|
_logger.LogDebug("Verifying bone indices for {path}, found {x} skeletons", file.ResolvedPath, skeletonIndices.Count);
|
||||||
|
|
||||||
foreach (var boneCount in skeletonIndices)
|
foreach (var boneCount in skeletonIndices.Select(k => k).ToList())
|
||||||
{
|
{
|
||||||
var maxAnimationIndex = boneCount.Value.DefaultIfEmpty().Max();
|
if (boneCount.Value.Max() > boneIndices.SelectMany(b => b.Value).Max())
|
||||||
if (maxAnimationIndex > maxPlayerBoneIndex)
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Found more bone indices on the animation {path} skeleton {skl} (max indice {idx}) than on any player related skeleton (max indice {idx2})",
|
_logger.LogWarning("Found more bone indices on the animation {path} skeleton {skl} (max indice {idx}) than on any player related skeleton (max indice {idx2})",
|
||||||
file.ResolvedPath, boneCount.Key, maxAnimationIndex, maxPlayerBoneIndex);
|
file.ResolvedPath, boneCount.Key, boneCount.Value.Max(), boneIndices.SelectMany(b => b.Value).Max());
|
||||||
validationFailed = true;
|
validationFailed = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,36 @@
|
|||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
|
|
||||||
namespace LightlessSync.PlayerData.Pairs;
|
namespace LightlessSync.PlayerData.Pairs;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// orchestrates the lifecycle of a paired character
|
/// orchestrates the lifecycle of a paired character
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
||||||
{
|
{
|
||||||
new string Ident { get; }
|
new string Ident { get; }
|
||||||
bool Initialized { get; }
|
bool Initialized { get; }
|
||||||
bool IsVisible { get; }
|
bool IsVisible { get; }
|
||||||
bool ScheduledForDeletion { get; set; }
|
bool ScheduledForDeletion { get; set; }
|
||||||
CharacterData? LastReceivedCharacterData { get; }
|
CharacterData? LastReceivedCharacterData { get; }
|
||||||
long LastAppliedDataBytes { get; }
|
long LastAppliedDataBytes { get; }
|
||||||
new string? PlayerName { get; }
|
new string? PlayerName { get; }
|
||||||
string PlayerNameHash { get; }
|
string PlayerNameHash { get; }
|
||||||
uint PlayerCharacterId { get; }
|
uint PlayerCharacterId { get; }
|
||||||
DateTime? LastDataReceivedAt { get; }
|
DateTime? LastDataReceivedAt { get; }
|
||||||
DateTime? LastApplyAttemptAt { get; }
|
DateTime? LastApplyAttemptAt { get; }
|
||||||
DateTime? LastSuccessfulApplyAt { get; }
|
DateTime? LastSuccessfulApplyAt { get; }
|
||||||
string? LastFailureReason { get; }
|
string? LastFailureReason { get; }
|
||||||
IReadOnlyList<string> LastBlockingConditions { get; }
|
IReadOnlyList<string> LastBlockingConditions { get; }
|
||||||
bool IsApplying { get; }
|
bool IsApplying { get; }
|
||||||
bool IsDownloading { get; }
|
bool IsDownloading { get; }
|
||||||
int PendingDownloadCount { get; }
|
int PendingDownloadCount { get; }
|
||||||
int ForbiddenDownloadCount { get; }
|
int ForbiddenDownloadCount { get; }
|
||||||
DateTime? InvisibleSinceUtc { get; }
|
|
||||||
DateTime? VisibilityEvictionDueAtUtc { get; }
|
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void ApplyData(CharacterData data);
|
void ApplyData(CharacterData data);
|
||||||
void ApplyLastReceivedData(bool forced = false);
|
void ApplyLastReceivedData(bool forced = false);
|
||||||
bool FetchPerformanceMetricsFromCache();
|
bool FetchPerformanceMetricsFromCache();
|
||||||
void LoadCachedCharacterData(CharacterData data);
|
void LoadCachedCharacterData(CharacterData data);
|
||||||
void SetUploading(bool uploading);
|
void SetUploading(bool uploading);
|
||||||
void SetPaused(bool paused);
|
void SetPaused(bool paused);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,13 +194,9 @@ public class Pair
|
|||||||
{
|
{
|
||||||
var handler = TryGetHandler();
|
var handler = TryGetHandler();
|
||||||
if (handler is null)
|
if (handler is null)
|
||||||
|
{
|
||||||
return PairDebugInfo.Empty;
|
return PairDebugInfo.Empty;
|
||||||
|
}
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
var dueAt = handler.VisibilityEvictionDueAtUtc;
|
|
||||||
var remainingSeconds = dueAt.HasValue
|
|
||||||
? Math.Max(0, (dueAt.Value - now).TotalSeconds)
|
|
||||||
: (double?)null;
|
|
||||||
|
|
||||||
return new PairDebugInfo(
|
return new PairDebugInfo(
|
||||||
true,
|
true,
|
||||||
@@ -210,9 +206,6 @@ public class Pair
|
|||||||
handler.LastDataReceivedAt,
|
handler.LastDataReceivedAt,
|
||||||
handler.LastApplyAttemptAt,
|
handler.LastApplyAttemptAt,
|
||||||
handler.LastSuccessfulApplyAt,
|
handler.LastSuccessfulApplyAt,
|
||||||
handler.InvisibleSinceUtc,
|
|
||||||
handler.VisibilityEvictionDueAtUtc,
|
|
||||||
remainingSeconds,
|
|
||||||
handler.LastFailureReason,
|
handler.LastFailureReason,
|
||||||
handler.LastBlockingConditions,
|
handler.LastBlockingConditions,
|
||||||
handler.IsApplying,
|
handler.IsApplying,
|
||||||
|
|||||||
@@ -8,9 +8,6 @@ public sealed record PairDebugInfo(
|
|||||||
DateTime? LastDataReceivedAt,
|
DateTime? LastDataReceivedAt,
|
||||||
DateTime? LastApplyAttemptAt,
|
DateTime? LastApplyAttemptAt,
|
||||||
DateTime? LastSuccessfulApplyAt,
|
DateTime? LastSuccessfulApplyAt,
|
||||||
DateTime? InvisibleSinceUtc,
|
|
||||||
DateTime? VisibilityEvictionDueAtUtc,
|
|
||||||
double? VisibilityEvictionRemainingSeconds,
|
|
||||||
string? LastFailureReason,
|
string? LastFailureReason,
|
||||||
IReadOnlyList<string> BlockingConditions,
|
IReadOnlyList<string> BlockingConditions,
|
||||||
bool IsApplying,
|
bool IsApplying,
|
||||||
@@ -27,9 +24,6 @@ public sealed record PairDebugInfo(
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
Array.Empty<string>(),
|
Array.Empty<string>(),
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -70,14 +70,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
private DateTime? _lastSuccessfulApplyAt;
|
private DateTime? _lastSuccessfulApplyAt;
|
||||||
private string? _lastFailureReason;
|
private string? _lastFailureReason;
|
||||||
private IReadOnlyList<string> _lastBlockingConditions = Array.Empty<string>();
|
private IReadOnlyList<string> _lastBlockingConditions = Array.Empty<string>();
|
||||||
private readonly object _visibilityGraceGate = new();
|
|
||||||
private CancellationTokenSource? _visibilityGraceCts;
|
|
||||||
private static readonly TimeSpan VisibilityEvictionGrace = TimeSpan.FromMinutes(1);
|
|
||||||
private DateTime? _invisibleSinceUtc;
|
|
||||||
private DateTime? _visibilityEvictionDueAtUtc;
|
|
||||||
|
|
||||||
public DateTime? InvisibleSinceUtc => _invisibleSinceUtc;
|
|
||||||
public DateTime? VisibilityEvictionDueAtUtc => _visibilityEvictionDueAtUtc;
|
|
||||||
public string Ident { get; }
|
public string Ident { get; }
|
||||||
public bool Initialized { get; private set; }
|
public bool Initialized { get; private set; }
|
||||||
public bool ScheduledForDeletion { get; set; }
|
public bool ScheduledForDeletion { get; set; }
|
||||||
@@ -87,37 +80,24 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
get => _isVisible;
|
get => _isVisible;
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
if (_isVisible == value) return;
|
if (_isVisible != value)
|
||||||
|
|
||||||
_isVisible = value;
|
|
||||||
|
|
||||||
if (!_isVisible)
|
|
||||||
{
|
{
|
||||||
DisableSync();
|
_isVisible = value;
|
||||||
|
if (!_isVisible)
|
||||||
_invisibleSinceUtc = DateTime.UtcNow;
|
{
|
||||||
_visibilityEvictionDueAtUtc = _invisibleSinceUtc.Value.Add(VisibilityEvictionGrace);
|
DisableSync();
|
||||||
|
ResetPenumbraCollection(reason: "VisibilityLost");
|
||||||
StartVisibilityGraceTask();
|
}
|
||||||
}
|
else if (_charaHandler is not null && _charaHandler.Address != nint.Zero)
|
||||||
else
|
{
|
||||||
{
|
|
||||||
CancelVisibilityGraceTask();
|
|
||||||
|
|
||||||
_invisibleSinceUtc = null;
|
|
||||||
_visibilityEvictionDueAtUtc = null;
|
|
||||||
|
|
||||||
ScheduledForDeletion = false;
|
|
||||||
|
|
||||||
if (_charaHandler is not null && _charaHandler.Address != nint.Zero)
|
|
||||||
_ = EnsurePenumbraCollection();
|
_ = EnsurePenumbraCollection();
|
||||||
|
}
|
||||||
|
var user = GetPrimaryUserData();
|
||||||
|
Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter),
|
||||||
|
EventSeverity.Informational, "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"))));
|
||||||
|
Mediator.Publish(new RefreshUiMessage());
|
||||||
|
Mediator.Publish(new VisibilityChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = GetPrimaryUserData();
|
|
||||||
Mediator.Publish(new EventMessage(new Event(PlayerName, user, nameof(PairHandlerAdapter),
|
|
||||||
EventSeverity.Informational, "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"))));
|
|
||||||
Mediator.Publish(new RefreshUiMessage());
|
|
||||||
Mediator.Publish(new VisibilityChange());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -938,46 +918,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CancelVisibilityGraceTask()
|
|
||||||
{
|
|
||||||
lock (_visibilityGraceGate)
|
|
||||||
{
|
|
||||||
_visibilityGraceCts?.CancelDispose();
|
|
||||||
_visibilityGraceCts = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartVisibilityGraceTask()
|
|
||||||
{
|
|
||||||
CancellationToken token;
|
|
||||||
lock (_visibilityGraceGate)
|
|
||||||
{
|
|
||||||
_visibilityGraceCts = _visibilityGraceCts?.CancelRecreate() ?? new CancellationTokenSource();
|
|
||||||
token = _visibilityGraceCts.Token;
|
|
||||||
}
|
|
||||||
|
|
||||||
_visibilityGraceTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(VisibilityEvictionGrace, token).ConfigureAwait(false);
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
if (IsVisible) return;
|
|
||||||
|
|
||||||
ScheduledForDeletion = true;
|
|
||||||
ResetPenumbraCollection(reason: "VisibilityLostTimeout");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// operation cancelled, do nothing
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogDebug(ex, "Visibility grace task failed for {handler}", GetLogIdentifier());
|
|
||||||
}
|
|
||||||
}, CancellationToken.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
@@ -996,10 +936,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
_downloadCancellationTokenSource = null;
|
_downloadCancellationTokenSource = null;
|
||||||
_downloadManager.Dispose();
|
_downloadManager.Dispose();
|
||||||
_charaHandler?.Dispose();
|
_charaHandler?.Dispose();
|
||||||
CancelVisibilityGraceTask();
|
|
||||||
_charaHandler = null;
|
_charaHandler = null;
|
||||||
_invisibleSinceUtc = null;
|
|
||||||
_visibilityEvictionDueAtUtc = null;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(name))
|
if (!string.IsNullOrEmpty(name))
|
||||||
{
|
{
|
||||||
@@ -1328,7 +1265,6 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Task? _pairDownloadTask;
|
private Task? _pairDownloadTask;
|
||||||
private Task _visibilityGraceTask;
|
|
||||||
|
|
||||||
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
||||||
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
|
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
{
|
{
|
||||||
private readonly object _gate = new();
|
private readonly object _gate = new();
|
||||||
private readonly object _pendingGate = new();
|
private readonly object _pendingGate = new();
|
||||||
private readonly object _visibilityGate = new();
|
|
||||||
private readonly Dictionary<string, PairHandlerEntry> _entriesByIdent = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, PairHandlerEntry> _entriesByIdent = new(StringComparer.Ordinal);
|
||||||
private readonly Dictionary<string, CancellationTokenSource> _pendingInvisibleEvictions = new(StringComparer.Ordinal);
|
|
||||||
private readonly Dictionary<IPairHandlerAdapter, PairHandlerEntry> _entriesByHandler = new(ReferenceEqualityComparer.Instance);
|
private readonly Dictionary<IPairHandlerAdapter, PairHandlerEntry> _entriesByHandler = new(ReferenceEqualityComparer.Instance);
|
||||||
|
|
||||||
private readonly IPairHandlerAdapterFactory _handlerFactory;
|
private readonly IPairHandlerAdapterFactory _handlerFactory;
|
||||||
@@ -146,37 +144,6 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
return PairOperationResult<PairUniqueIdentifier>.Ok(registration.PairIdent);
|
return PairOperationResult<PairUniqueIdentifier>.Ok(registration.PairIdent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PairOperationResult CancelAllInvisibleEvictions()
|
|
||||||
{
|
|
||||||
List<CancellationTokenSource> snapshot;
|
|
||||||
lock (_visibilityGate)
|
|
||||||
{
|
|
||||||
snapshot = [.. _pendingInvisibleEvictions.Values];
|
|
||||||
_pendingInvisibleEvictions.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string>? errors = null;
|
|
||||||
|
|
||||||
foreach (var cts in snapshot)
|
|
||||||
{
|
|
||||||
try { cts.Cancel(); }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
(errors ??= new List<string>()).Add($"Cancel: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
try { cts.Dispose(); }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
(errors ??= new List<string>()).Add($"Dispose: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors is null
|
|
||||||
? PairOperationResult.Ok()
|
|
||||||
: PairOperationResult.Fail($"CancelAllInvisibleEvictions had error(s): {string.Join(" | ", errors)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public PairOperationResult ApplyCharacterData(PairRegistration registration, OnlineUserCharaDataDto dto)
|
public PairOperationResult ApplyCharacterData(PairRegistration registration, OnlineUserCharaDataDto dto)
|
||||||
{
|
{
|
||||||
if (registration.CharacterIdent is null)
|
if (registration.CharacterIdent is null)
|
||||||
@@ -333,7 +300,6 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
handlers = _entriesByHandler.Keys.ToList();
|
handlers = _entriesByHandler.Keys.ToList();
|
||||||
CancelAllInvisibleEvictions();
|
|
||||||
_entriesByIdent.Clear();
|
_entriesByIdent.Clear();
|
||||||
_entriesByHandler.Clear();
|
_entriesByHandler.Clear();
|
||||||
}
|
}
|
||||||
@@ -366,7 +332,6 @@ public sealed class PairHandlerRegistry : IDisposable
|
|||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
handlers = _entriesByHandler.Keys.ToList();
|
handlers = _entriesByHandler.Keys.ToList();
|
||||||
CancelAllInvisibleEvictions();
|
|
||||||
_entriesByIdent.Clear();
|
_entriesByIdent.Clear();
|
||||||
_entriesByHandler.Clear();
|
_entriesByHandler.Clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
@@ -106,7 +105,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
services.AddSingleton(new Dalamud.Localization("LightlessSync.Localization.", string.Empty, useEmbedded: true));
|
services.AddSingleton(new Dalamud.Localization("LightlessSync.Localization.", string.Empty, useEmbedded: true));
|
||||||
services.AddSingleton(gameGui);
|
services.AddSingleton(gameGui);
|
||||||
services.AddSingleton(addonLifecycle);
|
services.AddSingleton(addonLifecycle);
|
||||||
services.AddSingleton<IUiBuilder>(pluginInterface.UiBuilder);
|
|
||||||
|
|
||||||
// Core singletons
|
// Core singletons
|
||||||
services.AddSingleton<LightlessMediator>();
|
services.AddSingleton<LightlessMediator>();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
private readonly DalamudUtilService _dalamudUtilService;
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
private readonly ActorObjectService _actorObjectService;
|
private readonly ActorObjectService _actorObjectService;
|
||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly ChatConfigService _chatConfigService;
|
|
||||||
|
|
||||||
private readonly Lock _sync = new();
|
private readonly Lock _sync = new();
|
||||||
|
|
||||||
@@ -58,7 +57,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_actorObjectService = actorObjectService;
|
_actorObjectService = actorObjectService;
|
||||||
_pairUiService = pairUiService;
|
_pairUiService = pairUiService;
|
||||||
_chatConfigService = chatConfigService;
|
|
||||||
|
|
||||||
_isLoggedIn = _dalamudUtilService.IsLoggedIn;
|
_isLoggedIn = _dalamudUtilService.IsLoggedIn;
|
||||||
_isConnected = _apiController.IsConnected;
|
_isConnected = _apiController.IsConnected;
|
||||||
@@ -138,42 +136,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MoveChannel(string draggedKey, string targetKey)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(draggedKey) || string.IsNullOrWhiteSpace(targetKey))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool updated = false;
|
|
||||||
using (_sync.EnterScope())
|
|
||||||
{
|
|
||||||
if (!_channels.ContainsKey(draggedKey) || !_channels.ContainsKey(targetKey))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fromIndex = _channelOrder.IndexOf(draggedKey);
|
|
||||||
var toIndex = _channelOrder.IndexOf(targetKey);
|
|
||||||
if (fromIndex < 0 || toIndex < 0 || fromIndex == toIndex)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_channelOrder.RemoveAt(fromIndex);
|
|
||||||
var insertIndex = Math.Clamp(toIndex, 0, _channelOrder.Count);
|
|
||||||
_channelOrder.Insert(insertIndex, draggedKey);
|
|
||||||
_chatConfigService.Current.ChannelOrder = new List<string>(_channelOrder);
|
|
||||||
_chatConfigService.Save();
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated)
|
|
||||||
{
|
|
||||||
PublishChannelListChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SetChatEnabledAsync(bool enabled)
|
public Task SetChatEnabledAsync(bool enabled)
|
||||||
=> enabled ? EnableChatAsync() : DisableChatAsync();
|
=> enabled ? EnableChatAsync() : DisableChatAsync();
|
||||||
|
|
||||||
@@ -550,7 +512,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
|
|
||||||
if (!_isLoggedIn || !_apiController.IsConnected)
|
if (!_isLoggedIn || !_apiController.IsConnected)
|
||||||
{
|
{
|
||||||
await LeaveCurrentZoneAsync(force, 0, 0).ConfigureAwait(false);
|
await LeaveCurrentZoneAsync(force, 0).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +520,6 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
{
|
{
|
||||||
var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
|
var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
|
||||||
var territoryId = (ushort)location.TerritoryId;
|
var territoryId = (ushort)location.TerritoryId;
|
||||||
var worldId = (ushort)location.ServerId;
|
|
||||||
|
|
||||||
string? zoneKey;
|
string? zoneKey;
|
||||||
ZoneChannelDefinition? definition = null;
|
ZoneChannelDefinition? definition = null;
|
||||||
@@ -575,14 +536,14 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
|
|
||||||
if (definition is null)
|
if (definition is null)
|
||||||
{
|
{
|
||||||
await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
|
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var descriptor = await BuildZoneDescriptorAsync(definition.Value).ConfigureAwait(false);
|
var descriptor = await BuildZoneDescriptorAsync(definition.Value).ConfigureAwait(false);
|
||||||
if (descriptor is null)
|
if (descriptor is null)
|
||||||
{
|
{
|
||||||
await LeaveCurrentZoneAsync(force, territoryId, worldId).ConfigureAwait(false);
|
await LeaveCurrentZoneAsync(force, territoryId).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +586,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LeaveCurrentZoneAsync(bool force, ushort territoryId, ushort worldId)
|
private async Task LeaveCurrentZoneAsync(bool force, ushort territoryId)
|
||||||
{
|
{
|
||||||
ChatChannelDescriptor? descriptor = null;
|
ChatChannelDescriptor? descriptor = null;
|
||||||
|
|
||||||
@@ -641,27 +602,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
state.StatusText = !_chatEnabled
|
state.StatusText = !_chatEnabled
|
||||||
? "Chat services disabled"
|
? "Chat services disabled"
|
||||||
: (_isConnected ? ZoneUnavailableMessage : "Disconnected from chat server");
|
: (_isConnected ? ZoneUnavailableMessage : "Disconnected from chat server");
|
||||||
if (territoryId != 0
|
state.DisplayName = "Zone Chat";
|
||||||
&& _dalamudUtilService.TerritoryData.Value.TryGetValue(territoryId, out var territoryName)
|
|
||||||
&& !string.IsNullOrWhiteSpace(territoryName))
|
|
||||||
{
|
|
||||||
state.DisplayName = territoryName;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state.DisplayName = "Zone Chat";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (worldId != 0)
|
|
||||||
{
|
|
||||||
state.Descriptor = new ChatChannelDescriptor
|
|
||||||
{
|
|
||||||
Type = ChatChannelType.Zone,
|
|
||||||
WorldId = worldId,
|
|
||||||
ZoneId = territoryId,
|
|
||||||
CustomKey = string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(_activeChannelKey, ZoneChannelKey, StringComparison.Ordinal))
|
if (string.Equals(_activeChannelKey, ZoneChannelKey, StringComparison.Ordinal))
|
||||||
@@ -1151,50 +1092,17 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
|||||||
{
|
{
|
||||||
_channelOrder.Clear();
|
_channelOrder.Clear();
|
||||||
|
|
||||||
var configuredOrder = _chatConfigService.Current.ChannelOrder;
|
if (_channels.ContainsKey(ZoneChannelKey))
|
||||||
if (configuredOrder.Count > 0)
|
|
||||||
{
|
{
|
||||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
_channelOrder.Add(ZoneChannelKey);
|
||||||
foreach (var key in configuredOrder)
|
|
||||||
{
|
|
||||||
if (_channels.ContainsKey(key) && seen.Add(key))
|
|
||||||
{
|
|
||||||
_channelOrder.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var remaining = _channels.Values
|
|
||||||
.Where(state => !seen.Contains(state.Key))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (remaining.Count > 0)
|
|
||||||
{
|
|
||||||
var zoneKeys = remaining
|
|
||||||
.Where(state => state.Type == ChatChannelType.Zone)
|
|
||||||
.Select(state => state.Key);
|
|
||||||
var groupKeys = remaining
|
|
||||||
.Where(state => state.Type == ChatChannelType.Group)
|
|
||||||
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Select(state => state.Key);
|
|
||||||
|
|
||||||
_channelOrder.AddRange(zoneKeys);
|
|
||||||
_channelOrder.AddRange(groupKeys);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_channels.ContainsKey(ZoneChannelKey))
|
|
||||||
{
|
|
||||||
_channelOrder.Add(ZoneChannelKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
var groups = _channels.Values
|
var groups = _channels.Values
|
||||||
.Where(state => state.Type == ChatChannelType.Group)
|
.Where(state => state.Type == ChatChannelType.Group)
|
||||||
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
|
.OrderBy(state => state.DisplayName, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(state => state.Key);
|
.Select(state => state.Key);
|
||||||
|
|
||||||
_channelOrder.AddRange(groups);
|
_channelOrder.AddRange(groups);
|
||||||
}
|
|
||||||
|
|
||||||
if (_activeChannelKey is null && _channelOrder.Count > 0)
|
if (_activeChannelKey is null && _channelOrder.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
"\t /light analyze - Opens the Lightless Character Data Analysis window" + Environment.NewLine +
|
"\t /light analyze - Opens the Lightless Character Data Analysis window" + Environment.NewLine +
|
||||||
"\t /light settings - Opens the Lightless Settings window" + Environment.NewLine +
|
"\t /light settings - Opens the Lightless Settings window" + Environment.NewLine +
|
||||||
"\t /light finder - Opens the Lightfinder window" + Environment.NewLine +
|
"\t /light finder - Opens the Lightfinder window" + Environment.NewLine +
|
||||||
"\t /light chat - Opens the Lightless Chat window"
|
"\t /light finder - Opens the Lightless Chat window"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -239,7 +239,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
public bool IsInCombat { get; private set; } = false;
|
public bool IsInCombat { get; private set; } = false;
|
||||||
public bool IsPerforming { get; private set; } = false;
|
public bool IsPerforming { get; private set; } = false;
|
||||||
public bool IsInInstance { get; private set; } = false;
|
public bool IsInInstance { get; private set; } = false;
|
||||||
public bool IsInDuty => _condition[ConditionFlag.BoundByDuty];
|
|
||||||
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
|
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
|
||||||
public uint ClassJobId => _classJobId!.Value;
|
public uint ClassJobId => _classJobId!.Value;
|
||||||
public Lazy<Dictionary<uint, string>> JobData { get; private set; }
|
public Lazy<Dictionary<uint, string>> JobData { get; private set; }
|
||||||
@@ -249,32 +248,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
public bool IsLodEnabled { get; private set; }
|
public bool IsLodEnabled { get; private set; }
|
||||||
public LightlessMediator Mediator { get; }
|
public LightlessMediator Mediator { get; }
|
||||||
|
|
||||||
public bool IsInFieldOperation
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!IsInDuty)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var territoryId = _clientState.TerritoryType;
|
|
||||||
if (territoryId == 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TerritoryData.Value.TryGetValue(territoryId, out var name) || string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return name.Contains("Eureka", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| name.Contains("Bozja", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| name.Contains("Zadnor", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IGameObject? CreateGameObject(IntPtr reference)
|
public IGameObject? CreateGameObject(IntPtr reference)
|
||||||
{
|
{
|
||||||
EnsureIsOnFramework();
|
EnsureIsOnFramework();
|
||||||
|
|||||||
@@ -641,8 +641,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_activeBroadcastingCids = newSet;
|
_activeBroadcastingCids = newSet;
|
||||||
if (_logger.IsEnabled(LogLevel.Trace))
|
if (_logger.IsEnabled(LogLevel.Information))
|
||||||
_logger.LogTrace("Active broadcast IDs: {Cids}", string.Join(',', _activeBroadcastingCids));
|
_logger.LogInformation("Active broadcast CIDs: {Cids}", string.Join(',', _activeBroadcastingCids));
|
||||||
FlagRefresh();
|
FlagRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,11 +126,11 @@ public sealed class TextureMetadataHelper
|
|||||||
private const string TextureSegment = "/texture/";
|
private const string TextureSegment = "/texture/";
|
||||||
private const string MaterialSegment = "/material/";
|
private const string MaterialSegment = "/material/";
|
||||||
|
|
||||||
private const uint NormalSamplerId = ShpkFile.NormalSamplerId;
|
private const uint NormalSamplerId = 0x0C5EC1F1u;
|
||||||
private const uint IndexSamplerId = ShpkFile.IndexSamplerId;
|
private const uint IndexSamplerId = 0x565F8FD8u;
|
||||||
private const uint SpecularSamplerId = ShpkFile.SpecularSamplerId;
|
private const uint SpecularSamplerId = 0x2B99E025u;
|
||||||
private const uint DiffuseSamplerId = ShpkFile.DiffuseSamplerId;
|
private const uint DiffuseSamplerId = 0x115306BEu;
|
||||||
private const uint MaskSamplerId = ShpkFile.MaskSamplerId;
|
private const uint MaskSamplerId = 0x8A4E82B6u;
|
||||||
|
|
||||||
public TextureMetadataHelper(ILogger<TextureMetadataHelper> logger, IDataManager dataManager)
|
public TextureMetadataHelper(ILogger<TextureMetadataHelper> logger, IDataManager dataManager)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -843,16 +843,12 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
//Filter of not grouped/foldered and offline pairs
|
//Filter of not grouped/foldered and offline pairs
|
||||||
var allOnlineNotTaggedPairs = SortEntries(allEntries.Where(FilterNotTaggedUsers));
|
var allOnlineNotTaggedPairs = SortEntries(allEntries.Where(FilterNotTaggedUsers));
|
||||||
if (allOnlineNotTaggedPairs.Count > 0 && _configService.Current.ShowOfflineUsersSeparately) {
|
var onlineNotTaggedPairs = SortEntries(filteredEntries.Where(e => FilterNotTaggedUsers(e) && FilterOnlineOrPausedSelf(e)));
|
||||||
var filteredOnlineEntries = SortOnlineEntries(filteredEntries.Where(e => FilterNotTaggedUsers(e) && FilterOnlineOrPausedSelf(e)));
|
|
||||||
|
if (allOnlineNotTaggedPairs.Count > 0)
|
||||||
|
{
|
||||||
drawFolders.Add(_drawEntityFactory.CreateTagFolder(
|
drawFolders.Add(_drawEntityFactory.CreateTagFolder(
|
||||||
TagHandler.CustomOnlineTag,
|
_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag,
|
||||||
filteredOnlineEntries,
|
|
||||||
allOnlineNotTaggedPairs));
|
|
||||||
} else if (allOnlineNotTaggedPairs.Count > 0 && !_configService.Current.ShowOfflineUsersSeparately) {
|
|
||||||
var onlineNotTaggedPairs = SortEntries(filteredEntries.Where(FilterNotTaggedUsers));
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateTagFolder(
|
|
||||||
TagHandler.CustomAllTag,
|
|
||||||
onlineNotTaggedPairs,
|
onlineNotTaggedPairs,
|
||||||
allOnlineNotTaggedPairs));
|
allOnlineNotTaggedPairs));
|
||||||
}
|
}
|
||||||
@@ -889,7 +885,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool PassesFilter(PairUiEntry entry, string filter)
|
private bool PassesFilter(PairUiEntry entry, string filter)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(filter)) return true;
|
if (string.IsNullOrEmpty(filter)) return true;
|
||||||
|
|
||||||
@@ -950,17 +946,6 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableList<PairUiEntry> SortOnlineEntries(IEnumerable<PairUiEntry> entries)
|
|
||||||
{
|
|
||||||
var entryList = entries.ToList();
|
|
||||||
return _configService.Current.OnlinePairSortMode switch
|
|
||||||
{
|
|
||||||
OnlinePairSortMode.Alphabetical => [.. entryList.OrderBy(e => AlphabeticalSortKey(e), StringComparer.OrdinalIgnoreCase)],
|
|
||||||
OnlinePairSortMode.PreferredDirectPairs => SortVisibleByPreferred(entryList),
|
|
||||||
_ => SortEntries(entryList),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImmutableList<PairUiEntry> SortVisibleByMetric(IEnumerable<PairUiEntry> entries, Func<PairUiEntry, long> selector)
|
private ImmutableList<PairUiEntry> SortVisibleByMetric(IEnumerable<PairUiEntry> entries, Func<PairUiEntry, long> selector)
|
||||||
{
|
{
|
||||||
return [.. entries
|
return [.. entries
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using Dalamud.Interface.Utility.Raii;
|
|||||||
using LightlessSync.UI.Handlers;
|
using LightlessSync.UI.Handlers;
|
||||||
using LightlessSync.UI.Models;
|
using LightlessSync.UI.Models;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using LightlessSync.UI;
|
||||||
using LightlessSync.UI.Style;
|
using LightlessSync.UI.Style;
|
||||||
using OtterGui.Text;
|
|
||||||
|
|
||||||
namespace LightlessSync.UI.Components;
|
namespace LightlessSync.UI.Components;
|
||||||
|
|
||||||
@@ -113,13 +113,9 @@ public abstract class DrawFolderBase : IDrawFolder
|
|||||||
using var indent = ImRaii.PushIndent(_uiSharedService.GetIconSize(FontAwesomeIcon.EllipsisV).X + ImGui.GetStyle().ItemSpacing.X, false);
|
using var indent = ImRaii.PushIndent(_uiSharedService.GetIconSize(FontAwesomeIcon.EllipsisV).X + ImGui.GetStyle().ItemSpacing.X, false);
|
||||||
if (DrawPairs.Any())
|
if (DrawPairs.Any())
|
||||||
{
|
{
|
||||||
using var clipper = ImUtf8.ListClipper(DrawPairs.Count, ImGui.GetFrameHeightWithSpacing());
|
foreach (var item in DrawPairs)
|
||||||
while (clipper.Step())
|
|
||||||
{
|
{
|
||||||
for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
item.DrawPairedClient();
|
||||||
{
|
|
||||||
DrawPairs[i].DrawPairedClient();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -169,16 +169,11 @@ public class DrawFolderTag : DrawFolderBase
|
|||||||
|
|
||||||
protected override float DrawRightSide(float currentRightSideX)
|
protected override float DrawRightSide(float currentRightSideX)
|
||||||
{
|
{
|
||||||
if (string.Equals(_id, TagHandler.CustomVisibleTag, StringComparison.Ordinal))
|
if (_id == TagHandler.CustomVisibleTag)
|
||||||
{
|
{
|
||||||
return DrawVisibleFilter(currentRightSideX);
|
return DrawVisibleFilter(currentRightSideX);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(_id, TagHandler.CustomOnlineTag, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return DrawOnlineFilter(currentRightSideX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!RenderPause)
|
if (!RenderPause)
|
||||||
{
|
{
|
||||||
return currentRightSideX;
|
return currentRightSideX;
|
||||||
@@ -259,7 +254,7 @@ public class DrawFolderTag : DrawFolderBase
|
|||||||
foreach (VisiblePairSortMode mode in Enum.GetValues<VisiblePairSortMode>())
|
foreach (VisiblePairSortMode mode in Enum.GetValues<VisiblePairSortMode>())
|
||||||
{
|
{
|
||||||
var selected = _configService.Current.VisiblePairSortMode == mode;
|
var selected = _configService.Current.VisiblePairSortMode == mode;
|
||||||
if (ImGui.MenuItem(GetSortVisibleLabel(mode), string.Empty, selected))
|
if (ImGui.MenuItem(GetSortLabel(mode), string.Empty, selected))
|
||||||
{
|
{
|
||||||
if (!selected)
|
if (!selected)
|
||||||
{
|
{
|
||||||
@@ -278,49 +273,7 @@ public class DrawFolderTag : DrawFolderBase
|
|||||||
return buttonStart - spacingX;
|
return buttonStart - spacingX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float DrawOnlineFilter(float currentRightSideX)
|
private static string GetSortLabel(VisiblePairSortMode mode) => mode switch
|
||||||
{
|
|
||||||
var buttonSize = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Filter);
|
|
||||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
|
||||||
var buttonStart = currentRightSideX - buttonSize.X;
|
|
||||||
|
|
||||||
ImGui.SameLine(buttonStart);
|
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Filter))
|
|
||||||
{
|
|
||||||
SuppressNextRowToggle();
|
|
||||||
ImGui.OpenPopup($"online-filter-{_id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
UiSharedService.AttachToolTip("Adjust how online pairs are ordered.");
|
|
||||||
|
|
||||||
if (ImGui.BeginPopup($"online-filter-{_id}"))
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted("Online Pair Ordering");
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
foreach (OnlinePairSortMode mode in Enum.GetValues<OnlinePairSortMode>())
|
|
||||||
{
|
|
||||||
var selected = _configService.Current.OnlinePairSortMode == mode;
|
|
||||||
if (ImGui.MenuItem(GetSortOnlineLabel(mode), string.Empty, selected))
|
|
||||||
{
|
|
||||||
if (!selected)
|
|
||||||
{
|
|
||||||
_configService.Current.OnlinePairSortMode = mode;
|
|
||||||
_configService.Save();
|
|
||||||
_mediator.Publish(new RefreshUiMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttonStart - spacingX;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetSortVisibleLabel(VisiblePairSortMode mode) => mode switch
|
|
||||||
{
|
{
|
||||||
VisiblePairSortMode.Alphabetical => "Alphabetical",
|
VisiblePairSortMode.Alphabetical => "Alphabetical",
|
||||||
VisiblePairSortMode.VramUsage => "VRAM usage (descending)",
|
VisiblePairSortMode.VramUsage => "VRAM usage (descending)",
|
||||||
@@ -329,11 +282,4 @@ public class DrawFolderTag : DrawFolderBase
|
|||||||
VisiblePairSortMode.PreferredDirectPairs => "Preferred permissions & Direct pairs",
|
VisiblePairSortMode.PreferredDirectPairs => "Preferred permissions & Direct pairs",
|
||||||
_ => "Default",
|
_ => "Default",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string GetSortOnlineLabel(OnlinePairSortMode mode) => mode switch
|
|
||||||
{
|
|
||||||
OnlinePairSortMode.Alphabetical => "Alphabetical",
|
|
||||||
OnlinePairSortMode.PreferredDirectPairs => "Preferred permissions & Direct pairs",
|
|
||||||
_ => "Default",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -27,11 +27,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
private const float MinTextureFilterPaneWidth = 305f;
|
private const float MinTextureFilterPaneWidth = 305f;
|
||||||
private const float MaxTextureFilterPaneWidth = 405f;
|
private const float MaxTextureFilterPaneWidth = 405f;
|
||||||
private const float MinTextureDetailPaneWidth = 480f;
|
private const float MinTextureDetailPaneWidth = 580f;
|
||||||
private const float MaxTextureDetailPaneWidth = 720f;
|
private const float MaxTextureDetailPaneWidth = 720f;
|
||||||
private const float TextureFilterSplitterWidth = 8f;
|
|
||||||
private const float TextureDetailSplitterWidth = 12f;
|
|
||||||
private const float TextureDetailSplitterCollapsedWidth = 18f;
|
|
||||||
private const float SelectedFilePanelLogicalHeight = 90f;
|
private const float SelectedFilePanelLogicalHeight = 90f;
|
||||||
private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f);
|
private static readonly Vector4 SelectedTextureRowTextColor = new(0f, 0f, 0f, 1f);
|
||||||
|
|
||||||
@@ -83,7 +80,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
private bool _modalOpen = false;
|
private bool _modalOpen = false;
|
||||||
private bool _showModal = false;
|
private bool _showModal = false;
|
||||||
private bool _textureRowsDirty = true;
|
private bool _textureRowsDirty = true;
|
||||||
private bool _textureDetailCollapsed = false;
|
|
||||||
private bool _conversionFailed;
|
private bool _conversionFailed;
|
||||||
private bool _showAlreadyAddedTransients = false;
|
private bool _showAlreadyAddedTransients = false;
|
||||||
private bool _acknowledgeReview = false;
|
private bool _acknowledgeReview = false;
|
||||||
@@ -115,7 +111,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_hasUpdate = true;
|
_hasUpdate = true;
|
||||||
});
|
});
|
||||||
WindowBuilder.For(this)
|
WindowBuilder.For(this)
|
||||||
.SetSizeConstraints(new Vector2(1240, 680), new Vector2(3840, 2160))
|
.SetSizeConstraints(new Vector2(1650, 1000), new Vector2(3840, 2160))
|
||||||
.Apply();
|
.Apply();
|
||||||
|
|
||||||
_conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged;
|
_conversionProgress.ProgressChanged += ConversionProgress_ProgressChanged;
|
||||||
@@ -1209,52 +1205,35 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
var availableSize = ImGui.GetContentRegionAvail();
|
var availableSize = ImGui.GetContentRegionAvail();
|
||||||
var windowPos = ImGui.GetWindowPos();
|
var windowPos = ImGui.GetWindowPos();
|
||||||
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
var spacingX = ImGui.GetStyle().ItemSpacing.X;
|
||||||
var filterSplitterWidth = TextureFilterSplitterWidth * scale;
|
var splitterWidth = 6f * scale;
|
||||||
var detailSplitterWidth = (_textureDetailCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth) * scale;
|
|
||||||
var totalSplitterWidth = filterSplitterWidth + detailSplitterWidth;
|
|
||||||
var totalSpacing = 2 * spacingX;
|
|
||||||
const float minFilterWidth = MinTextureFilterPaneWidth;
|
const float minFilterWidth = MinTextureFilterPaneWidth;
|
||||||
const float minDetailWidth = MinTextureDetailPaneWidth;
|
const float minDetailWidth = MinTextureDetailPaneWidth;
|
||||||
const float minCenterWidth = 340f;
|
const float minCenterWidth = 340f;
|
||||||
|
|
||||||
var detailMinForLayout = _textureDetailCollapsed ? 0f : minDetailWidth;
|
var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - minDetailWidth - minCenterWidth - 2 * (splitterWidth + spacingX));
|
||||||
var dynamicFilterMax = Math.Max(minFilterWidth, availableSize.X - detailMinForLayout - minCenterWidth - totalSplitterWidth - totalSpacing);
|
|
||||||
var filterMaxBound = Math.Min(MaxTextureFilterPaneWidth, dynamicFilterMax);
|
var filterMaxBound = Math.Min(MaxTextureFilterPaneWidth, dynamicFilterMax);
|
||||||
var filterWidth = Math.Clamp(_textureFilterPaneWidth, minFilterWidth, filterMaxBound);
|
var filterWidth = Math.Clamp(_textureFilterPaneWidth, minFilterWidth, filterMaxBound);
|
||||||
|
|
||||||
var dynamicDetailMax = Math.Max(detailMinForLayout, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing);
|
var dynamicDetailMax = Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX));
|
||||||
var detailMaxBound = _textureDetailCollapsed ? 0f : Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax);
|
var detailMaxBound = Math.Min(MaxTextureDetailPaneWidth, dynamicDetailMax);
|
||||||
var detailWidth = _textureDetailCollapsed ? 0f : Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound);
|
var detailWidth = Math.Clamp(_textureDetailPaneWidth, minDetailWidth, detailMaxBound);
|
||||||
|
|
||||||
var centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
|
var centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
|
||||||
|
|
||||||
if (centerWidth < minCenterWidth)
|
if (centerWidth < minCenterWidth)
|
||||||
{
|
{
|
||||||
var deficit = minCenterWidth - centerWidth;
|
var deficit = minCenterWidth - centerWidth;
|
||||||
if (!_textureDetailCollapsed)
|
detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth,
|
||||||
{
|
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
|
||||||
detailWidth = Math.Clamp(detailWidth - deficit, minDetailWidth,
|
centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
|
||||||
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
|
if (centerWidth < minCenterWidth)
|
||||||
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
|
|
||||||
if (centerWidth < minCenterWidth)
|
|
||||||
{
|
|
||||||
deficit = minCenterWidth - centerWidth;
|
|
||||||
filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth,
|
|
||||||
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
|
|
||||||
detailWidth = Math.Clamp(detailWidth, minDetailWidth,
|
|
||||||
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - totalSplitterWidth - totalSpacing)));
|
|
||||||
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
|
|
||||||
if (centerWidth < minCenterWidth)
|
|
||||||
{
|
|
||||||
centerWidth = minCenterWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
deficit = minCenterWidth - centerWidth;
|
||||||
filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth,
|
filterWidth = Math.Clamp(filterWidth - deficit, minFilterWidth,
|
||||||
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - totalSplitterWidth - totalSpacing)));
|
Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - detailWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
|
||||||
centerWidth = availableSize.X - filterWidth - detailWidth - totalSplitterWidth - totalSpacing;
|
detailWidth = Math.Clamp(detailWidth, minDetailWidth,
|
||||||
|
Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - filterWidth - minCenterWidth - 2 * (splitterWidth + spacingX))));
|
||||||
|
centerWidth = availableSize.X - filterWidth - detailWidth - 2 * (splitterWidth + spacingX);
|
||||||
if (centerWidth < minCenterWidth)
|
if (centerWidth < minCenterWidth)
|
||||||
{
|
{
|
||||||
centerWidth = minCenterWidth;
|
centerWidth = minCenterWidth;
|
||||||
@@ -1263,10 +1242,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
_textureFilterPaneWidth = filterWidth;
|
_textureFilterPaneWidth = filterWidth;
|
||||||
if (!_textureDetailCollapsed)
|
_textureDetailPaneWidth = detailWidth;
|
||||||
{
|
|
||||||
_textureDetailPaneWidth = detailWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
ImGui.BeginGroup();
|
||||||
using (var filters = ImRaii.Child("textureFilters", new Vector2(filterWidth, 0), true))
|
using (var filters = ImRaii.Child("textureFilters", new Vector2(filterWidth, 0), true))
|
||||||
@@ -1288,8 +1264,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
var filterMax = ImGui.GetItemRectMax();
|
var filterMax = ImGui.GetItemRectMax();
|
||||||
var filterHeight = filterMax.Y - filterMin.Y;
|
var filterHeight = filterMax.Y - filterMin.Y;
|
||||||
var filterTopLocal = filterMin - windowPos;
|
var filterTopLocal = filterMin - windowPos;
|
||||||
var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - detailMinForLayout - totalSplitterWidth - totalSpacing));
|
var maxFilterResize = Math.Min(MaxTextureFilterPaneWidth, Math.Max(minFilterWidth, availableSize.X - minCenterWidth - minDetailWidth - 2 * (splitterWidth + spacingX)));
|
||||||
DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize, out _);
|
DrawVerticalResizeHandle("##textureFilterSplitter", filterTopLocal.Y, filterHeight, ref _textureFilterPaneWidth, minFilterWidth, maxFilterResize);
|
||||||
|
|
||||||
TextureRow? selectedRow;
|
TextureRow? selectedRow;
|
||||||
ImGui.BeginGroup();
|
ImGui.BeginGroup();
|
||||||
@@ -1303,36 +1279,15 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
var tableMax = ImGui.GetItemRectMax();
|
var tableMax = ImGui.GetItemRectMax();
|
||||||
var tableHeight = tableMax.Y - tableMin.Y;
|
var tableHeight = tableMax.Y - tableMin.Y;
|
||||||
var tableTopLocal = tableMin - windowPos;
|
var tableTopLocal = tableMin - windowPos;
|
||||||
var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - totalSplitterWidth - totalSpacing));
|
var maxDetailResize = Math.Min(MaxTextureDetailPaneWidth, Math.Max(minDetailWidth, availableSize.X - _textureFilterPaneWidth - minCenterWidth - 2 * (splitterWidth + spacingX)));
|
||||||
var detailToggle = DrawVerticalResizeHandle(
|
DrawVerticalResizeHandle("##textureDetailSplitter", tableTopLocal.Y, tableHeight, ref _textureDetailPaneWidth, minDetailWidth, maxDetailResize, invert: true);
|
||||||
"##textureDetailSplitter",
|
|
||||||
tableTopLocal.Y,
|
|
||||||
tableHeight,
|
|
||||||
ref _textureDetailPaneWidth,
|
|
||||||
minDetailWidth,
|
|
||||||
maxDetailResize,
|
|
||||||
out var detailDragging,
|
|
||||||
invert: true,
|
|
||||||
showToggle: true,
|
|
||||||
isCollapsed: _textureDetailCollapsed);
|
|
||||||
if (detailToggle)
|
|
||||||
{
|
|
||||||
_textureDetailCollapsed = !_textureDetailCollapsed;
|
|
||||||
}
|
|
||||||
if (_textureDetailCollapsed && detailDragging)
|
|
||||||
{
|
|
||||||
_textureDetailCollapsed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_textureDetailCollapsed)
|
ImGui.BeginGroup();
|
||||||
|
using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true))
|
||||||
{
|
{
|
||||||
ImGui.BeginGroup();
|
DrawTextureDetail(selectedRow);
|
||||||
using (var detailChild = ImRaii.Child("textureDetailPane", new Vector2(detailWidth, 0), true))
|
|
||||||
{
|
|
||||||
DrawTextureDetail(selectedRow);
|
|
||||||
}
|
|
||||||
ImGui.EndGroup();
|
|
||||||
}
|
}
|
||||||
|
ImGui.EndGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawTextureFilters(
|
private void DrawTextureFilters(
|
||||||
@@ -1980,118 +1935,26 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawVerticalResizeHandle(
|
private void DrawVerticalResizeHandle(string id, float topY, float height, ref float leftWidth, float minWidth, float maxWidth, bool invert = false)
|
||||||
string id,
|
|
||||||
float topY,
|
|
||||||
float height,
|
|
||||||
ref float leftWidth,
|
|
||||||
float minWidth,
|
|
||||||
float maxWidth,
|
|
||||||
out bool isDragging,
|
|
||||||
bool invert = false,
|
|
||||||
bool showToggle = false,
|
|
||||||
bool isCollapsed = false)
|
|
||||||
{
|
{
|
||||||
var scale = ImGuiHelpers.GlobalScale;
|
var scale = ImGuiHelpers.GlobalScale;
|
||||||
var splitterWidth = (showToggle
|
var splitterWidth = 8f * scale;
|
||||||
? (isCollapsed ? TextureDetailSplitterCollapsedWidth : TextureDetailSplitterWidth)
|
|
||||||
: TextureFilterSplitterWidth) * scale;
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var cursor = ImGui.GetCursorPos();
|
var cursor = ImGui.GetCursorPos();
|
||||||
var contentMin = ImGui.GetWindowContentRegionMin();
|
ImGui.SetCursorPos(new Vector2(cursor.X, topY));
|
||||||
var contentMax = ImGui.GetWindowContentRegionMax();
|
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("ButtonDefault"));
|
||||||
var clampedTop = MathF.Max(topY, contentMin.Y);
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple"));
|
||||||
var clampedBottom = MathF.Min(topY + height, contentMax.Y);
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive"));
|
||||||
var clampedHeight = MathF.Max(0f, clampedBottom - clampedTop);
|
ImGui.Button(id, new Vector2(splitterWidth, height));
|
||||||
var splitterRounding = ImGui.GetStyle().FrameRounding;
|
ImGui.PopStyleColor(3);
|
||||||
ImGui.SetCursorPos(new Vector2(cursor.X, clampedTop));
|
|
||||||
if (clampedHeight <= 0f)
|
|
||||||
{
|
|
||||||
isDragging = false;
|
|
||||||
ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.InvisibleButton(id, new Vector2(splitterWidth, clampedHeight));
|
if (ImGui.IsItemActive())
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
|
||||||
var rectMin = ImGui.GetItemRectMin();
|
|
||||||
var rectMax = ImGui.GetItemRectMax();
|
|
||||||
var windowPos = ImGui.GetWindowPos();
|
|
||||||
var clipMin = windowPos + contentMin;
|
|
||||||
var clipMax = windowPos + contentMax;
|
|
||||||
drawList.PushClipRect(clipMin, clipMax, true);
|
|
||||||
var clipInset = 1f * scale;
|
|
||||||
var drawMin = new Vector2(
|
|
||||||
MathF.Max(rectMin.X, clipMin.X),
|
|
||||||
MathF.Max(rectMin.Y, clipMin.Y));
|
|
||||||
var drawMax = new Vector2(
|
|
||||||
MathF.Min(rectMax.X, clipMax.X - clipInset),
|
|
||||||
MathF.Min(rectMax.Y, clipMax.Y));
|
|
||||||
var hovered = ImGui.IsItemHovered();
|
|
||||||
isDragging = ImGui.IsItemActive();
|
|
||||||
var baseColor = UIColors.Get("ButtonDefault");
|
|
||||||
var hoverColor = UIColors.Get("LightlessPurple");
|
|
||||||
var activeColor = UIColors.Get("LightlessPurpleActive");
|
|
||||||
var handleColor = isDragging ? activeColor : hovered ? hoverColor : baseColor;
|
|
||||||
drawList.AddRectFilled(drawMin, drawMax, UiSharedService.Color(handleColor), splitterRounding);
|
|
||||||
drawList.AddRect(drawMin, drawMax, UiSharedService.Color(new Vector4(1f, 1f, 1f, 0.12f)), splitterRounding);
|
|
||||||
|
|
||||||
bool toggleHovered = false;
|
|
||||||
bool toggleClicked = false;
|
|
||||||
if (showToggle)
|
|
||||||
{
|
|
||||||
var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
|
|
||||||
Vector2 iconSize;
|
|
||||||
using (_uiSharedService.IconFont.Push())
|
|
||||||
{
|
|
||||||
iconSize = ImGui.CalcTextSize(icon.ToIconString());
|
|
||||||
}
|
|
||||||
|
|
||||||
var toggleHeight = MathF.Min(clampedHeight, 64f * scale);
|
|
||||||
var toggleMin = new Vector2(
|
|
||||||
drawMin.X,
|
|
||||||
drawMin.Y + (drawMax.Y - drawMin.Y - toggleHeight) / 2f);
|
|
||||||
var toggleMax = new Vector2(
|
|
||||||
drawMax.X,
|
|
||||||
toggleMin.Y + toggleHeight);
|
|
||||||
var toggleColorBase = UIColors.Get("LightlessPurple");
|
|
||||||
toggleHovered = ImGui.IsMouseHoveringRect(toggleMin, toggleMax);
|
|
||||||
var toggleBg = toggleHovered
|
|
||||||
? new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.65f)
|
|
||||||
: new Vector4(toggleColorBase.X, toggleColorBase.Y, toggleColorBase.Z, 0.35f);
|
|
||||||
if (toggleHovered)
|
|
||||||
{
|
|
||||||
UiSharedService.AttachToolTip(isCollapsed ? "Show texture details." : "Hide texture details.");
|
|
||||||
}
|
|
||||||
|
|
||||||
drawList.AddRectFilled(toggleMin, toggleMax, UiSharedService.Color(toggleBg), splitterRounding);
|
|
||||||
drawList.AddRect(toggleMin, toggleMax, UiSharedService.Color(toggleColorBase), splitterRounding);
|
|
||||||
|
|
||||||
var iconPos = new Vector2(
|
|
||||||
drawMin.X + (drawMax.X - drawMin.X - iconSize.X) / 2f,
|
|
||||||
drawMin.Y + (drawMax.Y - drawMin.Y - iconSize.Y) / 2f);
|
|
||||||
using (_uiSharedService.IconFont.Push())
|
|
||||||
{
|
|
||||||
drawList.AddText(iconPos, ImGui.GetColorU32(ImGuiCol.Text), icon.ToIconString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toggleHovered && ImGui.IsMouseReleased(ImGuiMouseButton.Left) && !ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
|
||||||
{
|
|
||||||
toggleClicked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDragging && !toggleHovered)
|
|
||||||
{
|
{
|
||||||
var delta = ImGui.GetIO().MouseDelta.X / scale;
|
var delta = ImGui.GetIO().MouseDelta.X / scale;
|
||||||
leftWidth += invert ? -delta : delta;
|
leftWidth += invert ? -delta : delta;
|
||||||
leftWidth = Math.Clamp(leftWidth, minWidth, maxWidth);
|
leftWidth = Math.Clamp(leftWidth, minWidth, maxWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawList.PopClipRect();
|
|
||||||
|
|
||||||
ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y));
|
ImGui.SetCursorPos(new Vector2(cursor.X + splitterWidth + ImGui.GetStyle().ItemSpacing.X, cursor.Y));
|
||||||
return toggleClicked;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (IDalamudTextureWrap? Texture, bool IsLoading, string? Error) GetTexturePreview(TextureRow row)
|
private (IDalamudTextureWrap? Texture, bool IsLoading, string? Error) GetTexturePreview(TextureRow row)
|
||||||
@@ -2231,7 +2094,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Check, ImGuiColors.DalamudWhite);
|
ImGui.TextDisabled("-");
|
||||||
UiSharedService.AttachToolTip("Already stored in a compressed format; additional compression is disabled.");
|
UiSharedService.AttachToolTip("Already stored in a compressed format; additional compression is disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2312,10 +2175,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_textureSelections[key] = target;
|
_textureSelections[key] = target;
|
||||||
currentSelection = target;
|
currentSelection = target;
|
||||||
}
|
}
|
||||||
if (TextureMetadataHelper.TryGetRecommendationInfo(target, out var targetInfo))
|
|
||||||
{
|
|
||||||
UiSharedService.AttachToolTip($"{targetInfo.Title}{UiSharedService.TooltipSeparator}{targetInfo.Description}");
|
|
||||||
}
|
|
||||||
if (targetSelected)
|
if (targetSelected)
|
||||||
{
|
{
|
||||||
ImGui.SetItemDefaultFocus();
|
ImGui.SetItemDefaultFocus();
|
||||||
|
|||||||
@@ -301,14 +301,6 @@ namespace LightlessSync.UI
|
|||||||
bool ShellFinderEnabled = _configService.Current.SyncshellFinderEnabled;
|
bool ShellFinderEnabled = _configService.Current.SyncshellFinderEnabled;
|
||||||
bool isBroadcasting = _broadcastService.IsBroadcasting;
|
bool isBroadcasting = _broadcastService.IsBroadcasting;
|
||||||
|
|
||||||
if (isBroadcasting)
|
|
||||||
{
|
|
||||||
var warningColor = UIColors.Get("LightlessYellow");
|
|
||||||
_uiSharedService.DrawNoteLine("! ", warningColor,
|
|
||||||
new SeStringUtils.RichTextEntry("Syncshell Finder can only be changed while Lightfinder is disabled.", warningColor));
|
|
||||||
ImGuiHelpers.ScaledDummy(0.2f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBroadcasting)
|
if (isBroadcasting)
|
||||||
ImGui.BeginDisabled();
|
ImGui.BeginDisabled();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace LightlessSync.UI.Models;
|
|
||||||
|
|
||||||
public enum OnlinePairSortMode
|
|
||||||
{
|
|
||||||
Alphabetical = 0,
|
|
||||||
PreferredDirectPairs = 1,
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,10 @@ namespace LightlessSync.UI.Models;
|
|||||||
|
|
||||||
public enum VisiblePairSortMode
|
public enum VisiblePairSortMode
|
||||||
{
|
{
|
||||||
Alphabetical = 0,
|
Default = 0,
|
||||||
VramUsage = 1,
|
Alphabetical = 1,
|
||||||
EffectiveVramUsage = 2,
|
VramUsage = 2,
|
||||||
TriangleCount = 3,
|
EffectiveVramUsage = 3,
|
||||||
PreferredDirectPairs = 4,
|
TriangleCount = 4,
|
||||||
|
PreferredDirectPairs = 5,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1463,10 +1463,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
DrawPairPropertyRow("Has Handler", FormatBool(debugInfo.HasHandler));
|
DrawPairPropertyRow("Has Handler", FormatBool(debugInfo.HasHandler));
|
||||||
DrawPairPropertyRow("Handler Initialized", FormatBool(debugInfo.HandlerInitialized));
|
DrawPairPropertyRow("Handler Initialized", FormatBool(debugInfo.HandlerInitialized));
|
||||||
DrawPairPropertyRow("Handler Visible", FormatBool(debugInfo.HandlerVisible));
|
DrawPairPropertyRow("Handler Visible", FormatBool(debugInfo.HandlerVisible));
|
||||||
DrawPairPropertyRow("Last Time person rendered in", FormatTimestamp(debugInfo.InvisibleSinceUtc));
|
|
||||||
DrawPairPropertyRow("Handler Timer Temp Collection removal", FormatCountdown(debugInfo.VisibilityEvictionRemainingSeconds));
|
|
||||||
DrawPairPropertyRow("Handler Scheduled For Deletion", FormatBool(debugInfo.HandlerScheduledForDeletion));
|
DrawPairPropertyRow("Handler Scheduled For Deletion", FormatBool(debugInfo.HandlerScheduledForDeletion));
|
||||||
|
|
||||||
DrawPairPropertyRow("Note", pair.GetNote() ?? "(none)");
|
DrawPairPropertyRow("Note", pair.GetNote() ?? "(none)");
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
@@ -1701,19 +1698,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
return value is null ? "n/a" : value.Value.ToLocalTime().ToString("G", CultureInfo.CurrentCulture);
|
return value is null ? "n/a" : value.Value.ToLocalTime().ToString("G", CultureInfo.CurrentCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? FormatCountdown(double? remainingSeconds)
|
|
||||||
{
|
|
||||||
if (!remainingSeconds.HasValue)
|
|
||||||
return "No";
|
|
||||||
|
|
||||||
var secs = Math.Max(0, remainingSeconds.Value);
|
|
||||||
var t = TimeSpan.FromSeconds(secs);
|
|
||||||
|
|
||||||
return t.TotalHours >= 1
|
|
||||||
? $"{(int)t.TotalHours:00}:{t.Minutes:00}:{t.Seconds:00}"
|
|
||||||
: $"{(int)t.TotalMinutes:00}:{t.Seconds:00}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatBytes(long value) => value < 0 ? "n/a" : UiSharedService.ByteToString(value);
|
private static string FormatBytes(long value) => value < 0 ? "n/a" : UiSharedService.ByteToString(value);
|
||||||
|
|
||||||
private static string FormatCharacterId(uint id) => id == uint.MaxValue ? "n/a" : $"{id} (0x{id:X8})";
|
private static string FormatCharacterId(uint id) => id == uint.MaxValue ? "n/a" : $"{id} (0x{id:X8})";
|
||||||
|
|||||||
@@ -350,9 +350,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
? shell.Group.Alias
|
? shell.Group.Alias
|
||||||
: shell.Group.GID;
|
: shell.Group.GID;
|
||||||
|
|
||||||
var style = ImGui.GetStyle();
|
|
||||||
float startX = ImGui.GetCursorPosX();
|
float startX = ImGui.GetCursorPosX();
|
||||||
float availW = ImGui.GetContentRegionAvail().X;
|
float availWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
float rightTextW = ImGui.CalcTextSize(broadcasterName).X;
|
||||||
|
|
||||||
ImGui.BeginGroup();
|
ImGui.BeginGroup();
|
||||||
|
|
||||||
@@ -364,45 +364,13 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
Mediator.Publish(new GroupProfileOpenStandaloneMessage(shell.Group));
|
Mediator.Publish(new GroupProfileOpenStandaloneMessage(shell.Group));
|
||||||
}
|
}
|
||||||
|
|
||||||
float nameRightX = ImGui.GetItemRectMax().X;
|
ImGui.SameLine();
|
||||||
|
float rightX = startX + availWidth - rightTextW;
|
||||||
var regionMinScreen = ImGui.GetCursorScreenPos();
|
var pos = ImGui.GetCursorPos();
|
||||||
float regionRightX = regionMinScreen.X + availW;
|
ImGui.SetCursorPos(new Vector2(rightX, pos.Y + 3f * ImGuiHelpers.GlobalScale));
|
||||||
|
ImGui.TextUnformatted(broadcasterName);
|
||||||
float minBroadcasterX = nameRightX + style.ItemSpacing.X;
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Broadcaster of the syncshell.");
|
||||||
float maxBroadcasterWidth = regionRightX - minBroadcasterX;
|
|
||||||
|
|
||||||
string broadcasterToShow = broadcasterName;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(broadcasterName) && maxBroadcasterWidth > 0f)
|
|
||||||
{
|
|
||||||
float bcFullWidth = ImGui.CalcTextSize(broadcasterName).X;
|
|
||||||
string toolTip;
|
|
||||||
|
|
||||||
if (bcFullWidth > maxBroadcasterWidth)
|
|
||||||
{
|
|
||||||
broadcasterToShow = TruncateTextToWidth(broadcasterName, maxBroadcasterWidth);
|
|
||||||
toolTip = broadcasterName + Environment.NewLine + Environment.NewLine + "Broadcaster of the syncshell.";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
toolTip = "Broadcaster of the syncshell.";
|
|
||||||
}
|
|
||||||
|
|
||||||
float bcWidth = ImGui.CalcTextSize(broadcasterToShow).X;
|
|
||||||
|
|
||||||
float broadX = regionRightX - bcWidth;
|
|
||||||
|
|
||||||
broadX = MathF.Max(broadX, minBroadcasterX);
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
var curPos = ImGui.GetCursorPos();
|
|
||||||
ImGui.SetCursorPos(new Vector2(broadX - regionMinScreen.X + startX, curPos.Y + 3f * ImGuiHelpers.GlobalScale));
|
|
||||||
ImGui.TextUnformatted(broadcasterToShow);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(toolTip);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndGroup();
|
ImGui.EndGroup();
|
||||||
|
|
||||||
@@ -622,40 +590,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
float widthUsed = cursorLocalX - baseLocal.X;
|
float widthUsed = cursorLocalX - baseLocal.X;
|
||||||
return (widthUsed, rowHeight);
|
return (widthUsed, rowHeight);
|
||||||
}
|
}
|
||||||
private static string TruncateTextToWidth(string text, float maxWidth)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(text))
|
|
||||||
return text;
|
|
||||||
|
|
||||||
const string ellipsis = "...";
|
|
||||||
float ellipsisWidth = ImGui.CalcTextSize(ellipsis).X;
|
|
||||||
|
|
||||||
if (maxWidth <= ellipsisWidth)
|
|
||||||
return ellipsis;
|
|
||||||
|
|
||||||
int low = 0;
|
|
||||||
int high = text.Length;
|
|
||||||
string best = ellipsis;
|
|
||||||
|
|
||||||
while (low <= high)
|
|
||||||
{
|
|
||||||
int mid = (low + high) / 2;
|
|
||||||
string candidate = string.Concat(text.AsSpan(0, mid), ellipsis);
|
|
||||||
float width = ImGui.CalcTextSize(candidate).X;
|
|
||||||
|
|
||||||
if (width <= maxWidth)
|
|
||||||
{
|
|
||||||
best = candidate;
|
|
||||||
low = mid + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
high = mid - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return best;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IDalamudTextureWrap? GetIconWrap(uint iconId)
|
private IDalamudTextureWrap? GetIconWrap(uint iconId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,11 +11,8 @@ using LightlessSync.LightlessConfiguration;
|
|||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Chat;
|
using LightlessSync.Services.Chat;
|
||||||
using LightlessSync.Services.LightFinder;
|
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Services.ServerConfiguration;
|
|
||||||
using LightlessSync.UI.Services;
|
using LightlessSync.UI.Services;
|
||||||
using LightlessSync.UI.Style;
|
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using LightlessSync.WebAPI.SignalR.Utils;
|
using LightlessSync.WebAPI.SignalR.Utils;
|
||||||
@@ -26,49 +23,35 @@ namespace LightlessSync.UI;
|
|||||||
public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
private const string ChatDisabledStatus = "Chat services disabled";
|
private const string ChatDisabledStatus = "Chat services disabled";
|
||||||
private const string ZoneUnavailableStatus = "Zone chat is only available in major cities.";
|
|
||||||
private const string SettingsPopupId = "zone_chat_settings_popup";
|
private const string SettingsPopupId = "zone_chat_settings_popup";
|
||||||
private const string ReportPopupId = "Report Message##zone_chat_report_popup";
|
private const string ReportPopupId = "Report Message##zone_chat_report_popup";
|
||||||
private const string ChannelDragPayloadId = "zone_chat_channel_drag";
|
|
||||||
private const float DefaultWindowOpacity = .97f;
|
private const float DefaultWindowOpacity = .97f;
|
||||||
private const float DefaultUnfocusedWindowOpacity = 0.6f;
|
|
||||||
private const float MinWindowOpacity = 0.05f;
|
private const float MinWindowOpacity = 0.05f;
|
||||||
private const float MaxWindowOpacity = 1f;
|
private const float MaxWindowOpacity = 1f;
|
||||||
private const float MinChatFontScale = 0.75f;
|
private const float MinChatFontScale = 0.75f;
|
||||||
private const float MaxChatFontScale = 1.5f;
|
private const float MaxChatFontScale = 1.5f;
|
||||||
private const float UnfocusedFadeOutSpeed = 0.22f;
|
|
||||||
private const float FocusFadeInSpeed = 2.0f;
|
|
||||||
private const int ReportReasonMaxLength = 500;
|
private const int ReportReasonMaxLength = 500;
|
||||||
private const int ReportContextMaxLength = 1000;
|
private const int ReportContextMaxLength = 1000;
|
||||||
private const int MaxChannelNoteTabLength = 25;
|
|
||||||
|
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly ZoneChatService _zoneChatService;
|
private readonly ZoneChatService _zoneChatService;
|
||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly LightFinderService _lightFinderService;
|
|
||||||
private readonly LightlessProfileManager _profileManager;
|
private readonly LightlessProfileManager _profileManager;
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly ChatConfigService _chatConfigService;
|
private readonly ChatConfigService _chatConfigService;
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
|
||||||
private readonly DalamudUtilService _dalamudUtilService;
|
|
||||||
private readonly IUiBuilder _uiBuilder;
|
|
||||||
private readonly Dictionary<string, string> _draftMessages = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, string> _draftMessages = new(StringComparer.Ordinal);
|
||||||
private readonly ImGuiWindowFlags _unpinnedWindowFlags;
|
private readonly ImGuiWindowFlags _unpinnedWindowFlags;
|
||||||
private float _currentWindowOpacity = DefaultWindowOpacity;
|
private float _currentWindowOpacity = DefaultWindowOpacity;
|
||||||
private float _baseWindowOpacity = DefaultWindowOpacity;
|
|
||||||
private bool _isWindowPinned;
|
private bool _isWindowPinned;
|
||||||
private bool _showRulesOverlay;
|
private bool _showRulesOverlay;
|
||||||
private bool _refocusChatInput;
|
private bool _refocusChatInput;
|
||||||
private string? _refocusChatInputKey;
|
private string? _refocusChatInputKey;
|
||||||
private bool _isWindowFocused = true;
|
|
||||||
private int _titleBarStylePopCount;
|
|
||||||
|
|
||||||
private string? _selectedChannelKey;
|
private string? _selectedChannelKey;
|
||||||
private bool _scrollToBottom = true;
|
private bool _scrollToBottom = true;
|
||||||
private float? _pendingChannelScroll;
|
private float? _pendingChannelScroll;
|
||||||
private float _channelScroll;
|
private float _channelScroll;
|
||||||
private float _channelScrollMax;
|
private float _channelScrollMax;
|
||||||
private readonly SeluneBrush _seluneBrush = new();
|
|
||||||
private ChatChannelSnapshot? _reportTargetChannel;
|
private ChatChannelSnapshot? _reportTargetChannel;
|
||||||
private ChatMessageEntry? _reportTargetMessage;
|
private ChatMessageEntry? _reportTargetMessage;
|
||||||
private string _reportReason = string.Empty;
|
private string _reportReason = string.Empty;
|
||||||
@@ -78,10 +61,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
private bool _reportSubmitting;
|
private bool _reportSubmitting;
|
||||||
private string? _reportError;
|
private string? _reportError;
|
||||||
private ChatReportResult? _reportSubmissionResult;
|
private ChatReportResult? _reportSubmissionResult;
|
||||||
private string? _dragChannelKey;
|
|
||||||
private string? _dragHoverKey;
|
|
||||||
private bool _HideStateActive;
|
|
||||||
private bool _HideStateWasOpen;
|
|
||||||
|
|
||||||
public ZoneChatUi(
|
public ZoneChatUi(
|
||||||
ILogger<ZoneChatUi> logger,
|
ILogger<ZoneChatUi> logger,
|
||||||
@@ -89,12 +68,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
UiSharedService uiSharedService,
|
UiSharedService uiSharedService,
|
||||||
ZoneChatService zoneChatService,
|
ZoneChatService zoneChatService,
|
||||||
PairUiService pairUiService,
|
PairUiService pairUiService,
|
||||||
LightFinderService lightFinderService,
|
|
||||||
LightlessProfileManager profileManager,
|
LightlessProfileManager profileManager,
|
||||||
ChatConfigService chatConfigService,
|
ChatConfigService chatConfigService,
|
||||||
ServerConfigurationManager serverConfigurationManager,
|
|
||||||
DalamudUtilService dalamudUtilService,
|
|
||||||
IUiBuilder uiBuilder,
|
|
||||||
ApiController apiController,
|
ApiController apiController,
|
||||||
PerformanceCollectorService performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "Lightless Chat", performanceCollectorService)
|
: base(logger, mediator, "Lightless Chat", performanceCollectorService)
|
||||||
@@ -102,12 +77,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
_zoneChatService = zoneChatService;
|
_zoneChatService = zoneChatService;
|
||||||
_pairUiService = pairUiService;
|
_pairUiService = pairUiService;
|
||||||
_lightFinderService = lightFinderService;
|
|
||||||
_profileManager = profileManager;
|
_profileManager = profileManager;
|
||||||
_chatConfigService = chatConfigService;
|
_chatConfigService = chatConfigService;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
|
||||||
_dalamudUtilService = dalamudUtilService;
|
|
||||||
_uiBuilder = uiBuilder;
|
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_isWindowPinned = _chatConfigService.Current.IsWindowPinned;
|
_isWindowPinned = _chatConfigService.Current.IsWindowPinned;
|
||||||
_showRulesOverlay = _chatConfigService.Current.ShowRulesOverlayOnOpen;
|
_showRulesOverlay = _chatConfigService.Current.ShowRulesOverlayOnOpen;
|
||||||
@@ -117,7 +88,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
_unpinnedWindowFlags = Flags;
|
_unpinnedWindowFlags = Flags;
|
||||||
RefreshWindowFlags();
|
RefreshWindowFlags();
|
||||||
ApplyUiVisibilitySettings();
|
|
||||||
Size = new Vector2(450, 420) * ImGuiHelpers.GlobalScale;
|
Size = new Vector2(450, 420) * ImGuiHelpers.GlobalScale;
|
||||||
SizeCondition = ImGuiCond.FirstUseEver;
|
SizeCondition = ImGuiCond.FirstUseEver;
|
||||||
WindowBuilder.For(this)
|
WindowBuilder.For(this)
|
||||||
@@ -128,116 +98,20 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
Mediator.Subscribe<ChatChannelMessageAdded>(this, OnChatChannelMessageAdded);
|
Mediator.Subscribe<ChatChannelMessageAdded>(this, OnChatChannelMessageAdded);
|
||||||
Mediator.Subscribe<ChatChannelsUpdated>(this, _ => _scrollToBottom = true);
|
Mediator.Subscribe<ChatChannelsUpdated>(this, _ => _scrollToBottom = true);
|
||||||
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ => UpdateHideState());
|
|
||||||
Mediator.Subscribe<CutsceneFrameworkUpdateMessage>(this, _ => UpdateHideState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PreDraw()
|
public override void PreDraw()
|
||||||
{
|
{
|
||||||
RefreshWindowFlags();
|
RefreshWindowFlags();
|
||||||
base.PreDraw();
|
base.PreDraw();
|
||||||
var config = _chatConfigService.Current;
|
_currentWindowOpacity = Math.Clamp(_chatConfigService.Current.ChatWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
||||||
var baseOpacity = Math.Clamp(config.ChatWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
|
||||||
_baseWindowOpacity = baseOpacity;
|
|
||||||
|
|
||||||
if (config.FadeWhenUnfocused)
|
|
||||||
{
|
|
||||||
var unfocusedOpacity = Math.Clamp(config.UnfocusedWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
|
||||||
var targetOpacity = _isWindowFocused ? baseOpacity : Math.Min(baseOpacity, unfocusedOpacity);
|
|
||||||
var delta = ImGui.GetIO().DeltaTime;
|
|
||||||
var speed = _isWindowFocused ? FocusFadeInSpeed : UnfocusedFadeOutSpeed;
|
|
||||||
_currentWindowOpacity = MoveTowards(_currentWindowOpacity, targetOpacity, speed * delta);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentWindowOpacity = baseOpacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetNextWindowBgAlpha(_currentWindowOpacity);
|
ImGui.SetNextWindowBgAlpha(_currentWindowOpacity);
|
||||||
PushTitleBarFadeColors(_currentWindowOpacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateHideState()
|
|
||||||
{
|
|
||||||
ApplyUiVisibilitySettings();
|
|
||||||
var shouldHide = ShouldHide();
|
|
||||||
if (shouldHide)
|
|
||||||
{
|
|
||||||
_HideStateWasOpen |= IsOpen;
|
|
||||||
if (IsOpen)
|
|
||||||
{
|
|
||||||
IsOpen = false;
|
|
||||||
}
|
|
||||||
_HideStateActive = true;
|
|
||||||
}
|
|
||||||
else if (_HideStateActive)
|
|
||||||
{
|
|
||||||
if (_HideStateWasOpen)
|
|
||||||
{
|
|
||||||
IsOpen = true;
|
|
||||||
}
|
|
||||||
_HideStateActive = false;
|
|
||||||
_HideStateWasOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyUiVisibilitySettings()
|
|
||||||
{
|
|
||||||
var config = _chatConfigService.Current;
|
|
||||||
_uiBuilder.DisableAutomaticUiHide = config.ShowWhenUiHidden;
|
|
||||||
_uiBuilder.DisableCutsceneUiHide = config.ShowInCutscenes;
|
|
||||||
_uiBuilder.DisableGposeUiHide = config.ShowInGpose;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldHide()
|
|
||||||
{
|
|
||||||
var config = _chatConfigService.Current;
|
|
||||||
|
|
||||||
if (config.HideInCombat && _dalamudUtilService.IsInCombat)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.HideInDuty && _dalamudUtilService.IsInDuty && !_dalamudUtilService.IsInFieldOperation)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
if (_titleBarStylePopCount > 0)
|
|
||||||
{
|
|
||||||
ImGui.PopStyleColor(_titleBarStylePopCount);
|
|
||||||
_titleBarStylePopCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = _chatConfigService.Current;
|
|
||||||
var isFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
|
|
||||||
var isHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.RootAndChildWindows);
|
|
||||||
if (config.FadeWhenUnfocused && isHovered && !isFocused)
|
|
||||||
{
|
|
||||||
ImGui.SetWindowFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
_isWindowFocused = config.FadeWhenUnfocused ? (isFocused || isHovered) : isFocused;
|
|
||||||
|
|
||||||
var contentAlpha = 1f;
|
|
||||||
if (config.FadeWhenUnfocused)
|
|
||||||
{
|
|
||||||
var baseOpacity = MathF.Max(_baseWindowOpacity, 0.001f);
|
|
||||||
contentAlpha = Math.Clamp(_currentWindowOpacity / baseOpacity, 0f, 1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
using var alpha = ImRaii.PushStyle(ImGuiStyleVar.Alpha, contentAlpha);
|
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
|
||||||
var windowPos = ImGui.GetWindowPos();
|
|
||||||
var windowSize = ImGui.GetWindowSize();
|
|
||||||
using var selune = Selune.Begin(_seluneBrush, drawList, windowPos, windowSize);
|
|
||||||
var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg];
|
var childBgColor = ImGui.GetStyle().Colors[(int)ImGuiCol.ChildBg];
|
||||||
childBgColor.W *= _baseWindowOpacity;
|
childBgColor.W *= _currentWindowOpacity;
|
||||||
using var childBg = ImRaii.PushColor(ImGuiCol.ChildBg, childBgColor);
|
using var childBg = ImRaii.PushColor(ImGuiCol.ChildBg, childBgColor);
|
||||||
DrawConnectionControls();
|
DrawConnectionControls();
|
||||||
|
|
||||||
@@ -249,61 +123,39 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
|
||||||
ImGui.TextWrapped("No chat channels available.");
|
ImGui.TextWrapped("No chat channels available.");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
EnsureSelectedChannel(channels);
|
||||||
|
CleanupDrafts(channels);
|
||||||
|
|
||||||
|
DrawChannelButtons(channels);
|
||||||
|
|
||||||
|
if (_selectedChannelKey is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var activeChannel = channels.FirstOrDefault(channel => string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal));
|
||||||
|
if (activeChannel.Equals(default(ChatChannelSnapshot)))
|
||||||
{
|
{
|
||||||
EnsureSelectedChannel(channels);
|
activeChannel = channels[0];
|
||||||
CleanupDrafts(channels);
|
_selectedChannelKey = activeChannel.Key;
|
||||||
|
|
||||||
DrawChannelButtons(channels);
|
|
||||||
|
|
||||||
if (_selectedChannelKey is null)
|
|
||||||
{
|
|
||||||
selune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var activeChannel = channels.FirstOrDefault(channel => string.Equals(channel.Key, _selectedChannelKey, StringComparison.Ordinal));
|
|
||||||
if (activeChannel.Equals(default(ChatChannelSnapshot)))
|
|
||||||
{
|
|
||||||
activeChannel = channels[0];
|
|
||||||
_selectedChannelKey = activeChannel.Key;
|
|
||||||
}
|
|
||||||
|
|
||||||
_zoneChatService.SetActiveChannel(activeChannel.Key);
|
|
||||||
|
|
||||||
DrawHeader(activeChannel);
|
|
||||||
ImGui.Separator();
|
|
||||||
DrawMessageArea(activeChannel, _currentWindowOpacity);
|
|
||||||
ImGui.Separator();
|
|
||||||
DrawInput(activeChannel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_zoneChatService.SetActiveChannel(activeChannel.Key);
|
||||||
|
|
||||||
|
DrawHeader(activeChannel);
|
||||||
|
ImGui.Separator();
|
||||||
|
DrawMessageArea(activeChannel, _currentWindowOpacity);
|
||||||
|
ImGui.Separator();
|
||||||
|
DrawInput(activeChannel);
|
||||||
|
|
||||||
if (_showRulesOverlay)
|
if (_showRulesOverlay)
|
||||||
{
|
{
|
||||||
DrawRulesOverlay();
|
DrawRulesOverlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
selune.DrawHighlightOnly(ImGui.GetIO().DeltaTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PushTitleBarFadeColors(float opacity)
|
private static void DrawHeader(ChatChannelSnapshot channel)
|
||||||
{
|
|
||||||
_titleBarStylePopCount = 0;
|
|
||||||
var alpha = Math.Clamp(opacity, 0f, 1f);
|
|
||||||
var colors = ImGui.GetStyle().Colors;
|
|
||||||
|
|
||||||
var titleBg = colors[(int)ImGuiCol.TitleBg];
|
|
||||||
var titleBgActive = colors[(int)ImGuiCol.TitleBgActive];
|
|
||||||
var titleBgCollapsed = colors[(int)ImGuiCol.TitleBgCollapsed];
|
|
||||||
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.TitleBg, new Vector4(titleBg.X, titleBg.Y, titleBg.Z, titleBg.W * alpha));
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.TitleBgActive, new Vector4(titleBgActive.X, titleBgActive.Y, titleBgActive.Z, titleBgActive.W * alpha));
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, new Vector4(titleBgCollapsed.X, titleBgCollapsed.Y, titleBgCollapsed.Z, titleBgCollapsed.W * alpha));
|
|
||||||
_titleBarStylePopCount = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawHeader(ChatChannelSnapshot channel)
|
|
||||||
{
|
{
|
||||||
var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
|
var prefix = channel.Type == ChatChannelType.Zone ? "Zone" : "Syncshell";
|
||||||
Vector4 color;
|
Vector4 color;
|
||||||
@@ -326,18 +178,11 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
if (channel.Type == ChatChannelType.Zone && channel.Descriptor.WorldId != 0)
|
if (channel.Type == ChatChannelType.Zone && channel.Descriptor.WorldId != 0)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var worldId = channel.Descriptor.WorldId;
|
ImGui.TextUnformatted($"World #{channel.Descriptor.WorldId}");
|
||||||
var worldName = _dalamudUtilService.WorldData.Value.TryGetValue(worldId, out var name) ? name : $"World #{worldId}";
|
|
||||||
ImGui.TextUnformatted(worldName);
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip($"World ID: {worldId}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var showInlineStatus = string.Equals(channel.StatusText, ChatDisabledStatus, StringComparison.OrdinalIgnoreCase)
|
var showInlineDisabled = string.Equals(channel.StatusText, ChatDisabledStatus, StringComparison.OrdinalIgnoreCase);
|
||||||
|| string.Equals(channel.StatusText, ZoneUnavailableStatus, StringComparison.OrdinalIgnoreCase);
|
if (showInlineDisabled)
|
||||||
if (showInlineStatus)
|
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey3);
|
||||||
@@ -479,15 +324,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
_refocusChatInputKey = null;
|
_refocusChatInputKey = null;
|
||||||
}
|
}
|
||||||
ImGui.InputText(inputId, ref draft, MaxMessageLength);
|
ImGui.InputText(inputId, ref draft, MaxMessageLength);
|
||||||
if (ImGui.IsItemActive() || ImGui.IsItemFocused())
|
|
||||||
{
|
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
|
||||||
var itemMin = ImGui.GetItemRectMin();
|
|
||||||
var itemMax = ImGui.GetItemRectMax();
|
|
||||||
var highlight = UIColors.Get("LightlessPurple").WithAlpha(0.35f);
|
|
||||||
var highlightU32 = ImGui.ColorConvertFloat4ToU32(highlight);
|
|
||||||
drawList.AddRect(itemMin, itemMax, highlightU32, style.FrameRounding, ImDrawFlags.None, Math.Max(1f, ImGuiHelpers.GlobalScale));
|
|
||||||
}
|
|
||||||
var enterPressed = ImGui.IsItemFocused()
|
var enterPressed = ImGui.IsItemFocused()
|
||||||
&& (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter));
|
&& (ImGui.IsKeyPressed(ImGuiKey.Enter) || ImGui.IsKeyPressed(ImGuiKey.KeypadEnter));
|
||||||
_draftMessages[channel.Key] = draft;
|
_draftMessages[channel.Key] = draft;
|
||||||
@@ -644,7 +480,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uiSharedService.MediumText("Syncshell Chat Rules", UIColors.Get("LightlessYellow"));
|
_uiSharedService.MediumText("Syncshell Chat Rules", UIColors.Get("LightlessYellow"));
|
||||||
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("Syncshell chats are self-moderated (their own set rules) by it's owner and appointed moderators."));
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("Syncshell chats are self-moderated (their own set rules) by it's owner and appointed moderators. If they fail to enforce chat rules within their syncshell, the owner (and its moderators) may face punishment."));
|
||||||
|
|
||||||
ImGui.Dummy(new Vector2(5));
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|
||||||
@@ -1198,56 +1034,18 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
var groupSize = ImGui.GetItemRectSize();
|
var groupSize = ImGui.GetItemRectSize();
|
||||||
var minBlockX = cursorStart.X + groupSize.X + style.ItemSpacing.X;
|
var minBlockX = cursorStart.X + groupSize.X + style.ItemSpacing.X;
|
||||||
var availableAfterGroup = contentRightX - (cursorStart.X + groupSize.X);
|
var availableAfterGroup = contentRightX - (cursorStart.X + groupSize.X);
|
||||||
var lightfinderButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.PersonCirclePlus).X;
|
|
||||||
var settingsButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Cog).X;
|
var settingsButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Cog).X;
|
||||||
var pinIcon = _isWindowPinned ? FontAwesomeIcon.Lock : FontAwesomeIcon.Unlock;
|
var pinIcon = _isWindowPinned ? FontAwesomeIcon.Lock : FontAwesomeIcon.Unlock;
|
||||||
var pinButtonWidth = _uiSharedService.GetIconButtonSize(pinIcon).X;
|
var pinButtonWidth = _uiSharedService.GetIconButtonSize(pinIcon).X;
|
||||||
var blockWidth = lightfinderButtonWidth + style.ItemSpacing.X + rulesButtonWidth + style.ItemSpacing.X + settingsButtonWidth + style.ItemSpacing.X + pinButtonWidth;
|
var blockWidth = rulesButtonWidth + style.ItemSpacing.X + settingsButtonWidth + style.ItemSpacing.X + pinButtonWidth;
|
||||||
var desiredBlockX = availableAfterGroup > blockWidth + style.ItemSpacing.X
|
var desiredBlockX = availableAfterGroup > blockWidth + style.ItemSpacing.X
|
||||||
? contentRightX - blockWidth
|
? contentRightX - blockWidth
|
||||||
: minBlockX;
|
: minBlockX;
|
||||||
desiredBlockX = Math.Max(cursorStart.X, desiredBlockX);
|
desiredBlockX = Math.Max(cursorStart.X, desiredBlockX);
|
||||||
var lightfinderPos = new Vector2(desiredBlockX, cursorStart.Y);
|
var rulesPos = new Vector2(desiredBlockX, cursorStart.Y);
|
||||||
var rulesPos = new Vector2(lightfinderPos.X + lightfinderButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
var settingsPos = new Vector2(desiredBlockX + rulesButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||||
var settingsPos = new Vector2(rulesPos.X + rulesButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
|
||||||
var pinPos = new Vector2(settingsPos.X + settingsButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
var pinPos = new Vector2(settingsPos.X + settingsButtonWidth + style.ItemSpacing.X, cursorStart.Y);
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetCursorPos(lightfinderPos);
|
|
||||||
var lightfinderEnabled = _lightFinderService.IsBroadcasting;
|
|
||||||
var lightfinderColor = lightfinderEnabled ? UIColors.Get("LightlessGreen") : ImGuiColors.DalamudGrey3;
|
|
||||||
var lightfinderButtonSize = new Vector2(lightfinderButtonWidth, ImGui.GetFrameHeight());
|
|
||||||
ImGui.InvisibleButton("zone_chat_lightfinder_button", lightfinderButtonSize);
|
|
||||||
var lightfinderMin = ImGui.GetItemRectMin();
|
|
||||||
var lightfinderMax = ImGui.GetItemRectMax();
|
|
||||||
var iconSize = _uiSharedService.GetIconSize(FontAwesomeIcon.PersonCirclePlus);
|
|
||||||
var iconPos = new Vector2(
|
|
||||||
lightfinderMin.X + (lightfinderButtonSize.X - iconSize.X) * 0.5f,
|
|
||||||
lightfinderMin.Y + (lightfinderButtonSize.Y - iconSize.Y) * 0.5f);
|
|
||||||
using (_uiSharedService.IconFont.Push())
|
|
||||||
{
|
|
||||||
ImGui.GetWindowDrawList().AddText(iconPos, ImGui.GetColorU32(lightfinderColor), FontAwesomeIcon.PersonCirclePlus.ToIconString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.IsItemClicked())
|
|
||||||
{
|
|
||||||
Mediator.Publish(new UiToggleMessage(typeof(LightFinderUI)));
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
var padding = new Vector2(8f * ImGuiHelpers.GlobalScale);
|
|
||||||
Selune.RegisterHighlight(
|
|
||||||
lightfinderMin - padding,
|
|
||||||
lightfinderMax + padding,
|
|
||||||
SeluneHighlightMode.Point,
|
|
||||||
exactSize: true,
|
|
||||||
clipToElement: true,
|
|
||||||
clipPadding: padding,
|
|
||||||
highlightColorOverride: lightfinderColor,
|
|
||||||
highlightAlphaOverride: 0.2f);
|
|
||||||
ImGui.SetTooltip("If Lightfinder is enabled, you will be able to see the character names of other Lightfinder users in the same zone when they send a message.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.SetCursorPos(rulesPos);
|
ImGui.SetCursorPos(rulesPos);
|
||||||
if (ImGui.Button("Rules", new Vector2(rulesButtonWidth, 0f)))
|
if (ImGui.Button("Rules", new Vector2(rulesButtonWidth, 0f)))
|
||||||
@@ -1389,71 +1187,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SetTooltip("Toggles the timestamp prefix on messages.");
|
ImGui.SetTooltip("Toggles the timestamp prefix on messages.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.TextUnformatted("Chat Visibility");
|
|
||||||
|
|
||||||
var autoHideCombat = chatConfig.HideInCombat;
|
|
||||||
if (ImGui.Checkbox("Hide in combat", ref autoHideCombat))
|
|
||||||
{
|
|
||||||
chatConfig.HideInCombat = autoHideCombat;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
UpdateHideState();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Temporarily hides the chat window while in combat.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var autoHideDuty = chatConfig.HideInDuty;
|
|
||||||
if (ImGui.Checkbox("Hide in duty (Not in field operations)", ref autoHideDuty))
|
|
||||||
{
|
|
||||||
chatConfig.HideInDuty = autoHideDuty;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
UpdateHideState();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Hides the chat window inside duties.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var showWhenUiHidden = chatConfig.ShowWhenUiHidden;
|
|
||||||
if (ImGui.Checkbox("Show when game UI is hidden", ref showWhenUiHidden))
|
|
||||||
{
|
|
||||||
chatConfig.ShowWhenUiHidden = showWhenUiHidden;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
UpdateHideState();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Allow the chat window to remain visible when the game UI is hidden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var showInCutscenes = chatConfig.ShowInCutscenes;
|
|
||||||
if (ImGui.Checkbox("Show in cutscenes", ref showInCutscenes))
|
|
||||||
{
|
|
||||||
chatConfig.ShowInCutscenes = showInCutscenes;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
UpdateHideState();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Allow the chat window to remain visible during cutscenes.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var showInGpose = chatConfig.ShowInGpose;
|
|
||||||
if (ImGui.Checkbox("Show in group pose", ref showInGpose))
|
|
||||||
{
|
|
||||||
chatConfig.ShowInGpose = showInGpose;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
UpdateHideState();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Allow the chat window to remain visible in /gpose.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
var fontScale = Math.Clamp(chatConfig.ChatFontScale, MinChatFontScale, MaxChatFontScale);
|
var fontScale = Math.Clamp(chatConfig.ChatFontScale, MinChatFontScale, MaxChatFontScale);
|
||||||
var fontScaleChanged = ImGui.SliderFloat("Message font scale", ref fontScale, MinChatFontScale, MaxChatFontScale, "%.2fx");
|
var fontScaleChanged = ImGui.SliderFloat("Message font scale", ref fontScale, MinChatFontScale, MaxChatFontScale, "%.2fx");
|
||||||
var resetFontScale = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
var resetFontScale = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
||||||
@@ -1493,55 +1226,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SetTooltip("Adjust chat window transparency.\nRight-click to reset to default.");
|
ImGui.SetTooltip("Adjust chat window transparency.\nRight-click to reset to default.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var fadeUnfocused = chatConfig.FadeWhenUnfocused;
|
|
||||||
if (ImGui.Checkbox("Fade window when unfocused", ref fadeUnfocused))
|
|
||||||
{
|
|
||||||
chatConfig.FadeWhenUnfocused = fadeUnfocused;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("When enabled, the chat window fades after it loses focus.\nHovering the window restores focus.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.BeginDisabled(!fadeUnfocused);
|
|
||||||
var unfocusedOpacity = Math.Clamp(chatConfig.UnfocusedWindowOpacity, MinWindowOpacity, MaxWindowOpacity);
|
|
||||||
var unfocusedChanged = ImGui.SliderFloat("Unfocused transparency", ref unfocusedOpacity, MinWindowOpacity, MaxWindowOpacity, "%.2f");
|
|
||||||
var resetUnfocused = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
|
||||||
if (resetUnfocused)
|
|
||||||
{
|
|
||||||
unfocusedOpacity = DefaultUnfocusedWindowOpacity;
|
|
||||||
unfocusedChanged = true;
|
|
||||||
}
|
|
||||||
if (unfocusedChanged)
|
|
||||||
{
|
|
||||||
chatConfig.UnfocusedWindowOpacity = unfocusedOpacity;
|
|
||||||
_chatConfigService.Save();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
ImGui.SetTooltip("Target transparency while the chat window is unfocused.\nRight-click to reset to default.");
|
|
||||||
}
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float MoveTowards(float current, float target, float maxDelta)
|
|
||||||
{
|
|
||||||
if (current < target)
|
|
||||||
{
|
|
||||||
return MathF.Min(current + maxDelta, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current > target)
|
|
||||||
{
|
|
||||||
return MathF.Max(current - maxDelta, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ToggleChatConnection(bool currentlyEnabled)
|
private void ToggleChatConnection(bool currentlyEnabled)
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
@@ -1557,7 +1244,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void DrawChannelButtons(IReadOnlyList<ChatChannelSnapshot> channels)
|
private void DrawChannelButtons(IReadOnlyList<ChatChannelSnapshot> channels)
|
||||||
{
|
{
|
||||||
var style = ImGui.GetStyle();
|
var style = ImGui.GetStyle();
|
||||||
var baseFramePadding = style.FramePadding;
|
var baseFramePadding = style.FramePadding;
|
||||||
@@ -1618,8 +1305,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (child)
|
if (child)
|
||||||
{
|
{
|
||||||
var dragActive = _dragChannelKey is not null && ImGui.IsMouseDragging(ImGuiMouseButton.Left);
|
|
||||||
var hoveredTargetThisFrame = false;
|
|
||||||
var first = true;
|
var first = true;
|
||||||
foreach (var channel in channels)
|
foreach (var channel in channels)
|
||||||
{
|
{
|
||||||
@@ -1630,7 +1315,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
var showBadge = !isSelected && channel.UnreadCount > 0;
|
var showBadge = !isSelected && channel.UnreadCount > 0;
|
||||||
var isZoneChannel = channel.Type == ChatChannelType.Zone;
|
var isZoneChannel = channel.Type == ChatChannelType.Zone;
|
||||||
(string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
|
(string Text, Vector2 TextSize, float Width, float Height)? badgeMetrics = null;
|
||||||
var channelLabel = GetChannelTabLabel(channel);
|
|
||||||
|
|
||||||
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
|
var normal = isSelected ? UIColors.Get("LightlessPurpleDefault") : UIColors.Get("ButtonDefault");
|
||||||
var hovered = isSelected
|
var hovered = isSelected
|
||||||
@@ -1659,7 +1343,7 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight);
|
badgeMetrics = (badgeText, badgeTextSize, badgeWidth, badgeHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clicked = ImGui.Button($"{channelLabel}##chat_channel_{channel.Key}");
|
var clicked = ImGui.Button($"{channel.DisplayName}##chat_channel_{channel.Key}");
|
||||||
|
|
||||||
if (showBadge)
|
if (showBadge)
|
||||||
{
|
{
|
||||||
@@ -1675,77 +1359,10 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
_scrollToBottom = true;
|
_scrollToBottom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldShowChannelTabContextMenu(channel)
|
|
||||||
&& ImGui.BeginPopupContextItem($"chat_channel_ctx##{channel.Key}"))
|
|
||||||
{
|
|
||||||
DrawChannelTabContextMenu(channel);
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginDragDropSource(ImGuiDragDropFlags.None))
|
|
||||||
{
|
|
||||||
if (!string.Equals(_dragChannelKey, channel.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_dragHoverKey = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_dragChannelKey = channel.Key;
|
|
||||||
ImGui.SetDragDropPayload(ChannelDragPayloadId, null, 0);
|
|
||||||
ImGui.TextUnformatted(channelLabel);
|
|
||||||
ImGui.EndDragDropSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
var isDragTarget = false;
|
|
||||||
|
|
||||||
if (ImGui.BeginDragDropTarget())
|
|
||||||
{
|
|
||||||
var acceptFlags = ImGuiDragDropFlags.AcceptBeforeDelivery | ImGuiDragDropFlags.AcceptNoDrawDefaultRect;
|
|
||||||
var payload = ImGui.AcceptDragDropPayload(ChannelDragPayloadId, acceptFlags);
|
|
||||||
if (!payload.IsNull && _dragChannelKey is { } draggedKey
|
|
||||||
&& !string.Equals(draggedKey, channel.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
isDragTarget = true;
|
|
||||||
if (!string.Equals(_dragHoverKey, channel.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_dragHoverKey = channel.Key;
|
|
||||||
_zoneChatService.MoveChannel(draggedKey, channel.Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndDragDropTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
var isHoveredDuringDrag = dragActive
|
|
||||||
&& ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenBlockedByActiveItem | ImGuiHoveredFlags.AllowWhenOverlapped);
|
|
||||||
|
|
||||||
if (!isDragTarget && isHoveredDuringDrag
|
|
||||||
&& !string.Equals(_dragChannelKey, channel.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
isDragTarget = true;
|
|
||||||
if (!string.Equals(_dragHoverKey, channel.Key, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_dragHoverKey = channel.Key;
|
|
||||||
_zoneChatService.MoveChannel(_dragChannelKey!, channel.Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
var itemMin = ImGui.GetItemRectMin();
|
var itemMin = ImGui.GetItemRectMin();
|
||||||
var itemMax = ImGui.GetItemRectMax();
|
var itemMax = ImGui.GetItemRectMax();
|
||||||
|
|
||||||
if (isHoveredDuringDrag)
|
|
||||||
{
|
|
||||||
var highlight = UIColors.Get("LightlessPurple").WithAlpha(0.35f);
|
|
||||||
var highlightU32 = ImGui.ColorConvertFloat4ToU32(highlight);
|
|
||||||
drawList.AddRectFilled(itemMin, itemMax, highlightU32, style.FrameRounding);
|
|
||||||
drawList.AddRect(itemMin, itemMax, highlightU32, style.FrameRounding, ImDrawFlags.None, Math.Max(1f, ImGuiHelpers.GlobalScale));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDragTarget)
|
|
||||||
{
|
|
||||||
hoveredTargetThisFrame = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isZoneChannel)
|
if (isZoneChannel)
|
||||||
{
|
{
|
||||||
var borderColor = UIColors.Get("LightlessOrange");
|
var borderColor = UIColors.Get("LightlessOrange");
|
||||||
@@ -1773,11 +1390,6 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dragActive && !hoveredTargetThisFrame)
|
|
||||||
{
|
|
||||||
_dragHoverKey = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pendingChannelScroll.HasValue)
|
if (_pendingChannelScroll.HasValue)
|
||||||
{
|
{
|
||||||
ImGui.SetScrollX(_pendingChannelScroll.Value);
|
ImGui.SetScrollX(_pendingChannelScroll.Value);
|
||||||
@@ -1818,123 +1430,9 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
_channelScroll = currentScroll;
|
_channelScroll = currentScroll;
|
||||||
_channelScrollMax = maxScroll;
|
_channelScrollMax = maxScroll;
|
||||||
|
|
||||||
if (_dragChannelKey is not null && !ImGui.IsMouseDown(ImGuiMouseButton.Left))
|
|
||||||
{
|
|
||||||
_dragChannelKey = null;
|
|
||||||
_dragHoverKey = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - style.ItemSpacing.Y * 0.3f);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - style.ItemSpacing.Y * 0.3f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetChannelTabLabel(ChatChannelSnapshot channel)
|
|
||||||
{
|
|
||||||
if (channel.Type != ChatChannelType.Group)
|
|
||||||
{
|
|
||||||
return channel.DisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var preferNote) || !preferNote)
|
|
||||||
{
|
|
||||||
return channel.DisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var note = GetChannelNote(channel);
|
|
||||||
if (string.IsNullOrWhiteSpace(note))
|
|
||||||
{
|
|
||||||
return channel.DisplayName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TruncateChannelNoteForTab(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string TruncateChannelNoteForTab(string note)
|
|
||||||
{
|
|
||||||
if (note.Length <= MaxChannelNoteTabLength)
|
|
||||||
{
|
|
||||||
return note;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ellipsis = "...";
|
|
||||||
var maxPrefix = Math.Max(0, MaxChannelNoteTabLength - ellipsis.Length);
|
|
||||||
return note[..maxPrefix] + ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ShouldShowChannelTabContextMenu(ChatChannelSnapshot channel)
|
|
||||||
{
|
|
||||||
if (channel.Type != ChatChannelType.Group)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var preferNote) && preferNote)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var note = GetChannelNote(channel);
|
|
||||||
return !string.IsNullOrWhiteSpace(note);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawChannelTabContextMenu(ChatChannelSnapshot channel)
|
|
||||||
{
|
|
||||||
var preferNote = _chatConfigService.Current.PreferNotesForChannels.TryGetValue(channel.Key, out var value) && value;
|
|
||||||
var note = GetChannelNote(channel);
|
|
||||||
var hasNote = !string.IsNullOrWhiteSpace(note);
|
|
||||||
if (preferNote || hasNote)
|
|
||||||
{
|
|
||||||
var label = preferNote ? "Prefer Name Instead" : "Prefer Note Instead";
|
|
||||||
if (ImGui.MenuItem(label))
|
|
||||||
{
|
|
||||||
SetPreferNoteForChannel(channel.Key, !preferNote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preferNote)
|
|
||||||
{
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.TextDisabled("Name:");
|
|
||||||
ImGui.TextWrapped(channel.DisplayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasNote)
|
|
||||||
{
|
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.TextDisabled("Note:");
|
|
||||||
ImGui.TextWrapped(note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetChannelNote(ChatChannelSnapshot channel)
|
|
||||||
{
|
|
||||||
if (channel.Type != ChatChannelType.Group)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gid = channel.Descriptor.CustomKey;
|
|
||||||
if (string.IsNullOrWhiteSpace(gid))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _serverConfigurationManager.GetNoteForGid(gid);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPreferNoteForChannel(string channelKey, bool preferNote)
|
|
||||||
{
|
|
||||||
if (preferNote)
|
|
||||||
{
|
|
||||||
_chatConfigService.Current.PreferNotesForChannels[channelKey] = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_chatConfigService.Current.PreferNotesForChannels.Remove(channelKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
_chatConfigService.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSystemEntry(ChatMessageEntry entry)
|
private void DrawSystemEntry(ChatMessageEntry entry)
|
||||||
{
|
{
|
||||||
var system = entry.SystemMessage;
|
var system = entry.SystemMessage;
|
||||||
|
|||||||
@@ -584,10 +584,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
||||||
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
|
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
|
||||||
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
|
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
|
||||||
if (!_initialized)
|
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
|
||||||
{
|
|
||||||
_lightlessHub.On(nameof(Client_ChatReceive), (Func<ChatMessageDto, Task>)Client_ChatReceive);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
|
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
|
||||||
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));
|
OnGposeLobbyLeave((dto) => _ = Client_GposeLobbyLeave(dto));
|
||||||
|
|||||||
Submodule Penumbra.Api updated: 52a3216a52...1750c41b53
Reference in New Issue
Block a user