Compare commits
74 Commits
1.12.0-Dev
...
1.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1364ac32bd | ||
|
|
e4931ded9f | ||
| a3b5a5cb2e | |||
|
|
0e274f04f1 | ||
|
|
dfb2e948c5 | ||
|
|
f037e7587d | ||
|
|
5bf443cf0e | ||
|
|
78455f7523 | ||
| 8dd13479fc | |||
|
|
87e6c0b3f6 | ||
| 25d39c0ad1 | |||
| 458aa5f933 | |||
|
|
3d2650cc5f | ||
|
|
931009607b | ||
|
|
36bf17aee2 | ||
|
|
012547056a | ||
|
|
9242f23787 | ||
|
|
e8a3d87ff0 | ||
|
|
4862921b03 | ||
|
|
f2b7b0c4e3 | ||
|
|
05872c285c | ||
|
|
da59105bab | ||
|
|
54ea1e9d4c | ||
|
|
f6e1832d62 | ||
| afc42d97d1 | |||
|
|
714aeef468 | ||
|
|
3a627f2d3e | ||
|
|
bf3770025b | ||
|
|
49d138049e | ||
|
|
e91d163763 | ||
| dea4ef4832 | |||
| 76520878bf | |||
|
|
068c8cb180 | ||
|
|
c34e379a6e | ||
| de007e2b04 | |||
| e52d4c6165 | |||
| 34250a18b4 | |||
| 390a0c2d61 | |||
|
|
571decd33b | ||
|
|
36c1611486 | ||
|
|
597a0beb91 | ||
|
|
1fa2f063e8 | ||
|
|
1fae47b171 | ||
| e85b900754 | |||
|
|
9696ac60f1 | ||
|
|
0b7b543dd7 | ||
|
|
8a9d4b8daa | ||
|
|
7d6d500e6a | ||
|
|
dc1fdd4a3e | ||
|
|
f74cd01a32 | ||
|
|
8f7f07311f | ||
|
|
7b415b4e47 | ||
| f90b9c3f8e | |||
|
|
0cc7181e98 | ||
|
|
914553d5ab | ||
|
|
08e3c8678f | ||
|
|
6af2013a03 | ||
|
|
3831dd24f1 | ||
|
|
c6f8d6843e | ||
| fd9bd3975b | |||
|
|
3e15dd643e | ||
|
|
f6784fdf28 | ||
|
|
c182d10a0a | ||
| 8e7cdce310 | |||
|
|
e3a3f16d14 | ||
|
|
31b56ba58a | ||
| 9bc2ad24cd | |||
| 864a2e6677 | |||
|
|
ab3ca78c7f | ||
|
|
6bb379ebad | ||
|
|
4060ba96f1 | ||
|
|
bd2c3a4a80 | ||
|
|
fe898cdc2b | ||
|
|
38a360bfee |
@@ -241,6 +241,7 @@ jobs:
|
|||||||
updatedRepoJson=$(jq \
|
updatedRepoJson=$(jq \
|
||||||
--arg internalName "$internalName" \
|
--arg internalName "$internalName" \
|
||||||
--arg dalamudApiLevel "$dalamudApiLevel" \
|
--arg dalamudApiLevel "$dalamudApiLevel" \
|
||||||
|
--arg assemblyVersion "$assemblyVersion" \
|
||||||
--arg version "$version" \
|
--arg version "$version" \
|
||||||
--arg downloadUrl "$downloadUrl" \
|
--arg downloadUrl "$downloadUrl" \
|
||||||
'
|
'
|
||||||
|
|||||||
140
.github/workflows/lightless-tag-and-release.yml
vendored
140
.github/workflows/lightless-tag-and-release.yml
vendored
@@ -1,140 +0,0 @@
|
|||||||
name: Tag and Release Lightless
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: LightlessSync
|
|
||||||
DOTNET_VERSION: 9.x
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
tag-and-release:
|
|
||||||
runs-on: windows-2022
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout Lightless
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup .NET 9 SDK
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: 9.x
|
|
||||||
|
|
||||||
- name: Download Dalamud
|
|
||||||
run: |
|
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
|
|
||||||
Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
|
||||||
|
|
||||||
- name: Lets Build Lightless!
|
|
||||||
run: |
|
|
||||||
dotnet restore
|
|
||||||
dotnet build --configuration Release --no-restore
|
|
||||||
dotnet publish --configuration Release --no-build
|
|
||||||
|
|
||||||
- name: Get version
|
|
||||||
id: package_version
|
|
||||||
uses: KageKirin/get-csproj-version@v0
|
|
||||||
with:
|
|
||||||
file: LightlessSync/LightlessSync.csproj
|
|
||||||
|
|
||||||
- name: Display version
|
|
||||||
run: |
|
|
||||||
echo "Version: ${{ steps.package_version.outputs.version }}"
|
|
||||||
|
|
||||||
- name: Prepare Lightless Client
|
|
||||||
run: |
|
|
||||||
$publishPath = "${{ env.PLUGIN_NAME }}/bin/x64/Release/publish"
|
|
||||||
if (Test-Path $publishPath) {
|
|
||||||
Remove-Item -Recurse -Force $publishPath
|
|
||||||
Write-Host "Removed $publishPath"
|
|
||||||
} else {
|
|
||||||
Write-Host "$publishPath does not exist, nothing to remove."
|
|
||||||
}
|
|
||||||
mkdir output
|
|
||||||
Compress-Archive -Path ${{ env.PLUGIN_NAME }}/bin/x64/Release/* -DestinationPath output/LightlessClient.zip
|
|
||||||
|
|
||||||
- name: Create Git tag if not exists
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$tag = "${{ steps.package_version.outputs.version }}"
|
|
||||||
git fetch --tags
|
|
||||||
if (-not (git tag -l $tag)) {
|
|
||||||
Write-Host "Tag $tag does not exist. Creating and pushing..."
|
|
||||||
git config user.name "GitHub Action"
|
|
||||||
git config user.email "action@github.com"
|
|
||||||
git tag $tag
|
|
||||||
git push origin $tag
|
|
||||||
} else {
|
|
||||||
Write-Host "Tag $tag already exists. Skipping tag creation."
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: ${{ steps.package_version.outputs.version }}
|
|
||||||
name: ${{ steps.package_version.outputs.version }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
files: output/LightlessClient.zip
|
|
||||||
|
|
||||||
- name: Clone plugin hosting repo
|
|
||||||
run: |
|
|
||||||
mkdir LightlessSyncRepo
|
|
||||||
cd LightlessSyncRepo
|
|
||||||
git clone https://github.com/${{ github.repository_owner }}/LightlessSync.git
|
|
||||||
env:
|
|
||||||
GIT_TERMINAL_PROMPT: 0
|
|
||||||
|
|
||||||
- name: Update plogonmaster.json with version
|
|
||||||
shell: pwsh
|
|
||||||
env:
|
|
||||||
VERSION: ${{ steps.package_version.outputs.version }}
|
|
||||||
run: |
|
|
||||||
$pluginJsonPath = "${{ env.PLUGIN_NAME }}/bin/x64/Release/${{ env.PLUGIN_NAME }}.json"
|
|
||||||
$pluginJson = Get-Content $pluginJsonPath | ConvertFrom-Json
|
|
||||||
$repoJsonPath = "LightlessSyncRepo/LightlessSync/plogonmaster.json"
|
|
||||||
$repoJsonRaw = Get-Content $repoJsonPath -Raw
|
|
||||||
$repoJson = $repoJsonRaw | ConvertFrom-Json
|
|
||||||
$version = $env:VERSION
|
|
||||||
$downloadUrl = "https://github.com/${{ github.repository_owner }}/LightlessClient/releases/download/$version/LightlessClient.zip"
|
|
||||||
|
|
||||||
if (-not ($repoJson -is [System.Collections.IEnumerable])) {
|
|
||||||
$repoJson = @($repoJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($plugin in $repoJson) {
|
|
||||||
if ($plugin.InternalName -eq $pluginJson.InternalName) {
|
|
||||||
$plugin.DalamudApiLevel = $pluginJson.DalamudApiLevel
|
|
||||||
$plugin.AssemblyVersion = $version
|
|
||||||
$plugin.DownloadLinkInstall = $downloadUrl
|
|
||||||
$plugin.DownloadLinkTesting = $downloadUrl
|
|
||||||
$plugin.DownloadLinkUpdate = $downloadUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$repoJson | ConvertTo-Json -Depth 100 | Set-Content $repoJsonPath
|
|
||||||
|
|
||||||
# Convert to JSON and force array brackets if necessary
|
|
||||||
$repoJsonString = $repoJson | ConvertTo-Json -Depth 100
|
|
||||||
|
|
||||||
# If the output is not an array, wrap it manually
|
|
||||||
if ($repoJsonString.Trim().StartsWith('{')) {
|
|
||||||
$repoJsonString = "[$repoJsonString]"
|
|
||||||
}
|
|
||||||
|
|
||||||
$repoJsonString | Set-Content $repoJsonPath
|
|
||||||
|
|
||||||
- name: Commit and push to LightlessSync
|
|
||||||
run: |
|
|
||||||
cd LightlessSyncRepo/LightlessSync
|
|
||||||
git config user.name "github-actions"
|
|
||||||
git config user.email "github-actions@github.com"
|
|
||||||
git add .
|
|
||||||
git commit -m "Update ${{ env.PLUGIN_NAME }} to ${{ steps.package_version.outputs.version }}"
|
|
||||||
git push https://x-access-token:${{ secrets.LIGHTLESS_TOKEN }}@github.com/${{ github.repository_owner }}/LightlessSync.git HEAD:main
|
|
||||||
Submodule LightlessAPI updated: aec2a5023e...167508d27b
@@ -20,7 +20,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
|
|||||||
private long _currentFileProgress = 0;
|
private long _currentFileProgress = 0;
|
||||||
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"];
|
public static readonly IImmutableList<string> AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk", ".kdb"];
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -188,8 +188,15 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
Parallel.ForEach(allEntities, entity =>
|
Parallel.ForEach(allEntities, entity =>
|
||||||
|
{
|
||||||
|
if (entity != null && entity.PrefixedFilePath != null)
|
||||||
{
|
{
|
||||||
cacheDict[entity.PrefixedFilePath] = entity;
|
cacheDict[entity.PrefixedFilePath] = entity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Null FileCacheEntity or PrefixedFilePath encountered in cache population: {entity}", entity);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var cleanedPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var cleanedPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -253,6 +260,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
{
|
{
|
||||||
if (_fileCaches.TryGetValue(hash, out var caches))
|
if (_fileCaches.TryGetValue(hash, out var caches))
|
||||||
{
|
{
|
||||||
|
_logger.LogTrace("Removing from DB: {hash} => {path}", hash, prefixedFilePath);
|
||||||
var removedCount = caches?.RemoveAll(c => string.Equals(c.PrefixedFilePath, prefixedFilePath, StringComparison.Ordinal));
|
var removedCount = caches?.RemoveAll(c => string.Equals(c.PrefixedFilePath, prefixedFilePath, StringComparison.Ordinal));
|
||||||
_logger.LogTrace("Removed from DB: {count} file(s) with hash {hash} and file cache {path}", removedCount, hash, prefixedFilePath);
|
_logger.LogTrace("Removed from DB: {count} file(s) with hash {hash} and file cache {path}", removedCount, hash, prefixedFilePath);
|
||||||
|
|
||||||
@@ -397,6 +405,12 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
private FileCacheEntity? Validate(FileCacheEntity fileCache)
|
private FileCacheEntity? Validate(FileCacheEntity fileCache)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(fileCache.ResolvedFilepath))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("FileCacheEntity has empty ResolvedFilepath for hash {hash}, prefixed path {prefixed}", fileCache.Hash, fileCache.PrefixedFilePath);
|
||||||
|
RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
var file = new FileInfo(fileCache.ResolvedFilepath);
|
var file = new FileInfo(fileCache.ResolvedFilepath);
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
|
private readonly HashSet<string> _cachedHandledPaths = new(StringComparer.Ordinal);
|
||||||
private readonly TransientConfigService _configurationService;
|
private readonly TransientConfigService _configurationService;
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly string[] _handledFileTypes = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
|
private readonly string[] _handledFileTypes = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk", "kdb"];
|
||||||
private readonly string[] _handledRecordingFileTypes = ["tex", "mdl", "mtrl"];
|
private readonly string[] _handledRecordingFileTypes = ["tex", "mdl", "mtrl"];
|
||||||
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
|
private readonly HashSet<GameObjectHandler> _playerRelatedPointers = [];
|
||||||
private ConcurrentDictionary<IntPtr, ObjectKind> _cachedFrameAddresses = [];
|
private ConcurrentDictionary<IntPtr, ObjectKind> _cachedFrameAddresses = [];
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public bool UseFocusTarget { get; set; } = false;
|
public bool UseFocusTarget { get; set; } = false;
|
||||||
public bool overrideFriendColor { get; set; } = false;
|
public bool overrideFriendColor { get; set; } = false;
|
||||||
public bool overridePartyColor { get; set; } = false;
|
public bool overridePartyColor { get; set; } = false;
|
||||||
|
public bool overrideFcTagColor { get; set; } = false;
|
||||||
|
public bool useColoredUIDs { get; set; } = true;
|
||||||
public bool BroadcastEnabled { get; set; } = false;
|
public bool BroadcastEnabled { get; set; } = false;
|
||||||
public DateTime BroadcastTtl { get; set; } = DateTime.MinValue;
|
public DateTime BroadcastTtl { get; set; } = DateTime.MinValue;
|
||||||
public bool SyncshellFinderEnabled { get; set; } = false;
|
public bool SyncshellFinderEnabled { get; set; } = false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
@@ -113,6 +113,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<PluginWarningNotificationService>();
|
collection.AddSingleton<PluginWarningNotificationService>();
|
||||||
collection.AddSingleton<FileCompactor>();
|
collection.AddSingleton<FileCompactor>();
|
||||||
collection.AddSingleton<TagHandler>();
|
collection.AddSingleton<TagHandler>();
|
||||||
|
collection.AddSingleton<PairRequestService>();
|
||||||
collection.AddSingleton<IdDisplayHandler>();
|
collection.AddSingleton<IdDisplayHandler>();
|
||||||
collection.AddSingleton<PlayerPerformanceService>();
|
collection.AddSingleton<PlayerPerformanceService>();
|
||||||
collection.AddSingleton<TransientResourceManager>();
|
collection.AddSingleton<TransientResourceManager>();
|
||||||
@@ -147,7 +148,9 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<RedrawManager>();
|
collection.AddSingleton<RedrawManager>();
|
||||||
collection.AddSingleton<BroadcastService>();
|
collection.AddSingleton<BroadcastService>();
|
||||||
collection.AddSingleton(addonLifecycle);
|
collection.AddSingleton(addonLifecycle);
|
||||||
collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService<ILogger<ContextMenu>>(), p.GetRequiredService<DalamudUtilService>(), p.GetRequiredService<ApiController>(), objectTable));
|
collection.AddSingleton(p => new ContextMenuService(contextMenu, pluginInterface, gameData,
|
||||||
|
p.GetRequiredService<ILogger<ContextMenuService>>(), p.GetRequiredService<DalamudUtilService>(), p.GetRequiredService<ApiController>(), objectTable,
|
||||||
|
p.GetRequiredService<LightlessConfigService>(), p.GetRequiredService<PairRequestService>(), p.GetRequiredService<PairManager>(), clientState));
|
||||||
collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService<ILogger<IpcCallerPenumbra>>(), pluginInterface,
|
collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService<ILogger<IpcCallerPenumbra>>(), pluginInterface,
|
||||||
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<RedrawManager>()));
|
s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<RedrawManager>()));
|
||||||
collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService<ILogger<IpcCallerGlamourer>>(), pluginInterface,
|
collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService<ILogger<IpcCallerGlamourer>>(), pluginInterface,
|
||||||
@@ -230,7 +233,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>()));
|
s.GetRequiredService<LightlessProfileManager>(), s.GetRequiredService<PerformanceCollectorService>()));
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
|
collection.AddScoped<WindowMediatorSubscriberBase, PopupHandler>();
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
|
collection.AddScoped<WindowMediatorSubscriberBase, BroadcastUI>((s) => new BroadcastUI(s.GetRequiredService<ILogger<BroadcastUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
|
||||||
collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>()));
|
collection.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>((s) => new SyncshellFinderUI(s.GetRequiredService<ILogger<SyncshellFinderUI>>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PerformanceCollectorService>(), s.GetRequiredService<BroadcastService>(), s.GetRequiredService<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<DalamudUtilService>()));
|
||||||
collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
||||||
collection.AddScoped<IPopupHandler, CensusPopupHandler>();
|
collection.AddScoped<IPopupHandler, CensusPopupHandler>();
|
||||||
collection.AddScoped<CacheCreationService>();
|
collection.AddScoped<CacheCreationService>();
|
||||||
@@ -261,7 +264,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<LightlessPlugin>());
|
collection.AddHostedService(p => p.GetRequiredService<LightlessPlugin>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<ContextMenu>());
|
collection.AddHostedService(p => p.GetRequiredService<ContextMenuService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<BroadcastService>());
|
collection.AddHostedService(p => p.GetRequiredService<BroadcastService>());
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ using LightlessSync.LightlessConfiguration;
|
|||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using LightlessSync.WebAPI.SignalR;
|
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
private readonly ILogger<BroadcastService> _logger;
|
private readonly ILogger<BroadcastService> _logger;
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly LightlessMediator _mediator;
|
private readonly LightlessMediator _mediator;
|
||||||
private readonly HubFactory _hubFactory;
|
|
||||||
private readonly LightlessConfigService _config;
|
private readonly LightlessConfigService _config;
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
public LightlessMediator Mediator => _mediator;
|
public LightlessMediator Mediator => _mediator;
|
||||||
@@ -29,35 +26,37 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
private TimeSpan? _remainingTtl = null;
|
private TimeSpan? _remainingTtl = null;
|
||||||
private DateTime _lastTtlCheck = DateTime.MinValue;
|
private DateTime _lastTtlCheck = DateTime.MinValue;
|
||||||
private DateTime _lastForcedDisableTime = DateTime.MinValue;
|
private DateTime _lastForcedDisableTime = DateTime.MinValue;
|
||||||
private static readonly TimeSpan DisableCooldown = TimeSpan.FromSeconds(5);
|
private static readonly TimeSpan _disableCooldown = TimeSpan.FromSeconds(5);
|
||||||
public TimeSpan? RemainingTtl => _remainingTtl;
|
public TimeSpan? RemainingTtl => _remainingTtl;
|
||||||
public TimeSpan? RemainingCooldown
|
public TimeSpan? RemainingCooldown
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var elapsed = DateTime.UtcNow - _lastForcedDisableTime;
|
var elapsed = DateTime.UtcNow - _lastForcedDisableTime;
|
||||||
if (elapsed >= DisableCooldown) return null;
|
if (elapsed >= _disableCooldown) return null;
|
||||||
return DisableCooldown - elapsed;
|
return _disableCooldown - elapsed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public BroadcastService(ILogger<BroadcastService> logger, LightlessMediator mediator, HubFactory hubFactory, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController)
|
|
||||||
|
public BroadcastService(ILogger<BroadcastService> logger, LightlessMediator mediator, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_hubFactory = hubFactory;
|
|
||||||
_config = config;
|
_config = config;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RequireConnectionAsync(string context, Func<Task> action)
|
private async Task RequireConnectionAsync(string context, Func<Task> action)
|
||||||
{
|
{
|
||||||
if (!_apiController.IsConnected)
|
if (!_apiController.IsConnected)
|
||||||
{
|
{
|
||||||
_logger.LogDebug($"{context} skipped, not connected");
|
_logger.LogDebug(context + " skipped, not connected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await action().ConfigureAwait(false);
|
await action().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast);
|
_mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast);
|
||||||
@@ -65,7 +64,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
|
_mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
|
||||||
|
|
||||||
_apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken);
|
_apiController.OnConnected += () => _ = CheckLightfinderSupportAsync(cancellationToken);
|
||||||
_ = CheckLightfinderSupportAsync(cancellationToken);
|
//_ = CheckLightfinderSupportAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
@@ -86,13 +85,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var hub = _hubFactory.GetOrCreate(CancellationToken.None);
|
|
||||||
var dummy = "0".PadLeft(64, '0');
|
var dummy = "0".PadLeft(64, '0');
|
||||||
|
|
||||||
await hub.InvokeAsync<BroadcastStatusInfoDto?>("IsUserBroadcasting", dummy, cancellationToken);
|
await _apiController.IsUserBroadcasting(dummy).ConfigureAwait(false);
|
||||||
await hub.InvokeAsync("SetBroadcastStatus", dummy, true, null, cancellationToken);
|
await _apiController.SetBroadcastStatus(dummy, true, null).ConfigureAwait(false);
|
||||||
await hub.InvokeAsync<TimeSpan?>("GetBroadcastTtl", dummy, cancellationToken);
|
await _apiController.GetBroadcastTtl(dummy).ConfigureAwait(false);
|
||||||
await hub.InvokeAsync<Dictionary<string, BroadcastStatusInfoDto?>>("AreUsersBroadcasting", new[] { dummy }, cancellationToken);
|
await _apiController.AreUsersBroadcasting([dummy]).ConfigureAwait(false);
|
||||||
|
|
||||||
IsLightFinderAvailable = true;
|
IsLightFinderAvailable = true;
|
||||||
_logger.LogInformation("Lightfinder is available.");
|
_logger.LogInformation("Lightfinder is available.");
|
||||||
@@ -119,6 +117,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_config.Current.BroadcastEnabled = false;
|
_config.Current.BroadcastEnabled = false;
|
||||||
_config.Current.BroadcastTtl = DateTime.MinValue;
|
_config.Current.BroadcastTtl = DateTime.MinValue;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
|
||||||
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
|
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +141,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
|
|
||||||
await _apiController.SetBroadcastStatus(msg.HashedCid, msg.Enabled, groupDto).ConfigureAwait(false);
|
await _apiController.SetBroadcastStatus(msg.HashedCid, msg.Enabled, groupDto).ConfigureAwait(false);
|
||||||
|
|
||||||
_logger.LogInformation("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);
|
_logger.LogDebug("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);
|
||||||
|
|
||||||
if (!msg.Enabled)
|
if (!msg.Enabled)
|
||||||
{
|
{
|
||||||
@@ -151,13 +150,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_config.Save();
|
_config.Save();
|
||||||
|
|
||||||
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
|
_mediator.Publish(new BroadcastStatusChangedMessage(false, null));
|
||||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational,$"Disabled Lightfinder for Player: {msg.HashedCid}")));
|
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Disabled Lightfinder for Player: {msg.HashedCid}")));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_waitingForTtlFetch = true;
|
_waitingForTtlFetch = true;
|
||||||
|
|
||||||
var ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
|
TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
|
||||||
|
|
||||||
if (ttl is { } remaining && remaining > TimeSpan.Zero)
|
if (ttl is { } remaining && remaining > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
@@ -165,7 +164,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_config.Current.BroadcastEnabled = true;
|
_config.Current.BroadcastEnabled = true;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
|
||||||
_logger.LogInformation("Fetched TTL from server: {TTL}", remaining);
|
_logger.LogDebug("Fetched TTL from server: {TTL}", remaining);
|
||||||
_mediator.Publish(new BroadcastStatusChangedMessage(true, remaining));
|
_mediator.Publish(new BroadcastStatusChangedMessage(true, remaining));
|
||||||
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
|
Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
|
||||||
}
|
}
|
||||||
@@ -202,13 +201,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("[BroadcastCheck] Checking CID: {cid}", targetCid);
|
_logger.LogDebug("[BroadcastCheck] Checking CID: {cid}", targetCid);
|
||||||
|
|
||||||
var info = await _apiController.IsUserBroadcasting(targetCid).ConfigureAwait(false);
|
var info = await _apiController.IsUserBroadcasting(targetCid).ConfigureAwait(false);
|
||||||
result = info?.TTL > TimeSpan.Zero;
|
result = info?.TTL > TimeSpan.Zero;
|
||||||
|
|
||||||
|
|
||||||
_logger.LogInformation("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID);
|
_logger.LogDebug("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -252,7 +251,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
result[kv.Key] = kv.Value;
|
result[kv.Key] = kv.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count);
|
_logger.LogTrace("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -292,10 +291,10 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
if (!newStatus)
|
if (!newStatus)
|
||||||
{
|
{
|
||||||
_lastForcedDisableTime = DateTime.UtcNow;
|
_lastForcedDisableTime = DateTime.UtcNow;
|
||||||
_logger.LogInformation("Manual disable: cooldown timer started.");
|
_logger.LogDebug("Manual disable: cooldown timer started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
_logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
||||||
|
|
||||||
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
||||||
}
|
}
|
||||||
@@ -325,15 +324,15 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_syncedOnStartup = true;
|
_syncedOnStartup = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
string hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||||
var ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
|
TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
|
||||||
if (ttl is { }
|
if (ttl is { }
|
||||||
remaining && remaining > TimeSpan.Zero)
|
remaining && remaining > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
_config.Current.BroadcastTtl = DateTime.UtcNow + remaining;
|
_config.Current.BroadcastTtl = DateTime.UtcNow + remaining;
|
||||||
_config.Current.BroadcastEnabled = true;
|
_config.Current.BroadcastEnabled = true;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
_logger.LogInformation("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining);
|
_logger.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -357,12 +356,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expiry = _config.Current.BroadcastTtl;
|
DateTime expiry = _config.Current.BroadcastTtl;
|
||||||
var remaining = expiry - DateTime.UtcNow;
|
TimeSpan remaining = expiry - DateTime.UtcNow;
|
||||||
_remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
|
_remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
|
||||||
if (_remainingTtl == null)
|
if (_remainingTtl == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Broadcast TTL expired. Disabling broadcast locally.");
|
_logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
|
||||||
_config.Current.BroadcastEnabled = false;
|
_config.Current.BroadcastEnabled = false;
|
||||||
_config.Current.BroadcastTtl = DateTime.MinValue;
|
_config.Current.BroadcastTtl = DateTime.MinValue;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
|||||||
204
LightlessSync/Services/ContextMenuService.cs
Normal file
204
LightlessSync/Services/ContextMenuService.cs
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
|
using Dalamud.Game.Gui.ContextMenu;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using LightlessSync.LightlessConfiguration;
|
||||||
|
using LightlessSync.PlayerData.Pairs;
|
||||||
|
using LightlessSync.Utils;
|
||||||
|
using LightlessSync.WebAPI;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
|
internal class ContextMenuService : IHostedService
|
||||||
|
{
|
||||||
|
private readonly IContextMenu _contextMenu;
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly IDataManager _gameData;
|
||||||
|
private readonly ILogger<ContextMenuService> _logger;
|
||||||
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly PairRequestService _pairRequestService;
|
||||||
|
private readonly ApiController _apiController;
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
|
public ContextMenuService(
|
||||||
|
IContextMenu contextMenu,
|
||||||
|
IDalamudPluginInterface pluginInterface,
|
||||||
|
IDataManager gameData,
|
||||||
|
ILogger<ContextMenuService> logger,
|
||||||
|
DalamudUtilService dalamudUtil,
|
||||||
|
ApiController apiController,
|
||||||
|
IObjectTable objectTable,
|
||||||
|
LightlessConfigService configService,
|
||||||
|
PairRequestService pairRequestService,
|
||||||
|
PairManager pairManager,
|
||||||
|
IClientState clientState)
|
||||||
|
{
|
||||||
|
_contextMenu = contextMenu;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_gameData = gameData;
|
||||||
|
_logger = logger;
|
||||||
|
_dalamudUtil = dalamudUtil;
|
||||||
|
_apiController = apiController;
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_pairManager = pairManager;
|
||||||
|
_pairRequestService = pairRequestService;
|
||||||
|
_clientState = clientState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_contextMenu.OnMenuOpened += OnMenuOpened;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
_contextMenu.OnMenuOpened += OnMenuOpened;
|
||||||
|
_logger.LogDebug("Context menu enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
||||||
|
_logger.LogDebug("Context menu disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMenuOpened(IMenuOpenedArgs args)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!_pluginInterface.UiBuilder.ShouldModifyUi)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (args.AddonName != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check if target is not menutargetdefault.
|
||||||
|
if (args.Target is not MenuTargetDefault target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check if name or target id isnt null/zero
|
||||||
|
if (string.IsNullOrEmpty(target.TargetName) || target.TargetObjectId == 0 || target.TargetHomeWorld.RowId == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check if it is a real target.
|
||||||
|
IPlayerCharacter? targetData = GetPlayerFromObjectTable(target);
|
||||||
|
if (targetData == null || targetData.Address == nint.Zero)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check if user is paired or is own.
|
||||||
|
if (VisibleUserIds.Any(u => u == target.TargetObjectId) || _clientState.LocalPlayer.GameObjectId == target.TargetObjectId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check if in PVP or GPose
|
||||||
|
if (_clientState.IsPvPExcludingDen || _clientState.IsGPosing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Check for valid world.
|
||||||
|
var world = GetWorld(target.TargetHomeWorld.RowId);
|
||||||
|
if (!IsWorldValid(world))
|
||||||
|
return;
|
||||||
|
|
||||||
|
args.AddMenuItem(new MenuItem
|
||||||
|
{
|
||||||
|
Name = "Send Pair Request",
|
||||||
|
PrefixChar = 'L',
|
||||||
|
UseDefaultPrefix = false,
|
||||||
|
PrefixColor = 708,
|
||||||
|
OnClicked = async _ => await HandleSelection(args).ConfigureAwait(false)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleSelection(IMenuArgs args)
|
||||||
|
{
|
||||||
|
if (args.Target is not MenuTargetDefault target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var world = GetWorld(target.TargetHomeWorld.RowId);
|
||||||
|
if (!IsWorldValid(world))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IPlayerCharacter? targetData = GetPlayerFromObjectTable(target);
|
||||||
|
|
||||||
|
if (targetData == null || targetData.Address == nint.Zero)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||||
|
var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
|
||||||
|
|
||||||
|
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);
|
||||||
|
await _apiController.TryPairWithContentId(receiverCid, senderCid).ConfigureAwait(false);
|
||||||
|
if (!string.IsNullOrWhiteSpace(receiverCid))
|
||||||
|
{
|
||||||
|
_pairRequestService.RemoveRequest(receiverCid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error sending pair request.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private HashSet<ulong> VisibleUserIds => [.. _pairManager.GetOnlineUserPairs()
|
||||||
|
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
||||||
|
.Select(u => (ulong)u.PlayerCharacterId)];
|
||||||
|
|
||||||
|
private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target)
|
||||||
|
{
|
||||||
|
return _objectTable
|
||||||
|
.OfType<IPlayerCharacter>()
|
||||||
|
.FirstOrDefault(p =>
|
||||||
|
string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private World GetWorld(uint worldId)
|
||||||
|
{
|
||||||
|
var sheet = _gameData.GetExcelSheet<World>()!;
|
||||||
|
var luminaWorlds = sheet.Where(x =>
|
||||||
|
{
|
||||||
|
var dc = x.DataCenter.ValueNullable;
|
||||||
|
var name = x.Name.ExtractText();
|
||||||
|
var internalName = x.InternalName.ExtractText();
|
||||||
|
|
||||||
|
if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return luminaWorlds.FirstOrDefault(x => x.RowId == worldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter);
|
||||||
|
|
||||||
|
private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF;
|
||||||
|
|
||||||
|
public bool IsWorldValid(uint worldId) => IsWorldValid(GetWorld(worldId));
|
||||||
|
|
||||||
|
public static bool IsWorldValid(World world)
|
||||||
|
{
|
||||||
|
var name = world.Name.ToString();
|
||||||
|
return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
@@ -541,7 +541,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
curWaitTime += tick;
|
curWaitTime += tick;
|
||||||
Thread.Sleep(tick);
|
Thread.Sleep(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(tick * 2);
|
Thread.Sleep(tick * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +556,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string? GetWorldNameFromPlayerAddress(nint address)
|
||||||
|
{
|
||||||
|
if (address == nint.Zero) return null;
|
||||||
|
|
||||||
|
EnsureIsOnFramework();
|
||||||
|
var playerCharacter = _objectTable.OfType<IPlayerCharacter>().FirstOrDefault(p => p.Address == address);
|
||||||
|
if (playerCharacter == null) return null;
|
||||||
|
|
||||||
|
var worldId = (ushort)playerCharacter.HomeWorld.RowId;
|
||||||
|
return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null;
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe void CheckCharacterForDrawing(nint address, string characterName)
|
private unsafe void CheckCharacterForDrawing(nint address, string characterName)
|
||||||
{
|
{
|
||||||
var gameObj = (GameObject*)address;
|
var gameObj = (GameObject*)address;
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ public record OpenCharaDataHubWithFilterMessage(UserData UserData) : MessageBase
|
|||||||
public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBase;
|
public record EnableBroadcastMessage(string HashedCid, bool Enabled) : MessageBase;
|
||||||
public record BroadcastStatusChangedMessage(bool Enabled, TimeSpan? Ttl) : MessageBase;
|
public record BroadcastStatusChangedMessage(bool Enabled, TimeSpan? Ttl) : MessageBase;
|
||||||
public record SyncshellBroadcastsUpdatedMessage : MessageBase;
|
public record SyncshellBroadcastsUpdatedMessage : MessageBase;
|
||||||
|
public record PairRequestsUpdatedMessage : MessageBase;
|
||||||
public record VisibilityChange : MessageBase;
|
public record VisibilityChange : MessageBase;
|
||||||
#pragma warning restore S2094
|
#pragma warning restore S2094
|
||||||
#pragma warning restore MA0048 // File name must match type name
|
#pragma warning restore MA0048 // File name must match type name
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.Gui.NamePlate;
|
using Dalamud.Game.Gui.NamePlate;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
@@ -40,7 +40,6 @@ public class NameplateService : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen))
|
if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -69,16 +68,26 @@ public class NameplateService : DisposableMediatorSubscriberBase
|
|||||||
(isFriend && !friendColorAllowed)
|
(isFriend && !friendColorAllowed)
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
//_logger.LogInformation("added nameplate color to {Name}", playerCharacter.Name.TextValue);
|
|
||||||
handler.NameParts.TextWrap = CreateTextWrap(colors);
|
handler.NameParts.TextWrap = CreateTextWrap(colors);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (_configService.Current.overrideFcTagColor)
|
||||||
|
{
|
||||||
|
bool hasActualFcTag = playerCharacter.CompanyTag.TextValue.Length > 0;
|
||||||
|
bool isFromDifferentRealm = playerCharacter.HomeWorld.RowId != playerCharacter.CurrentWorld.RowId;
|
||||||
|
bool shouldColorFcArea = hasActualFcTag || (!hasActualFcTag && isFromDifferentRealm);
|
||||||
|
|
||||||
|
if (shouldColorFcArea)
|
||||||
|
{
|
||||||
|
handler.FreeCompanyTagParts.OuterWrap = CreateTextWrap(colors);
|
||||||
|
handler.FreeCompanyTagParts.TextWrap = CreateTextWrap(colors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestRedraw()
|
public void RequestRedraw()
|
||||||
{
|
{
|
||||||
|
|
||||||
_namePlateGui.RequestRedraw();
|
_namePlateGui.RequestRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
189
LightlessSync/Services/PairRequestService.cs
Normal file
189
LightlessSync/Services/PairRequestService.cs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using LightlessSync.PlayerData.Pairs;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
|
public sealed class PairRequestService : DisposableMediatorSubscriberBase
|
||||||
|
{
|
||||||
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly object _syncRoot = new();
|
||||||
|
private readonly List<PairRequestEntry> _requests = [];
|
||||||
|
|
||||||
|
private static readonly TimeSpan Expiration = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
public PairRequestService(ILogger<PairRequestService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtil, PairManager pairManager)
|
||||||
|
: base(logger, mediator)
|
||||||
|
{
|
||||||
|
_dalamudUtil = dalamudUtil;
|
||||||
|
_pairManager = pairManager;
|
||||||
|
|
||||||
|
Mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, _ =>
|
||||||
|
{
|
||||||
|
bool removed;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
removed = CleanupExpiredUnsafe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
Mediator.Publish(new PairRequestsUpdatedMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public PairRequestDisplay RegisterIncomingRequest(string hashedCid, string messageTemplate)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(hashedCid))
|
||||||
|
{
|
||||||
|
hashedCid = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
messageTemplate ??= string.Empty;
|
||||||
|
|
||||||
|
PairRequestEntry entry = new(hashedCid, messageTemplate, DateTime.UtcNow);
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
CleanupExpiredUnsafe();
|
||||||
|
var index = _requests.FindIndex(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal));
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
_requests[index] = entry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_requests.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var display = _dalamudUtil.IsOnFrameworkThread
|
||||||
|
? ToDisplay(entry)
|
||||||
|
: _dalamudUtil.RunOnFrameworkThread(() => ToDisplay(entry)).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
Mediator.Publish(new PairRequestsUpdatedMessage());
|
||||||
|
return display;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<PairRequestDisplay> GetActiveRequests()
|
||||||
|
{
|
||||||
|
List<PairRequestEntry> entries;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
CleanupExpiredUnsafe();
|
||||||
|
entries = _requests
|
||||||
|
.OrderByDescending(r => r.ReceivedAt)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _dalamudUtil.IsOnFrameworkThread
|
||||||
|
? entries.Select(ToDisplay).ToList()
|
||||||
|
: _dalamudUtil.RunOnFrameworkThread(() => entries.Select(ToDisplay).ToList()).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveRequest(string hashedCid)
|
||||||
|
{
|
||||||
|
bool removed;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
removed = _requests.RemoveAll(r => string.Equals(r.HashedCid, hashedCid, StringComparison.Ordinal)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
Mediator.Publish(new PairRequestsUpdatedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasPendingRequests()
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
CleanupExpiredUnsafe();
|
||||||
|
return _requests.Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PairRequestDisplay ToDisplay(PairRequestEntry entry)
|
||||||
|
{
|
||||||
|
var displayName = ResolveDisplayName(entry.HashedCid);
|
||||||
|
var message = FormatMessage(entry.MessageTemplate, displayName);
|
||||||
|
return new PairRequestDisplay(entry.HashedCid, displayName, message, entry.ReceivedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ResolveDisplayName(string hashedCid)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(hashedCid))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (name, address) = _dalamudUtil.FindPlayerByNameHash(hashedCid);
|
||||||
|
if (!string.IsNullOrWhiteSpace(name))
|
||||||
|
{
|
||||||
|
var worldName = _dalamudUtil.GetWorldNameFromPlayerAddress(address);
|
||||||
|
return !string.IsNullOrWhiteSpace(worldName)
|
||||||
|
? $"{name} @ {worldName}"
|
||||||
|
: name;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pair = _pairManager
|
||||||
|
.GetOnlineUserPairs()
|
||||||
|
.FirstOrDefault(p => string.Equals(p.Ident, hashedCid, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
if (pair != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(pair.PlayerName))
|
||||||
|
{
|
||||||
|
return pair.PlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(pair.UserData.AliasOrUID))
|
||||||
|
{
|
||||||
|
return pair.UserData.AliasOrUID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatMessage(string template, string displayName)
|
||||||
|
{
|
||||||
|
var safeName = string.IsNullOrWhiteSpace(displayName) ? "Someone" : displayName;
|
||||||
|
template ??= string.Empty;
|
||||||
|
const string placeholder = "{DisplayName}";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(template) && template.Contains(placeholder, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return template.Replace(placeholder, safeName, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(template))
|
||||||
|
{
|
||||||
|
return $"{safeName}: {template}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{safeName} sent you a pair request.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CleanupExpiredUnsafe()
|
||||||
|
{
|
||||||
|
if (_requests.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
return _requests.RemoveAll(r => now - r.ReceivedAt > Expiration) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record struct PairRequestEntry(string HashedCid, string MessageTemplate, DateTime ReceivedAt);
|
||||||
|
|
||||||
|
public readonly record struct PairRequestDisplay(string HashedCid, string DisplayName, string Message, DateTime ReceivedAt);
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using LightlessSync.API.Dto.Group;
|
using LightlessSync.API.Dto.Group;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -22,7 +24,7 @@ namespace LightlessSync.UI
|
|||||||
private IReadOnlyList<GroupFullInfoDto> _allSyncshells;
|
private IReadOnlyList<GroupFullInfoDto> _allSyncshells;
|
||||||
private string _userUid = string.Empty;
|
private string _userUid = string.Empty;
|
||||||
|
|
||||||
private List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
|
private readonly List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
|
||||||
|
|
||||||
public BroadcastUI(
|
public BroadcastUI(
|
||||||
ILogger<BroadcastUI> logger,
|
ILogger<BroadcastUI> logger,
|
||||||
@@ -44,11 +46,11 @@ namespace LightlessSync.UI
|
|||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
this.SizeConstraints = new()
|
this.SizeConstraints = new()
|
||||||
{
|
{
|
||||||
MinimumSize = new(600, 340),
|
MinimumSize = new(600, 465),
|
||||||
MaximumSize = new(750, 400)
|
MaximumSize = new(750, 525)
|
||||||
};
|
};
|
||||||
|
|
||||||
mediator.Subscribe<RefreshUiMessage>(this, async _ => await RefreshSyncshells());
|
mediator.Subscribe<RefreshUiMessage>(this, async _ => await RefreshSyncshells().ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RebuildSyncshellDropdownOptions()
|
private void RebuildSyncshellDropdownOptions()
|
||||||
@@ -62,7 +64,7 @@ namespace LightlessSync.UI
|
|||||||
_syncshellOptions.Clear();
|
_syncshellOptions.Clear();
|
||||||
_syncshellOptions.Add(("None", null, true));
|
_syncshellOptions.Add(("None", null, true));
|
||||||
|
|
||||||
var addedGids = new HashSet<string>();
|
var addedGids = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
foreach (var shell in ownedSyncshells)
|
foreach (var shell in ownedSyncshells)
|
||||||
{
|
{
|
||||||
@@ -73,7 +75,7 @@ namespace LightlessSync.UI
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
||||||
{
|
{
|
||||||
var matching = allSyncshells.FirstOrDefault(g => g.GID == selectedGid);
|
var matching = allSyncshells.FirstOrDefault(g => string.Equals(g.GID, selectedGid, StringComparison.Ordinal));
|
||||||
if (matching != null)
|
if (matching != null)
|
||||||
{
|
{
|
||||||
var label = matching.GroupAliasOrGID ?? matching.GID;
|
var label = matching.GroupAliasOrGID ?? matching.GID;
|
||||||
@@ -97,7 +99,7 @@ namespace LightlessSync.UI
|
|||||||
{
|
{
|
||||||
if (!_apiController.IsConnected)
|
if (!_apiController.IsConnected)
|
||||||
{
|
{
|
||||||
_allSyncshells = Array.Empty<GroupFullInfoDto>();
|
_allSyncshells = [];
|
||||||
RebuildSyncshellDropdownOptions();
|
RebuildSyncshellDropdownOptions();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,7 @@ namespace LightlessSync.UI
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to fetch Syncshells.");
|
_logger.LogError(ex, "Failed to fetch Syncshells.");
|
||||||
_allSyncshells = Array.Empty<GroupFullInfoDto>();
|
_allSyncshells = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
RebuildSyncshellDropdownOptions();
|
RebuildSyncshellDropdownOptions();
|
||||||
@@ -131,25 +133,66 @@ namespace LightlessSync.UI
|
|||||||
ImGuiHelpers.ScaledDummy(0.25f);
|
ImGuiHelpers.ScaledDummy(0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTabBar("##MyTabBar"))
|
if (ImGui.BeginTabBar("##BroadcastTabs"))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTabItem("Lightfinder"))
|
if (ImGui.BeginTabItem("Lightfinder"))
|
||||||
{
|
{
|
||||||
_uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue"));
|
_uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue"));
|
||||||
|
|
||||||
ImGui.PushTextWrapPos();
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, -2));
|
||||||
ImGui.Text("This lets other Lightless users know you use Lightless.");
|
|
||||||
ImGui.Text("By enabling this, the server will allow other people to see that you are using Lightless.");
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"),"This lets other Lightless users know you use Lightless. While enabled, you and others using Lightfinder can see each other identified as Lightless users.");
|
||||||
ImGui.Text("When disabled, pairing is still possible but both parties need to mutually send each other requests, receiving party will not be notified about the request unless the pairing is complete.");
|
|
||||||
ImGui.Text("At no point ever, even when Lightfinder is active that any Lightless data is getting sent to other people (including ID's), the server keeps this to itself.");
|
ImGui.Indent(15f);
|
||||||
ImGui.Text("You can request to pair by right-clicking any (not yourself) character and using 'Send Pair Request'.");
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||||
ImGui.PopTextWrapPos();
|
ImGui.Text("- This is done using a 'Lightless' label above player nameplates.");
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
ImGui.Unindent(15f);
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(3f);
|
||||||
|
|
||||||
|
_uiSharedService.MediumText("Pairing", UIColors.Get("PairBlue"));
|
||||||
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Pairing may be initiated via the right-click context menu on another player." +
|
||||||
|
" The process requires mutual confirmation: the sender initiates the request, and the recipient completes it by responding with a request in return.");
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine(
|
||||||
|
"! ",
|
||||||
|
UIColors.Get("LightlessYellow"),
|
||||||
|
new SeStringUtils.RichTextEntry("If Lightfinder is "),
|
||||||
|
new SeStringUtils.RichTextEntry("ENABLED", UIColors.Get("LightlessGreen"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will get notified about it."));
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine(
|
||||||
|
"! ",
|
||||||
|
UIColors.Get("LightlessYellow"),
|
||||||
|
new SeStringUtils.RichTextEntry("If Lightfinder is "),
|
||||||
|
new SeStringUtils.RichTextEntry("DISABLED", UIColors.Get("DimRed"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will "),
|
||||||
|
new SeStringUtils.RichTextEntry("NOT", UIColors.Get("DimRed"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(" get a notification, and the request will not be visible to them in any way."));
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(3f);
|
||||||
|
|
||||||
|
_uiSharedService.MediumText("Privacy", UIColors.Get("PairBlue"));
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine(
|
||||||
|
"! ",
|
||||||
|
UIColors.Get("DimRed"),
|
||||||
|
new SeStringUtils.RichTextEntry("Lightfinder is entirely "),
|
||||||
|
new SeStringUtils.RichTextEntry("opt-in", UIColors.Get("LightlessYellow"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(" and does not share any data with other users. All identifying information remains private to the server."));
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"), "Pairing is intended as a mutual agreement between both parties. A pair request will not be visible to the recipient unless Lightfinder is enabled.");
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(5f);
|
||||||
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
ImGui.Text("Use it only when you want to be visible.");
|
ImGui.TextWrapped("Use Lightfinder when you're okay with being visible to other users and understand that you are responsible for your own experience.");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0.2f);
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(3f);
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
||||||
|
|
||||||
if (_configService.Current.BroadcastEnabled)
|
if (_configService.Current.BroadcastEnabled)
|
||||||
@@ -168,7 +211,7 @@ namespace LightlessSync.UI
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
ImGui.Text("The Lightfinder’s light wanes, but not in vain."); // cringe..
|
ImGui.Text("The Lightfinder<EFBFBD>s light wanes, but not in vain."); // cringe..
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,14 +303,14 @@ namespace LightlessSync.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
||||||
var currentOption = _syncshellOptions.FirstOrDefault(o => o.GID == selectedGid);
|
var currentOption = _syncshellOptions.FirstOrDefault(o => string.Equals(o.GID, selectedGid, StringComparison.Ordinal));
|
||||||
var preview = currentOption.Label ?? "Select a Syncshell...";
|
var preview = currentOption.Label ?? "Select a Syncshell...";
|
||||||
|
|
||||||
if (ImGui.BeginCombo("##SyncshellDropdown", preview))
|
if (ImGui.BeginCombo("##SyncshellDropdown", preview))
|
||||||
{
|
{
|
||||||
foreach (var (label, gid, available) in _syncshellOptions)
|
foreach (var (label, gid, available) in _syncshellOptions)
|
||||||
{
|
{
|
||||||
bool isSelected = gid == selectedGid;
|
bool isSelected = string.Equals(gid, selectedGid, StringComparison.Ordinal);
|
||||||
|
|
||||||
if (!available)
|
if (!available)
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
@@ -310,6 +353,7 @@ namespace LightlessSync.UI
|
|||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
if (ImGui.BeginTabItem("Debug"))
|
if (ImGui.BeginTabItem("Debug"))
|
||||||
{
|
{
|
||||||
ImGui.Text("Broadcast Cache");
|
ImGui.Text("Broadcast Cache");
|
||||||
@@ -366,17 +410,12 @@ namespace LightlessSync.UI
|
|||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ImGui.EndTabBar();
|
ImGui.EndTabBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
IpcManager ipcManager,
|
IpcManager ipcManager,
|
||||||
BroadcastService broadcastService,
|
BroadcastService broadcastService,
|
||||||
CharacterAnalyzer characterAnalyzer,
|
CharacterAnalyzer characterAnalyzer,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfig) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_uiSharedService = uiShared;
|
_uiSharedService = uiShared;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -103,7 +104,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_renamePairTagUi = renameTagUi;
|
_renamePairTagUi = renameTagUi;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
_broadcastService = broadcastService;
|
_broadcastService = broadcastService;
|
||||||
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService);
|
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService);
|
||||||
|
|
||||||
AllowPinning = true;
|
AllowPinning = true;
|
||||||
AllowClickthrough = false;
|
AllowClickthrough = false;
|
||||||
@@ -141,7 +142,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
_drawFolders = [.. GetDrawFolders()];
|
_drawFolders = [.. DrawFolders];
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
string dev = "Dev Build";
|
string dev = "Dev Build";
|
||||||
@@ -158,7 +159,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
||||||
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
||||||
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
||||||
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = GetDrawFolders().ToList());
|
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = DrawFolders.ToList());
|
||||||
|
|
||||||
Flags |= ImGuiWindowFlags.NoDocking;
|
Flags |= ImGuiWindowFlags.NoDocking;
|
||||||
|
|
||||||
@@ -401,7 +402,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.TextUnformatted("No uploads in progress");
|
ImGui.TextUnformatted("No uploads in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentDownloads = _currentDownloads.SelectMany(d => d.Value.Values).ToList();
|
var currentDownloads = BuildCurrentDownloadSnapshot();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
||||||
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
||||||
@@ -428,10 +429,53 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<FileDownloadStatus> BuildCurrentDownloadSnapshot()
|
||||||
|
{
|
||||||
|
List<FileDownloadStatus> snapshot = new();
|
||||||
|
|
||||||
|
foreach (var kvp in _currentDownloads.ToArray())
|
||||||
|
{
|
||||||
|
var value = kvp.Value;
|
||||||
|
if (value == null || value.Count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
snapshot.AddRange(value.Values.ToArray());
|
||||||
|
}
|
||||||
|
catch (System.ArgumentException)
|
||||||
|
{
|
||||||
|
// skibidi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawUIDHeader()
|
private void DrawUIDHeader()
|
||||||
{
|
{
|
||||||
var uidText = GetUidText();
|
var uidText = GetUidText();
|
||||||
|
|
||||||
|
Vector4? vanityTextColor = null;
|
||||||
|
Vector4? vanityGlowColor = null;
|
||||||
|
bool useVanityColors = false;
|
||||||
|
|
||||||
|
if (_configService.Current.useColoredUIDs && _apiController.HasVanity)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(_apiController.TextColorHex))
|
||||||
|
{
|
||||||
|
vanityTextColor = UIColors.HexToRgba(_apiController.TextColorHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(_apiController.TextGlowColorHex))
|
||||||
|
{
|
||||||
|
vanityGlowColor = UIColors.HexToRgba(_apiController.TextGlowColorHex);
|
||||||
|
}
|
||||||
|
|
||||||
|
useVanityColors = vanityTextColor is not null || vanityGlowColor is not null;
|
||||||
|
}
|
||||||
|
|
||||||
//Getting information of character and triangles threshold to show overlimit status in UID bar.
|
//Getting information of character and triangles threshold to show overlimit status in UID bar.
|
||||||
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
|
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
|
||||||
|
|
||||||
@@ -446,7 +490,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
float uidStartX = (contentWidth - uidTextSize.X) / 2f;
|
float uidStartX = (contentWidth - uidTextSize.X) / 2f;
|
||||||
float cursorY = ImGui.GetCursorPosY();
|
float cursorY = ImGui.GetCursorPosY();
|
||||||
|
|
||||||
if (_configService.Current.BroadcastEnabled)
|
if (_configService.Current.BroadcastEnabled && _apiController.IsConnected)
|
||||||
{
|
{
|
||||||
float iconYOffset = (uidTextSize.Y - iconSize.Y) * 0.5f;
|
float iconYOffset = (uidTextSize.Y - iconSize.Y) * 0.5f;
|
||||||
var buttonSize = new Vector2(iconSize.X, uidTextSize.Y);
|
var buttonSize = new Vector2(iconSize.X, uidTextSize.Y);
|
||||||
@@ -467,14 +511,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Text("Lightfinder");
|
ImGui.Text("Lightfinder");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGui.Text("This lets other Lightless users know you use Lightless.");
|
|
||||||
ImGui.Text("By enabling this, the server will allow other people to see that you are using Lightless.");
|
|
||||||
ImGui.Text("When disabled, pairing is still possible but both parties need to mutually send each other requests, receiving party will not be notified about the request unless the pairing is complete.");
|
|
||||||
ImGui.Text("At no point ever, even when Lightfinder is active that any Lightless data is getting sent to other people (including ID's), the server keeps this to itself.");
|
|
||||||
ImGui.Text("You can request to pair by right-clicking any (not yourself) character and using 'Send Pair Request'.");
|
|
||||||
|
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
ImGui.Text("Use it only when you want to be visible.");
|
ImGui.TextWrapped("Use Lightfinder when you're okay with being visible to other users and understand that you are responsible for your own experience.");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0.2f);
|
ImGuiHelpers.ScaledDummy(0.2f);
|
||||||
@@ -524,12 +562,30 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.SetCursorPosY(cursorY);
|
ImGui.SetCursorPosY(cursorY);
|
||||||
ImGui.SetCursorPosX(uidStartX);
|
ImGui.SetCursorPosX(uidStartX);
|
||||||
|
|
||||||
|
bool headerItemClicked;
|
||||||
using (_uiSharedService.UidFont.Push())
|
using (_uiSharedService.UidFont.Push())
|
||||||
|
{
|
||||||
|
if (useVanityColors)
|
||||||
|
{
|
||||||
|
var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor);
|
||||||
|
var cursorPos = ImGui.GetCursorScreenPos();
|
||||||
|
var fontPtr = ImGui.GetFont();
|
||||||
|
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
ImGui.TextColored(GetUidColor(), uidText);
|
ImGui.TextColored(GetUidColor(), uidText);
|
||||||
if (ImGui.IsItemClicked())
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headerItemClicked = ImGui.IsItemClicked();
|
||||||
|
|
||||||
|
if (headerItemClicked)
|
||||||
|
{
|
||||||
ImGui.SetClipboardText(uidText);
|
ImGui.SetClipboardText(uidText);
|
||||||
}
|
}
|
||||||
|
|
||||||
UiSharedService.AttachToolTip("Click to copy");
|
UiSharedService.AttachToolTip("Click to copy");
|
||||||
|
|
||||||
if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected)
|
if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected)
|
||||||
@@ -561,6 +617,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
|
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
ImGui.SetCursorPosY(cursorY + 15f);
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
|
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
|
||||||
|
|
||||||
string warningMessage = "";
|
string warningMessage = "";
|
||||||
@@ -588,7 +645,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (_apiController.ServerState is ServerState.Connected)
|
if (_apiController.ServerState is ServerState.Connected)
|
||||||
{
|
{
|
||||||
if (ImGui.IsItemClicked())
|
if (headerItemClicked)
|
||||||
{
|
{
|
||||||
ImGui.SetClipboardText(_apiController.DisplayName);
|
ImGui.SetClipboardText(_apiController.DisplayName);
|
||||||
}
|
}
|
||||||
@@ -597,9 +654,22 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var origTextSize = ImGui.CalcTextSize(_apiController.UID);
|
var origTextSize = ImGui.CalcTextSize(_apiController.UID);
|
||||||
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X) / 2 - (origTextSize.X / 2));
|
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X) / 2 - (origTextSize.X / 2));
|
||||||
|
|
||||||
|
if (useVanityColors)
|
||||||
|
{
|
||||||
|
var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor);
|
||||||
|
var cursorPos = ImGui.GetCursorScreenPos();
|
||||||
|
var fontPtr = ImGui.GetFont();
|
||||||
|
SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
ImGui.TextColored(GetUidColor(), _apiController.UID);
|
ImGui.TextColored(GetUidColor(), _apiController.UID);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool uidFooterClicked = ImGui.IsItemClicked();
|
||||||
UiSharedService.AttachToolTip("Click to copy");
|
UiSharedService.AttachToolTip("Click to copy");
|
||||||
if (ImGui.IsItemClicked())
|
if (uidFooterClicked)
|
||||||
{
|
{
|
||||||
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
||||||
}
|
}
|
||||||
@@ -611,152 +681,151 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IDrawFolder> GetDrawFolders()
|
private IEnumerable<IDrawFolder> DrawFolders
|
||||||
{
|
{
|
||||||
List<IDrawFolder> drawFolders = [];
|
get
|
||||||
|
|
||||||
var allPairs = _pairManager.PairsWithGroups
|
|
||||||
.ToDictionary(k => k.Key, k => k.Value);
|
|
||||||
var filteredPairs = allPairs
|
|
||||||
.Where(p =>
|
|
||||||
{
|
{
|
||||||
if (_tabMenu.Filter.IsNullOrEmpty()) return true;
|
var drawFolders = new List<IDrawFolder>();
|
||||||
return p.Key.UserData.AliasOrUID.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ||
|
var filter = _tabMenu.Filter;
|
||||||
(p.Key.GetNote()?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
|
|
||||||
(p.Key.PlayerName?.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ?? false);
|
|
||||||
})
|
|
||||||
.ToDictionary(k => k.Key, k => k.Value);
|
|
||||||
|
|
||||||
string? AlphabeticalSort(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(u.Key.PlayerName)
|
|
||||||
? (_configService.Current.PreferNotesOverNamesForVisible ? u.Key.GetNote() : u.Key.PlayerName)
|
|
||||||
: (u.Key.GetNote() ?? u.Key.UserData.AliasOrUID));
|
|
||||||
bool FilterOnlineOrPausedSelf(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> (u.Key.IsOnline || (!u.Key.IsOnline && !_configService.Current.ShowOfflineUsersSeparately)
|
|
||||||
|| u.Key.UserPair.OwnPermissions.IsPaused());
|
|
||||||
Dictionary<Pair, List<GroupFullInfoDto>> BasicSortedDictionary(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> u)
|
|
||||||
=> u.OrderByDescending(u => u.Key.IsVisible)
|
|
||||||
.ThenByDescending(u => u.Key.IsOnline)
|
|
||||||
.ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToDictionary(u => u.Key, u => u.Value);
|
|
||||||
ImmutableList<Pair> ImmutablePairList(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> u)
|
|
||||||
=> u.Select(k => k.Key).ToImmutableList();
|
|
||||||
bool FilterVisibleUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> u.Key.IsVisible
|
|
||||||
&& (_configService.Current.ShowSyncshellUsersInVisible || !(!_configService.Current.ShowSyncshellUsersInVisible && !u.Key.IsDirectlyPaired));
|
|
||||||
bool FilterTagUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u, string tag)
|
|
||||||
=> u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && _tagHandler.HasPairTag(u.Key.UserData.UID, tag);
|
|
||||||
bool FilterGroupUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u, GroupFullInfoDto group)
|
|
||||||
=> u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal));
|
|
||||||
bool FilterNotTaggedUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID);
|
|
||||||
bool FilterNotTaggedSyncshells(GroupFullInfoDto group)
|
|
||||||
=> (!_tagHandler.HasAnySyncshellTag(group.GID) && !_configService.Current.ShowGroupedSyncshellsInAll) || true;
|
|
||||||
bool FilterOfflineUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately)
|
|
||||||
|| !_configService.Current.ShowSyncshellOfflineUsersSeparately)
|
|
||||||
&& (!u.Key.IsOneSidedPair || u.Value.Any()) && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused();
|
|
||||||
bool FilterOfflineSyncshellUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
|
|
||||||
=> (!u.Key.IsDirectlyPaired && !u.Key.IsOnline && !u.Key.UserPair.OwnPermissions.IsPaused());
|
|
||||||
|
|
||||||
|
var allPairs = _pairManager.PairsWithGroups.ToDictionary(k => k.Key, k => k.Value);
|
||||||
|
var filteredPairs = allPairs.Where(p => PassesFilter(p.Key, filter)).ToDictionary(k => k.Key, k => k.Value);
|
||||||
|
|
||||||
|
//Filter of online/visible pairs
|
||||||
if (_configService.Current.ShowVisibleUsersSeparately)
|
if (_configService.Current.ShowVisibleUsersSeparately)
|
||||||
{
|
{
|
||||||
var allVisiblePairs = ImmutablePairList(allPairs
|
var allVisiblePairs = ImmutablePairList(allPairs.Where(p => FilterVisibleUsers(p.Key)));
|
||||||
.Where(FilterVisibleUsers));
|
var filteredVisiblePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterVisibleUsers(p.Key)));
|
||||||
var filteredVisiblePairs = BasicSortedDictionary(filteredPairs
|
|
||||||
.Where(FilterVisibleUsers));
|
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IDrawFolder> groupFolders = new();
|
//Filter of not foldered syncshells
|
||||||
|
var groupFolders = new List<IDrawFolder>();
|
||||||
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
|
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
|
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
|
||||||
|
|
||||||
if (FilterNotTaggedSyncshells(group))
|
if (FilterNotTaggedSyncshells(group))
|
||||||
{
|
{
|
||||||
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs));
|
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Filter of grouped up syncshells (All Syncshells Folder)
|
||||||
if (_configService.Current.GroupUpSyncshells)
|
if (_configService.Current.GroupUpSyncshells)
|
||||||
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
|
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService,
|
||||||
|
_selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
|
||||||
else
|
else
|
||||||
drawFolders.AddRange(groupFolders);
|
drawFolders.AddRange(groupFolders);
|
||||||
|
|
||||||
var tags = _tagHandler.GetAllPairTagsSorted();
|
//Filter of grouped/foldered pairs
|
||||||
foreach (var tag in tags)
|
foreach (var tag in _tagHandler.GetAllPairTagsSorted())
|
||||||
{
|
{
|
||||||
var allTagPairs = ImmutablePairList(allPairs
|
var allTagPairs = ImmutablePairList(allPairs.Where(p => FilterTagUsers(p.Key, tag)));
|
||||||
.Where(u => FilterTagUsers(u, tag)));
|
var filteredTagPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterTagUsers(p.Key, tag) && FilterOnlineOrPausedSelf(p.Key)));
|
||||||
var filteredTagPairs = BasicSortedDictionary(filteredPairs
|
|
||||||
.Where(u => FilterTagUsers(u, tag) && FilterOnlineOrPausedSelf(u)));
|
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(tag, filteredTagPairs, allTagPairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
var syncshellTags = _tagHandler.GetAllSyncshellTagsSorted();
|
//Filter of grouped/foldered syncshells
|
||||||
foreach (var syncshelltag in syncshellTags)
|
foreach (var syncshellTag in _tagHandler.GetAllSyncshellTagsSorted())
|
||||||
{
|
{
|
||||||
List<IDrawFolder> syncshellFolderTags = [];
|
var syncshellFolderTags = new List<IDrawFolder>();
|
||||||
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
|
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (_tagHandler.HasSyncshellTag(group.GID, syncshelltag))
|
if (_tagHandler.HasSyncshellTag(group.GID, syncshellTag))
|
||||||
{
|
{
|
||||||
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
|
GetGroups(allPairs, filteredPairs, group,
|
||||||
|
out ImmutableList<Pair> allGroupPairs,
|
||||||
|
out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
|
||||||
|
|
||||||
syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs));
|
syncshellFolderTags.Add(_drawEntityFactory.CreateDrawGroupFolder($"tag_{group.GID}", group, filteredGroupPairs, allGroupPairs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncshellFolderTags.Count > 0)
|
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag));
|
||||||
{
|
|
||||||
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var allOnlineNotTaggedPairs = ImmutablePairList(allPairs
|
//Filter of not grouped/foldered and offline pairs
|
||||||
.Where(FilterNotTaggedUsers));
|
var allOnlineNotTaggedPairs = ImmutablePairList(allPairs.Where(p => FilterNotTaggedUsers(p.Key)));
|
||||||
var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs
|
var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterNotTaggedUsers(p.Key) && FilterOnlineOrPausedSelf(p.Key)));
|
||||||
.Where(u => FilterNotTaggedUsers(u) && FilterOnlineOrPausedSelf(u)));
|
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag),
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), onlineNotTaggedPairs, allOnlineNotTaggedPairs));
|
||||||
onlineNotTaggedPairs, allOnlineNotTaggedPairs));
|
|
||||||
|
|
||||||
if (_configService.Current.ShowOfflineUsersSeparately)
|
if (_configService.Current.ShowOfflineUsersSeparately)
|
||||||
{
|
{
|
||||||
var allOfflinePairs = ImmutablePairList(allPairs
|
var allOfflinePairs = ImmutablePairList(allPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
|
||||||
.Where(FilterOfflineUsers));
|
var filteredOfflinePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
|
||||||
var filteredOfflinePairs = BasicSortedDictionary(filteredPairs
|
|
||||||
.Where(FilterOfflineUsers));
|
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineTag, filteredOfflinePairs, allOfflinePairs));
|
||||||
|
|
||||||
if (_configService.Current.ShowSyncshellOfflineUsersSeparately)
|
if (_configService.Current.ShowSyncshellOfflineUsersSeparately)
|
||||||
{
|
{
|
||||||
var allOfflineSyncshellUsers = ImmutablePairList(allPairs
|
var allOfflineSyncshellUsers = ImmutablePairList(allPairs.Where(p => FilterOfflineSyncshellUsers(p.Key)));
|
||||||
.Where(FilterOfflineSyncshellUsers));
|
var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineSyncshellUsers(p.Key)));
|
||||||
var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs
|
|
||||||
.Where(FilterOfflineSyncshellUsers));
|
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag,
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, filteredOfflineSyncshellUsers, allOfflineSyncshellUsers));
|
||||||
filteredOfflineSyncshellUsers,
|
|
||||||
allOfflineSyncshellUsers));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Unpaired
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag,
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag,
|
||||||
BasicSortedDictionary(filteredPairs.Where(u => u.Key.IsOneSidedPair)),
|
BasicSortedDictionary(filteredPairs.Where(p => p.Key.IsOneSidedPair)),
|
||||||
ImmutablePairList(allPairs.Where(u => u.Key.IsOneSidedPair))));
|
ImmutablePairList(allPairs.Where(p => p.Key.IsOneSidedPair))));
|
||||||
|
|
||||||
return drawFolders;
|
return drawFolders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GetGroups(Dictionary<Pair, List<GroupFullInfoDto>> allPairs, Dictionary<Pair, List<GroupFullInfoDto>> filteredPairs, GroupFullInfoDto group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs)
|
private static bool PassesFilter(Pair pair, string filter)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filter)) return true;
|
||||||
|
|
||||||
|
return pair.UserData.AliasOrUID.Contains(filter, StringComparison.OrdinalIgnoreCase) || (pair.GetNote()?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) || (pair.PlayerName?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AlphabeticalSortKey(Pair pair)
|
||||||
|
{
|
||||||
|
if (_configService.Current.ShowCharacterNameInsteadOfNotesForVisible && !string.IsNullOrEmpty(pair.PlayerName))
|
||||||
|
{
|
||||||
|
return _configService.Current.PreferNotesOverNamesForVisible ? (pair.GetNote() ?? string.Empty) : pair.PlayerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pair.GetNote() ?? pair.UserData.AliasOrUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool FilterOnlineOrPausedSelf(Pair pair) => pair.IsOnline || (!pair.IsOnline && !_configService.Current.ShowOfflineUsersSeparately) || pair.UserPair.OwnPermissions.IsPaused();
|
||||||
|
|
||||||
|
private bool FilterVisibleUsers(Pair pair) => pair.IsVisible && (_configService.Current.ShowSyncshellUsersInVisible || pair.IsDirectlyPaired);
|
||||||
|
|
||||||
|
private bool FilterTagUsers(Pair pair, string tag) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && _tagHandler.HasPairTag(pair.UserData.UID, tag);
|
||||||
|
|
||||||
|
private static bool FilterGroupUsers(List<GroupFullInfoDto> groups, GroupFullInfoDto group) => groups.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
private bool FilterNotTaggedUsers(Pair pair) => pair.IsDirectlyPaired && !pair.IsOneSidedPair && !_tagHandler.HasAnyPairTag(pair.UserData.UID);
|
||||||
|
|
||||||
|
private bool FilterNotTaggedSyncshells(GroupFullInfoDto group) => !_tagHandler.HasAnySyncshellTag(group.GID) || _configService.Current.ShowGroupedSyncshellsInAll;
|
||||||
|
|
||||||
|
private bool FilterOfflineUsers(Pair pair, List<GroupFullInfoDto> groups) => ((pair.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately) && (!pair.IsOneSidedPair || groups.Count != 0) && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused();
|
||||||
|
|
||||||
|
private static bool FilterOfflineSyncshellUsers(Pair pair) => !pair.IsDirectlyPaired && !pair.IsOnline && !pair.UserPair.OwnPermissions.IsPaused();
|
||||||
|
|
||||||
|
private Dictionary<Pair, List<GroupFullInfoDto>> BasicSortedDictionary(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> pairs) => pairs.OrderByDescending(u => u.Key.IsVisible).ThenByDescending(u => u.Key.IsOnline).ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase).ToDictionary(u => u.Key, u => u.Value);
|
||||||
|
|
||||||
|
private static ImmutableList<Pair> ImmutablePairList(IEnumerable<KeyValuePair<Pair, List<GroupFullInfoDto>>> pairs) => [.. pairs.Select(k => k.Key)];
|
||||||
|
|
||||||
|
private void GetGroups(Dictionary<Pair, List<GroupFullInfoDto>> allPairs,
|
||||||
|
Dictionary<Pair, List<GroupFullInfoDto>> filteredPairs,
|
||||||
|
GroupFullInfoDto group,
|
||||||
|
out ImmutableList<Pair> allGroupPairs,
|
||||||
|
out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs)
|
||||||
{
|
{
|
||||||
allGroupPairs = ImmutablePairList(allPairs
|
allGroupPairs = ImmutablePairList(allPairs
|
||||||
.Where(u => FilterGroupUsers(u, group)));
|
.Where(u => FilterGroupUsers(u.Value, group)));
|
||||||
|
|
||||||
filteredGroupPairs = filteredPairs
|
filteredGroupPairs = filteredPairs
|
||||||
.Where(u => FilterGroupUsers(u, group) && FilterOnlineOrPausedSelf(u))
|
.Where(u => FilterGroupUsers( u.Value, group) && FilterOnlineOrPausedSelf(u.Key))
|
||||||
.OrderByDescending(u => u.Key.IsOnline)
|
.OrderByDescending(u => u.Key.IsOnline)
|
||||||
.ThenBy(u =>
|
.ThenBy(u =>
|
||||||
{
|
{
|
||||||
@@ -768,10 +837,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
return u.Key.IsVisible ? 3 : 4;
|
return u.Key.IsVisible ? 3 : 4;
|
||||||
})
|
})
|
||||||
.ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase)
|
.ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase)
|
||||||
.ToDictionary(k => k.Key, k => k.Value);
|
.ToDictionary(k => k.Key, k => k.Value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private string GetServerError()
|
private string GetServerError()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ public class DrawGroupedGroupFolder : IDrawFolder
|
|||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
if (!_groups.Any()) return;
|
|
||||||
|
|
||||||
string _id = "__folder_syncshells";
|
string _id = "__folder_syncshells";
|
||||||
if (_tag != "")
|
if (_tag != "")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
|
||||||
using Dalamud.Game.Gui.ContextMenu;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using LightlessSync.Services;
|
|
||||||
using LightlessSync.Utils;
|
|
||||||
using LightlessSync.WebAPI;
|
|
||||||
using Lumina.Excel.Sheets;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
|
||||||
|
|
||||||
internal class ContextMenu : IHostedService
|
|
||||||
{
|
|
||||||
private readonly IContextMenu _contextMenu;
|
|
||||||
private readonly IDalamudPluginInterface _pluginInterface;
|
|
||||||
private readonly IDataManager _gameData;
|
|
||||||
private readonly ILogger<ContextMenu> _logger;
|
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
|
||||||
private readonly ApiController _apiController;
|
|
||||||
private readonly IObjectTable _objectTable;
|
|
||||||
|
|
||||||
private static readonly string[] ValidAddons = new[]
|
|
||||||
{
|
|
||||||
null,
|
|
||||||
"PartyMemberList", "FriendList", "FreeCompany", "LinkShell", "CrossWorldLinkshell",
|
|
||||||
"_PartyList", "ChatLog", "LookingForGroup", "BlackList", "ContentMemberList",
|
|
||||||
"SocialList", "ContactList", "BeginnerChatList", "MuteList"
|
|
||||||
};
|
|
||||||
|
|
||||||
public ContextMenu(
|
|
||||||
IContextMenu contextMenu,
|
|
||||||
IDalamudPluginInterface pluginInterface,
|
|
||||||
IDataManager gameData,
|
|
||||||
ILogger<ContextMenu> logger,
|
|
||||||
DalamudUtilService dalamudUtil,
|
|
||||||
ApiController apiController,
|
|
||||||
IObjectTable objectTable)
|
|
||||||
{
|
|
||||||
_contextMenu = contextMenu;
|
|
||||||
_pluginInterface = pluginInterface;
|
|
||||||
_gameData = gameData;
|
|
||||||
_logger = logger;
|
|
||||||
_dalamudUtil = dalamudUtil;
|
|
||||||
_apiController = apiController;
|
|
||||||
_objectTable = objectTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_contextMenu.OnMenuOpened += OnMenuOpened;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Enable()
|
|
||||||
{
|
|
||||||
_contextMenu.OnMenuOpened += OnMenuOpened;
|
|
||||||
_logger.LogDebug("Context menu enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Disable()
|
|
||||||
{
|
|
||||||
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
|
||||||
_logger.LogDebug("Context menu disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnMenuOpened(IMenuOpenedArgs args)
|
|
||||||
{
|
|
||||||
if (!_pluginInterface.UiBuilder.ShouldModifyUi)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!ValidAddons.Contains(args.AddonName))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (args.Target is not MenuTargetDefault target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(target.TargetName) || target.TargetObjectId == 0 || target.TargetHomeWorld.RowId == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
|
||||||
if (!IsWorldValid(world))
|
|
||||||
return;
|
|
||||||
|
|
||||||
args.AddMenuItem(new MenuItem
|
|
||||||
{
|
|
||||||
Name = "Send Pair Request",
|
|
||||||
PrefixChar = 'L',
|
|
||||||
UseDefaultPrefix = false,
|
|
||||||
PrefixColor = 708,
|
|
||||||
OnClicked = async _ => await HandleSelection(args)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task HandleSelection(IMenuArgs args)
|
|
||||||
{
|
|
||||||
if (args.Target is not MenuTargetDefault target)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
|
||||||
if (!IsWorldValid(world))
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var targetData = _objectTable
|
|
||||||
.OfType<IPlayerCharacter>()
|
|
||||||
.FirstOrDefault(p =>
|
|
||||||
string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
|
||||||
|
|
||||||
if (targetData == null || targetData.Address == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var senderCid = (await _dalamudUtil.GetCIDAsync()).ToString().GetHash256();
|
|
||||||
var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
|
|
||||||
|
|
||||||
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);
|
|
||||||
await _apiController.TryPairWithContentId(receiverCid, senderCid);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error sending pair request.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private World GetWorld(uint worldId)
|
|
||||||
{
|
|
||||||
var sheet = _gameData.GetExcelSheet<World>()!;
|
|
||||||
return sheet.TryGetRow(worldId, out var world) ? world : sheet.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsWorldValid(uint worldId) => IsWorldValid(GetWorld(worldId));
|
|
||||||
|
|
||||||
public static bool IsWorldValid(World world)
|
|
||||||
{
|
|
||||||
var name = world.Name.ToString();
|
|
||||||
return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -547,73 +547,147 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
|
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
|
||||||
if (tab.Success)
|
if (tab.Success)
|
||||||
{
|
{
|
||||||
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal).OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
||||||
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
|
||||||
|
|
||||||
ImGui.TextUnformatted("Files for " + kvp.Key);
|
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(1f, 1f));
|
||||||
ImGui.SameLine();
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1f, 1f));
|
||||||
|
|
||||||
|
if (ImGui.BeginTable($"##fileStats_{kvp.Key}", 3,
|
||||||
|
ImGuiTableFlags.BordersInnerV | ImGuiTableFlags.SizingFixedFit))
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"Files for {kvp.Key}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
string text = "";
|
string text = string.Join(Environment.NewLine, groupedfiles.Select(f =>
|
||||||
text = string.Join(Environment.NewLine, groupedfiles
|
$"{f.Key}: {f.Count()} files, size: {UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))}, compressed: {UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))}"));
|
||||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
|
||||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
|
||||||
ImGui.SetTooltip(text);
|
ImGui.SetTooltip(text);
|
||||||
}
|
}
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{kvp.Key} size (actual):");
|
ImGui.TextUnformatted($"{kvp.Key} size (actual):");
|
||||||
ImGui.SameLine();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):");
|
ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):");
|
||||||
ImGui.SameLine();
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||||
ImGui.Separator();
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
|
||||||
if (vramUsage != null)
|
if (vramUsage != null)
|
||||||
{
|
{
|
||||||
var actualVramUsage = vramUsage.Sum(f => f.OriginalSize);
|
var actualVramUsage = vramUsage.Sum(f => f.OriginalSize);
|
||||||
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
|
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
|
||||||
ImGui.SameLine();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(actualVramUsage));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(actualVramUsage));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds
|
if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds
|
||||||
|| _playerPerformanceConfig.Current.ShowPerformanceIndicator)
|
|| _playerPerformanceConfig.Current.ShowPerformanceIndicator)
|
||||||
{
|
{
|
||||||
using var _ = ImRaii.PushIndent(10f);
|
|
||||||
var currentVramWarning = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB;
|
var currentVramWarning = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB;
|
||||||
ImGui.TextUnformatted($"Configured VRAM warning threshold: {currentVramWarning} MiB.");
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted("Configured VRAM threshold:");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"{currentVramWarning} MiB.");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
if (currentVramWarning * 1024 * 1024 < actualVramUsage)
|
if (currentVramWarning * 1024 * 1024 < actualVramUsage)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorText($"You exceed your own threshold by " +
|
UiSharedService.ColorText(
|
||||||
$"{UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}.",
|
$"You exceed your own threshold by {UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}",
|
||||||
UIColors.Get("LightlessYellow"));
|
UIColors.Get("LightlessYellow"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualTriCount = kvp.Value.Sum(f => f.Value.Triangles);
|
var actualTriCount = kvp.Value.Sum(f => f.Value.Triangles);
|
||||||
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {actualTriCount}");
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"{kvp.Key} modded model triangles:");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted(actualTriCount.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds
|
if (_playerPerformanceConfig.Current.WarnOnExceedingThresholds
|
||||||
|| _playerPerformanceConfig.Current.ShowPerformanceIndicator)
|
|| _playerPerformanceConfig.Current.ShowPerformanceIndicator)
|
||||||
{
|
{
|
||||||
using var _ = ImRaii.PushIndent(10f);
|
|
||||||
var currentTriWarning = _playerPerformanceConfig.Current.TrisWarningThresholdThousands;
|
var currentTriWarning = _playerPerformanceConfig.Current.TrisWarningThresholdThousands;
|
||||||
ImGui.TextUnformatted($"Configured triangle warning threshold: {currentTriWarning * 1000} triangles.");
|
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted("Configured triangle threshold:");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.TextUnformatted($"{currentTriWarning * 1000} triangles.");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
if (currentTriWarning * 1000 < actualTriCount)
|
if (currentTriWarning * 1000 < actualTriCount)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorText($"You exceed your own threshold by " +
|
UiSharedService.ColorText(
|
||||||
$"{actualTriCount - (currentTriWarning * 1000)} triangles.",
|
$"You exceed your own threshold by {actualTriCount - (currentTriWarning * 1000)}",
|
||||||
UIColors.Get("LightlessYellow"));
|
UIColors.Get("LightlessYellow"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopStyleVar(2);
|
||||||
|
|
||||||
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
||||||
|
|
||||||
|
_uiSharedService.MediumText("Selected file:", UIColors.Get("LightlessBlue"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
_uiSharedService.MediumText(_selectedHash, UIColors.Get("LightlessYellow"));
|
||||||
|
|
||||||
|
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
|
||||||
|
{
|
||||||
|
var filePaths = item.FilePaths;
|
||||||
|
UiSharedService.ColorText("Local file path:", UIColors.Get("LightlessBlue"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
UiSharedService.TextWrapped(filePaths[0]);
|
||||||
|
if (filePaths.Count > 1)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)");
|
||||||
|
ImGui.SameLine();
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
||||||
|
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
var gamepaths = item.GamePaths;
|
||||||
|
UiSharedService.ColorText("Used by game path:", UIColors.Get("LightlessBlue"));
|
||||||
|
ImGui.SameLine();
|
||||||
|
UiSharedService.TextWrapped(gamepaths[0]);
|
||||||
|
if (gamepaths.Count > 1)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)");
|
||||||
|
ImGui.SameLine();
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
||||||
|
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_selectedObjectTab != kvp.Key)
|
if (_selectedObjectTab != kvp.Key)
|
||||||
{
|
{
|
||||||
_selectedHash = string.Empty;
|
_selectedHash = string.Empty;
|
||||||
@@ -692,41 +766,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
ImGui.TextUnformatted("Selected file:");
|
|
||||||
ImGui.SameLine();
|
|
||||||
UiSharedService.ColorText(_selectedHash, UIColors.Get("LightlessYellow"));
|
|
||||||
|
|
||||||
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
|
|
||||||
{
|
|
||||||
var filePaths = item.FilePaths;
|
|
||||||
ImGui.TextUnformatted("Local file path:");
|
|
||||||
ImGui.SameLine();
|
|
||||||
UiSharedService.TextWrapped(filePaths[0]);
|
|
||||||
if (filePaths.Count > 1)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)");
|
|
||||||
ImGui.SameLine();
|
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
|
||||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
|
|
||||||
}
|
|
||||||
|
|
||||||
var gamepaths = item.GamePaths;
|
|
||||||
ImGui.TextUnformatted("Used by game path:");
|
|
||||||
ImGui.SameLine();
|
|
||||||
UiSharedService.TextWrapped(gamepaths[0]);
|
|
||||||
if (gamepaths.Count > 1)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)");
|
|
||||||
ImGui.SameLine();
|
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.InfoCircle);
|
|
||||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
@@ -855,7 +894,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
var filePath = item.FilePaths[0];
|
var filePath = item.FilePaths[0];
|
||||||
bool toConvert = _texturesToConvert.ContainsKey(filePath);
|
bool toConvert = _texturesToConvert.ContainsKey(filePath);
|
||||||
if (ImGui.Checkbox("###convert" + item.Hash, ref toConvert))
|
if (UiSharedService.CheckboxWithBorder("###convert" + item.Hash, ref toConvert, UIColors.Get("LightlessPurple"), 1.5f))
|
||||||
{
|
{
|
||||||
if (toConvert && !_texturesToConvert.ContainsKey(filePath))
|
if (toConvert && !_texturesToConvert.ContainsKey(filePath))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,14 +4,18 @@ using Dalamud.Interface.Colors;
|
|||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto.User;
|
using LightlessSync.API.Dto.User;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
|
using LightlessSync.UI.Style;
|
||||||
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
namespace LightlessSync.UI;
|
||||||
|
|
||||||
@@ -30,6 +34,16 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
private bool _showFileDialogError = false;
|
private bool _showFileDialogError = false;
|
||||||
private bool _wasOpen;
|
private bool _wasOpen;
|
||||||
|
|
||||||
|
private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
|
||||||
|
private bool vanityInitialized; // useless for now
|
||||||
|
private bool textEnabled;
|
||||||
|
private bool glowEnabled;
|
||||||
|
private Vector4 textColor;
|
||||||
|
private Vector4 glowColor;
|
||||||
|
|
||||||
|
private record VanityState(bool TextEnabled, bool GlowEnabled, Vector4 TextColor, Vector4 GlowColor);
|
||||||
|
private VanityState _savedVanity;
|
||||||
|
|
||||||
public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator,
|
public EditProfileUi(ILogger<EditProfileUi> logger, LightlessMediator mediator,
|
||||||
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
ApiController apiController, UiSharedService uiSharedService, FileDialogManager fileDialogManager,
|
||||||
LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService)
|
LightlessProfileManager lightlessProfileManager, PerformanceCollectorService performanceCollectorService)
|
||||||
@@ -38,8 +52,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
this.SizeConstraints = new()
|
this.SizeConstraints = new()
|
||||||
{
|
{
|
||||||
MinimumSize = new(768, 512),
|
MinimumSize = new(850, 640),
|
||||||
MaximumSize = new(768, 2000)
|
MaximumSize = new(850, 700)
|
||||||
};
|
};
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
@@ -57,14 +71,52 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
_pfpTextureWrap = null;
|
_pfpTextureWrap = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Mediator.Subscribe<ConnectedMessage>(this, msg =>
|
||||||
|
{
|
||||||
|
LoadVanity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadVanity()
|
||||||
|
{
|
||||||
|
textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex);
|
||||||
|
glowEnabled = !string.IsNullOrEmpty(_apiController.TextGlowColorHex);
|
||||||
|
|
||||||
|
textColor = textEnabled ? UIColors.HexToRgba(_apiController.TextColorHex!) : Vector4.One;
|
||||||
|
glowColor = glowEnabled ? UIColors.HexToRgba(_apiController.TextGlowColorHex!) : Vector4.Zero;
|
||||||
|
|
||||||
|
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
|
||||||
|
vanityInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
{
|
{
|
||||||
_uiSharedService.BigText("Current Profile (as saved on server)");
|
_uiSharedService.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow"));
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, 1));
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description.");
|
||||||
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules.");
|
||||||
|
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)");
|
||||||
|
_uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive");
|
||||||
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.");
|
||||||
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.");
|
||||||
|
_uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings.");
|
||||||
|
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(3));
|
||||||
|
|
||||||
var profile = _lightlessProfileManager.GetLightlessProfile(new UserData(_apiController.UID));
|
var profile = _lightlessProfileManager.GetLightlessProfile(new UserData(_apiController.UID));
|
||||||
|
|
||||||
|
if (ImGui.BeginTabBar("##EditProfileTabs"))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginTabItem("Current Profile"))
|
||||||
|
{
|
||||||
|
_uiSharedService.MediumText("Current Profile (as saved on server)", UIColors.Get("LightlessPurple"));
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|
||||||
if (profile.IsFlagged)
|
if (profile.IsFlagged)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed);
|
UiSharedService.ColorTextWrapped(profile.Description, ImGuiColors.DalamudRed);
|
||||||
@@ -119,18 +171,14 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Checkbox("Is NSFW", ref nsfw);
|
ImGui.Checkbox("Is NSFW", ref nsfw);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
ImGui.Separator();
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
_uiSharedService.BigText("Notes and Rules for Profiles");
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.TextWrapped($"- All users that are paired and unpaused with you will be able to see your profile picture and description.{Environment.NewLine}" +
|
if (ImGui.BeginTabItem("Profile Settings"))
|
||||||
$"- Other users have the possibility to report your profile for breaking the rules.{Environment.NewLine}" +
|
{
|
||||||
$"- !!! AVOID: anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.){Environment.NewLine}" +
|
_uiSharedService.MediumText("Profile Settings", UIColors.Get("LightlessPurple"));
|
||||||
$"- !!! AVOID: slurs of any kind in the description that can be considered highly offensive{Environment.NewLine}" +
|
ImGui.Dummy(new Vector2(5));
|
||||||
$"- In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely.{Environment.NewLine}" +
|
|
||||||
$"- Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent.{Environment.NewLine}" +
|
|
||||||
$"- If your profile picture or profile description could be considered NSFW, enable the toggle below.");
|
|
||||||
ImGui.Separator();
|
|
||||||
_uiSharedService.BigText("Profile Settings");
|
|
||||||
|
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.FileUpload, "Upload new profile picture"))
|
||||||
{
|
{
|
||||||
@@ -223,6 +271,120 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, ""));
|
_ = _apiController.UserSetProfile(new UserProfileDto(new UserData(_apiController.UID), Disabled: false, IsNSFW: null, ProfilePictureBase64: null, ""));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Clears your profile description text");
|
UiSharedService.AttachToolTip("Clears your profile description text");
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Vanity Settings"))
|
||||||
|
{
|
||||||
|
_uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple"));
|
||||||
|
ImGui.Dummy(new Vector2(4));
|
||||||
|
_uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings.");
|
||||||
|
|
||||||
|
var hasVanity = _apiController.HasVanity;
|
||||||
|
|
||||||
|
if (!hasVanity)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("You do not currently have vanity access. Become a supporter to unlock these features.", UIColors.Get("DimRed"));
|
||||||
|
ImGui.Dummy(new Vector2(8));
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
_uiSharedService.MediumText("Colored UID", UIColors.Get("LightlessPurple"));
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|
||||||
|
var font = UiBuilder.MonoFont;
|
||||||
|
var playerUID = _apiController.UID;
|
||||||
|
var playerDisplay = _apiController.DisplayName;
|
||||||
|
|
||||||
|
var previewTextColor = textEnabled ? textColor : Vector4.One;
|
||||||
|
var previewGlowColor = glowEnabled ? glowColor : Vector4.Zero;
|
||||||
|
|
||||||
|
var seString = SeStringUtils.BuildFormattedPlayerName(playerDisplay, previewTextColor, previewGlowColor);
|
||||||
|
|
||||||
|
using (ImRaii.PushFont(font))
|
||||||
|
{
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
var textSize = ImGui.CalcTextSize(seString.TextValue);
|
||||||
|
|
||||||
|
float minWidth = 150f * ImGuiHelpers.GlobalScale;
|
||||||
|
float bgWidth = Math.Max(textSize.X + 20f, minWidth);
|
||||||
|
|
||||||
|
float paddingY = 5f * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
var cursor = ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
|
var rectMin = cursor;
|
||||||
|
var rectMax = rectMin + new Vector2(bgWidth, textSize.Y + (paddingY * 2f));
|
||||||
|
|
||||||
|
float boost = Luminance.ComputeHighlight(previewTextColor, previewGlowColor);
|
||||||
|
|
||||||
|
var baseBg = new Vector4(0.15f + boost, 0.15f + boost, 0.15f + boost, 1f);
|
||||||
|
var bgColor = Luminance.BackgroundContrast(previewTextColor, previewGlowColor, baseBg, ref _currentBg);
|
||||||
|
|
||||||
|
var borderColor = UIColors.Get("LightlessPurple");
|
||||||
|
|
||||||
|
drawList.AddRectFilled(rectMin, rectMax, ImGui.GetColorU32(bgColor), 6.0f);
|
||||||
|
drawList.AddRect(rectMin, rectMax, ImGui.GetColorU32(borderColor), 6.0f, ImDrawFlags.None, 1.5f);
|
||||||
|
|
||||||
|
var textPos = new Vector2(
|
||||||
|
rectMin.X + (bgWidth - textSize.X) * 0.5f,
|
||||||
|
rectMin.Y + paddingY
|
||||||
|
);
|
||||||
|
|
||||||
|
SeStringUtils.RenderSeStringWithHitbox(seString, textPos, font);
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
const float colorPickAlign = 90f;
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color");
|
||||||
|
ImGui.SameLine(colorPickAlign);
|
||||||
|
ImGui.Checkbox("##toggleTextColor", ref textEnabled);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(!textEnabled);
|
||||||
|
ImGui.ColorEdit4($"##color_text", ref textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
_uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color");
|
||||||
|
ImGui.SameLine(colorPickAlign);
|
||||||
|
ImGui.Checkbox("##toggleGlowColor", ref glowEnabled);
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(!glowEnabled);
|
||||||
|
ImGui.ColorEdit4($"##color_glow", ref glowColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf);
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
bool changed = !Equals(_savedVanity, new VanityState(textEnabled, glowEnabled, textColor, glowColor));
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Save, "Save Changes"))
|
||||||
|
{
|
||||||
|
string? newText = textEnabled ? UIColors.RgbaToHex(textColor) : string.Empty;
|
||||||
|
string? newGlow = glowEnabled ? UIColors.RgbaToHex(glowColor) : string.Empty;
|
||||||
|
|
||||||
|
_ = _apiController.UserUpdateVanityColors(new UserVanityColorsDto(newText, newGlow));
|
||||||
|
|
||||||
|
_savedVanity = new VanityState(textEnabled, glowEnabled, textColor, glowColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
|
|
||||||
|
if (!hasVanity)
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabBar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using LightlessSync.API.Dto.Group;
|
using LightlessSync.API.Dto.Group;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.PlayerData.Pairs;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Services.ServerConfiguration;
|
using LightlessSync.Services.ServerConfiguration;
|
||||||
|
using LightlessSync.UI.Style;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace LightlessSync.UI.Handlers;
|
namespace LightlessSync.UI.Handlers;
|
||||||
@@ -24,6 +27,9 @@ public class IdDisplayHandler
|
|||||||
private bool _popupShown = false;
|
private bool _popupShown = false;
|
||||||
private DateTime? _popupTime;
|
private DateTime? _popupTime;
|
||||||
|
|
||||||
|
private Vector4 _currentBg = new(0.15f, 0.15f, 0.15f, 1f);
|
||||||
|
private float _highlightBoost;
|
||||||
|
|
||||||
public IdDisplayHandler(LightlessMediator mediator, ServerConfigurationManager serverManager, LightlessConfigService lightlessConfigService)
|
public IdDisplayHandler(LightlessMediator mediator, ServerConfigurationManager serverManager, LightlessConfigService lightlessConfigService)
|
||||||
{
|
{
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
@@ -96,31 +102,102 @@ public class IdDisplayHandler
|
|||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
||||||
var font = UiBuilder.MonoFont;
|
var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
|
||||||
|
|
||||||
var isAdmin = pair.UserData.IsAdmin;
|
Vector4? textColor = null;
|
||||||
var isModerator = pair.UserData.IsModerator;
|
Vector4? glowColor = null;
|
||||||
|
|
||||||
Vector4? textColor = isAdmin
|
if (pair.UserData.HasVanity)
|
||||||
? UIColors.Get("LightlessAdminText")
|
{
|
||||||
: isModerator
|
if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
|
||||||
? UIColors.Get("LightlessModeratorText")
|
{
|
||||||
: null;
|
textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
|
||||||
|
}
|
||||||
|
|
||||||
Vector4? glowColor = isAdmin
|
if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
|
||||||
? UIColors.Get("LightlessAdminGlow")
|
{
|
||||||
: isModerator
|
glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
|
||||||
? UIColors.Get("LightlessModeratorGlow")
|
}
|
||||||
: null;
|
}
|
||||||
|
|
||||||
var seString = (textColor != null || glowColor != null)
|
var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null);
|
||||||
|
var seString = useVanityColors
|
||||||
? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)
|
? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor)
|
||||||
: SeStringUtils.BuildPlain(playerText);
|
: SeStringUtils.BuildPlain(playerText);
|
||||||
|
|
||||||
|
var rowStart = ImGui.GetCursorScreenPos();
|
||||||
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
bool useHighlight = false;
|
||||||
|
float highlightPadX = 0f;
|
||||||
|
float highlightPadY = 0f;
|
||||||
|
|
||||||
|
if (useVanityColors)
|
||||||
|
{
|
||||||
|
float boost = Luminance.ComputeHighlight(textColor, glowColor);
|
||||||
|
|
||||||
|
if (boost > 0f)
|
||||||
|
{
|
||||||
|
var style = ImGui.GetStyle();
|
||||||
|
useHighlight = true;
|
||||||
|
highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale);
|
||||||
|
highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale);
|
||||||
|
drawList.ChannelsSplit(2);
|
||||||
|
drawList.ChannelsSetCurrent(1);
|
||||||
|
|
||||||
|
_highlightBoost = boost;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_highlightBoost = 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 itemMin;
|
||||||
|
Vector2 itemMax;
|
||||||
|
Vector2 textSize;
|
||||||
using (ImRaii.PushFont(font, textIsUid))
|
using (ImRaii.PushFont(font, textIsUid))
|
||||||
{
|
{
|
||||||
var pos = ImGui.GetCursorScreenPos();
|
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font);
|
||||||
SeStringUtils.RenderSeStringWithHitbox(seString, pos, font);
|
itemMin = ImGui.GetItemRectMin();
|
||||||
|
itemMax = ImGui.GetItemRectMax();
|
||||||
|
//textSize = itemMax - itemMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useHighlight)
|
||||||
|
{
|
||||||
|
var style = ImGui.GetStyle();
|
||||||
|
var frameHeight = ImGui.GetFrameHeight();
|
||||||
|
var rowTop = rowStart.Y - style.FramePadding.Y;
|
||||||
|
var rowBottom = rowTop + frameHeight;
|
||||||
|
|
||||||
|
var highlightMin = new Vector2(itemMin.X - highlightPadX, rowTop - highlightPadY);
|
||||||
|
var highlightMax = new Vector2(itemMax.X + highlightPadX, rowBottom + highlightPadY);
|
||||||
|
|
||||||
|
var windowPos = ImGui.GetWindowPos();
|
||||||
|
var contentMin = windowPos + ImGui.GetWindowContentRegionMin();
|
||||||
|
var contentMax = windowPos + ImGui.GetWindowContentRegionMax();
|
||||||
|
highlightMin.X = MathF.Max(highlightMin.X, contentMin.X);
|
||||||
|
highlightMax.X = MathF.Min(highlightMax.X, contentMax.X);
|
||||||
|
highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y);
|
||||||
|
highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y);
|
||||||
|
|
||||||
|
var highlightColor = new Vector4(
|
||||||
|
0.25f + _highlightBoost,
|
||||||
|
0.25f + _highlightBoost,
|
||||||
|
0.25f + _highlightBoost,
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
|
||||||
|
highlightColor = Luminance.BackgroundContrast(textColor, glowColor, highlightColor, ref _currentBg);
|
||||||
|
|
||||||
|
float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale;
|
||||||
|
drawList.ChannelsSetCurrent(0);
|
||||||
|
drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding);
|
||||||
|
|
||||||
|
var borderColor = style.Colors[(int)ImGuiCol.Border];
|
||||||
|
borderColor.W *= 0.25f;
|
||||||
|
drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding);
|
||||||
|
drawList.ChannelsMerge();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public partial class IntroUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UiSharedService.TextWrapped("To not unnecessary download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " +
|
UiSharedService.TextWrapped("To not unnecessarily download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " +
|
||||||
"Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " +
|
"Additionally, a local storage folder must be set where Lightless Sync will download other character files to. " +
|
||||||
"Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
"Once the storage folder is set and the scan complete, this page will automatically forward to registration at a service.");
|
||||||
UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
UiSharedService.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed.");
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ internal class JoinSyncshellUI : WindowMediatorSubscriberBase
|
|||||||
"Joining a Syncshell will pair you implicitly with all existing users in the Syncshell." + Environment.NewLine +
|
"Joining a Syncshell will pair you implicitly with all existing users in the Syncshell." + Environment.NewLine +
|
||||||
"All permissions to all users in the Syncshell will be set to the preferred Syncshell permissions on joining, excluding prior set preferred permissions.");
|
"All permissions to all users in the Syncshell will be set to the preferred Syncshell permissions on joining, excluding prior set preferred permissions.");
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. MSS- is part of Syncshell IDs, unless using Vanity IDs.");
|
ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. LLS- is part of Syncshell IDs, unless using Vanity IDs.");
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("Syncshell ID");
|
ImGui.TextUnformatted("Syncshell ID");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
@@ -978,38 +978,69 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var colorNames = new[]
|
var colorNames = new[]
|
||||||
{
|
{
|
||||||
("LightlessPurple", "Lightless Purple", "Primary colors"),
|
("LightlessPurple", "Primary Purple", "Section titles and dividers"),
|
||||||
("LightlessBlue", "Lightless Blue", "Secondary colors"),
|
("LightlessPurpleActive", "Primary Purple (Active)", "Active tabs and hover highlights"),
|
||||||
("LightlessYellow", "Lightless Yellow", "Warning colors"),
|
("LightlessPurpleDefault", "Primary Purple (Inactive)", "Inactive tabs and default dividers"),
|
||||||
("PairBlue", "Pair Blue", "Pair UI elements"),
|
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
|
||||||
("DimRed", "Dim Red", "Error and offline")
|
|
||||||
|
("LightlessGreen", "Success Green", "Join buttons and success messages"),
|
||||||
|
|
||||||
|
("LightlessYellow", "Warning Yellow", "Warning colors"),
|
||||||
|
("LightlessYellow2", "Warning Yellow (Alt)", "Warning colors"),
|
||||||
|
|
||||||
|
("PairBlue", "Syncshell Blue", "Syncshell headers, toggle highlights, and moderator actions"),
|
||||||
|
|
||||||
|
("DimRed", "Error Red", "Error and offline colors")
|
||||||
};
|
};
|
||||||
|
if (ImGui.BeginTable("##ColorTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Color", ImGuiTableColumnFlags.WidthFixed);
|
||||||
|
ImGui.TableSetupColumn("Description", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableSetupColumn("Reset", ImGuiTableColumnFlags.WidthFixed, 40);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var (colorKey, displayName, description) in colorNames)
|
foreach (var (colorKey, displayName, description) in colorNames)
|
||||||
{
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
|
||||||
|
// color column
|
||||||
|
ImGui.TableSetColumnIndex(0);
|
||||||
var currentColor = UIColors.Get(colorKey);
|
var currentColor = UIColors.Get(colorKey);
|
||||||
var colorToEdit = currentColor;
|
var colorToEdit = currentColor;
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
|
||||||
|
|
||||||
if (ImGui.ColorEdit4($"##color_{colorKey}", ref colorToEdit, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf))
|
if (ImGui.ColorEdit4($"##color_{colorKey}", ref colorToEdit, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf))
|
||||||
{
|
{
|
||||||
UIColors.Set(colorKey, colorToEdit);
|
UIColors.Set(colorKey, colorToEdit);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted($"{displayName} - {description}");
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(displayName);
|
||||||
|
|
||||||
if (UIColors.IsCustom(colorKey))
|
// description column
|
||||||
|
ImGui.TableSetColumnIndex(1);
|
||||||
|
ImGui.AlignTextToFramePadding();
|
||||||
|
ImGui.TextUnformatted(description);
|
||||||
|
|
||||||
|
// actions column
|
||||||
|
ImGui.TableSetColumnIndex(2);
|
||||||
|
using var resetId = ImRaii.PushId($"Reset_{colorKey}");
|
||||||
|
var availableWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
var isCustom = UIColors.IsCustom(colorKey);
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(!isCustom))
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Undo, $"Reset {colorKey}"))
|
{
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Undo.ToIconString(), new Vector2(availableWidth, 0)))
|
||||||
{
|
{
|
||||||
UIColors.Reset(colorKey);
|
UIColors.Reset(colorKey);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Reset this color to default");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UiSharedService.AttachToolTip(isCustom ? "Reset this color to default" : "Color is already at default value");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Undo, "Reset All Theme Colors"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Undo, "Reset All Theme Colors"))
|
||||||
@@ -1020,6 +1051,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
|
||||||
ImGui.TextUnformatted("Server Info Bar Colors");
|
ImGui.TextUnformatted("Server Info Bar Colors");
|
||||||
|
|
||||||
if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr))
|
if (ImGui.Checkbox("Color-code the Server Info Bar entry according to status", ref useColorsInDtr))
|
||||||
@@ -1054,12 +1087,16 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
|
||||||
ImGui.TextUnformatted("Nameplate Colors");
|
ImGui.TextUnformatted("Nameplate Colors");
|
||||||
|
|
||||||
var nameColorsEnabled = _configService.Current.IsNameplateColorsEnabled;
|
var nameColorsEnabled = _configService.Current.IsNameplateColorsEnabled;
|
||||||
var nameColors = _configService.Current.NameplateColors;
|
var nameColors = _configService.Current.NameplateColors;
|
||||||
var isFriendOverride = _configService.Current.overrideFriendColor;
|
var isFriendOverride = _configService.Current.overrideFriendColor;
|
||||||
var isPartyOverride = _configService.Current.overridePartyColor;
|
var isPartyOverride = _configService.Current.overridePartyColor;
|
||||||
|
var isFcTagOverride = _configService.Current.overrideFcTagColor;
|
||||||
|
|
||||||
if (ImGui.Checkbox("Override name color of visible paired players", ref nameColorsEnabled))
|
if (ImGui.Checkbox("Override name color of visible paired players", ref nameColorsEnabled))
|
||||||
{
|
{
|
||||||
@@ -1090,14 +1127,35 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_configService.Save();
|
_configService.Save();
|
||||||
_nameplateService.RequestRedraw();
|
_nameplateService.RequestRedraw();
|
||||||
}
|
}
|
||||||
|
if (ImGui.Checkbox("Override FC tag color", ref isFcTagOverride))
|
||||||
|
{
|
||||||
|
_configService.Current.overrideFcTagColor = isFcTagOverride;
|
||||||
|
_configService.Save();
|
||||||
|
_nameplateService.RequestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Checkbox("Use the complete redesign of the UI for Lightless client.", ref useLightlessRedesign))
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
|
||||||
|
ImGui.TextUnformatted("UI Theme");
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Use the redesign of the UI for Lightless client", ref useLightlessRedesign))
|
||||||
{
|
{
|
||||||
_configService.Current.UseLightlessRedesign = useLightlessRedesign;
|
_configService.Current.UseLightlessRedesign = useLightlessRedesign;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var usePairColoredUIDs = _configService.Current.useColoredUIDs;
|
||||||
|
|
||||||
|
if (ImGui.Checkbox("Toggle the colored UID's in pair list", ref usePairColoredUIDs))
|
||||||
|
{
|
||||||
|
_configService.Current.useColoredUIDs = usePairColoredUIDs;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("This changes the vanity colored UID's in pair list.");
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
@@ -1152,12 +1210,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
_uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI.");
|
_uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI.");
|
||||||
|
|
||||||
//if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells))
|
if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells))
|
||||||
//{
|
{
|
||||||
// _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells;
|
_configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells;
|
||||||
// _configService.Save();
|
_configService.Save();
|
||||||
// Mediator.Publish(new RefreshUiMessage());
|
Mediator.Publish(new RefreshUiMessage());
|
||||||
//}
|
}
|
||||||
_uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'.");
|
_uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'.");
|
||||||
|
|
||||||
if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes))
|
if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes))
|
||||||
|
|||||||
64
LightlessSync/UI/Style/Luminance.cs
Normal file
64
LightlessSync/UI/Style/Luminance.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace LightlessSync.UI.Style
|
||||||
|
{
|
||||||
|
internal static class Luminance
|
||||||
|
{
|
||||||
|
public static float BrightnessThreshold { get; set; } = 0.4f;
|
||||||
|
public static float HighlightBoostMax { get; set; } = 0.1f;
|
||||||
|
public static float SmoothFactor { get; set; } = 0.15f;
|
||||||
|
|
||||||
|
private static float Brightness(Vector4 color)
|
||||||
|
=> Math.Max(color.X, Math.Max(color.Y, color.Z));
|
||||||
|
|
||||||
|
public static float ComputeHighlight(Vector4? textColor, Vector4? glowColor)
|
||||||
|
{
|
||||||
|
float brightnessText = textColor.HasValue ? Brightness(textColor.Value) : 1f;
|
||||||
|
float brightnessGlow = glowColor.HasValue ? Brightness(glowColor.Value) : 1f;
|
||||||
|
|
||||||
|
if (brightnessText >= BrightnessThreshold || brightnessGlow >= BrightnessThreshold)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
float deficit = Math.Min(BrightnessThreshold - brightnessText,
|
||||||
|
BrightnessThreshold - brightnessGlow);
|
||||||
|
|
||||||
|
float factor = Math.Clamp(deficit / BrightnessThreshold, 0f, 1f);
|
||||||
|
factor = MathF.Pow(factor, 2.0f);
|
||||||
|
|
||||||
|
return factor * HighlightBoostMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector4 BackgroundContrast(Vector4? textColor, Vector4? glowColor, Vector4 backgroundColor, ref Vector4 currentBg)
|
||||||
|
{
|
||||||
|
if (!textColor.HasValue && !glowColor.HasValue)
|
||||||
|
return backgroundColor;
|
||||||
|
|
||||||
|
float brightnessText = textColor.HasValue ? Brightness(textColor.Value) : 0f;
|
||||||
|
float brightnessGlow = glowColor.HasValue ? Brightness(glowColor.Value) : 0f;
|
||||||
|
|
||||||
|
float fgBrightness = Math.Max(brightnessText, brightnessGlow);
|
||||||
|
float bgBrightness = Brightness(backgroundColor);
|
||||||
|
float diff = Math.Abs(bgBrightness - fgBrightness);
|
||||||
|
|
||||||
|
bool shouldBeDark = fgBrightness > 0.5f;
|
||||||
|
Vector4 targetBg;
|
||||||
|
|
||||||
|
if (diff >= BrightnessThreshold)
|
||||||
|
{
|
||||||
|
targetBg = backgroundColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetBg = shouldBeDark
|
||||||
|
? new Vector4(0.05f, 0.05f, 0.05f, backgroundColor.W)
|
||||||
|
: new Vector4(0.95f, 0.95f, 0.95f, backgroundColor.W);
|
||||||
|
}
|
||||||
|
|
||||||
|
float t = Math.Clamp(SmoothFactor, 0f, 1f);
|
||||||
|
currentBg = t <= 0f ? targetBg : Vector4.Lerp(currentBg, targetBg, t);
|
||||||
|
|
||||||
|
return currentBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -336,7 +336,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("LightlessPurple")))
|
if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("DimRed")))
|
||||||
{
|
{
|
||||||
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
|
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
|
||||||
{
|
{
|
||||||
@@ -348,6 +348,18 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell."
|
UiSharedService.AttachToolTip("This will remove all non-pinned, non-moderator users from the Syncshell."
|
||||||
+ UiSharedService.TooltipSeparator + "Hold CTRL to enable this button");
|
+ UiSharedService.TooltipSeparator + "Hold CTRL to enable this button");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
|
||||||
|
{
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Brush, "Clear Lightfinder Users"))
|
||||||
|
{
|
||||||
|
_ = _apiController.GroupClearFinder(new(GroupFullInfo.Group));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("This will remove all users that joined through Lightfinder from the Syncshell."
|
||||||
|
+ UiSharedService.TooltipSeparator + "Hold CTRL to enable this button");
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(2f);
|
ImGuiHelpers.ScaledDummy(2f);
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGuiHelpers.ScaledDummy(2f);
|
ImGuiHelpers.ScaledDummy(2f);
|
||||||
@@ -410,12 +422,12 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed.");
|
UiSharedService.TextWrapped($"Syncshell was pruned and {_pruneTask.Result} inactive user(s) have been removed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("DimRed"), 1.5f);
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessPurple")))
|
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessYellow")))
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
|
||||||
{
|
{
|
||||||
@@ -456,7 +468,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
|
using LightlessSync.API.Data.Extensions;
|
||||||
using LightlessSync.API.Dto;
|
using LightlessSync.API.Dto;
|
||||||
using LightlessSync.API.Dto.Group;
|
using LightlessSync.API.Dto.Group;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using LightlessSync.API.Data.Extensions;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
namespace LightlessSync.UI;
|
||||||
@@ -20,12 +20,14 @@ namespace LightlessSync.UI;
|
|||||||
public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
private readonly LightlessConfigService _configService;
|
|
||||||
private readonly BroadcastService _broadcastService;
|
private readonly BroadcastService _broadcastService;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly BroadcastScannerService _broadcastScannerService;
|
private readonly BroadcastScannerService _broadcastScannerService;
|
||||||
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
|
|
||||||
private readonly List<GroupJoinDto> _nearbySyncshells = new();
|
private readonly List<GroupJoinDto> _nearbySyncshells = [];
|
||||||
|
private List<GroupFullInfoDto> _currentSyncshells = [];
|
||||||
private int _selectedNearbyIndex = -1;
|
private int _selectedNearbyIndex = -1;
|
||||||
|
|
||||||
private GroupJoinDto? _joinDto;
|
private GroupJoinDto? _joinDto;
|
||||||
@@ -37,17 +39,18 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
LightlessMediator mediator,
|
LightlessMediator mediator,
|
||||||
PerformanceCollectorService performanceCollectorService,
|
PerformanceCollectorService performanceCollectorService,
|
||||||
BroadcastService broadcastService,
|
BroadcastService broadcastService,
|
||||||
LightlessConfigService configService,
|
|
||||||
UiSharedService uiShared,
|
UiSharedService uiShared,
|
||||||
ApiController apiController,
|
ApiController apiController,
|
||||||
BroadcastScannerService broadcastScannerService
|
BroadcastScannerService broadcastScannerService,
|
||||||
) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
|
PairManager pairManager,
|
||||||
|
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_broadcastService = broadcastService;
|
_broadcastService = broadcastService;
|
||||||
_uiSharedService = uiShared;
|
_uiSharedService = uiShared;
|
||||||
_configService = configService;
|
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_broadcastScannerService = broadcastScannerService;
|
_broadcastScannerService = broadcastScannerService;
|
||||||
|
_pairManager = pairManager;
|
||||||
|
_dalamudUtilService = dalamudUtilService;
|
||||||
|
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
SizeConstraints = new()
|
SizeConstraints = new()
|
||||||
@@ -56,14 +59,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
MaximumSize = new(600, 550)
|
MaximumSize = new(600, 550)
|
||||||
};
|
};
|
||||||
|
|
||||||
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync());
|
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
|
||||||
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync());
|
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnOpen()
|
public override async void OnOpen()
|
||||||
{
|
{
|
||||||
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
|
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
|
||||||
await RefreshSyncshellsAsync();
|
await RefreshSyncshellsAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
@@ -100,22 +103,45 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrawSyncshellTable();
|
||||||
|
|
||||||
|
if (_joinDto != null && _joinInfo != null && _joinInfo.Success)
|
||||||
|
DrawConfirmation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSyncshellTable()
|
||||||
|
{
|
||||||
if (ImGui.BeginTable("##NearbySyncshellsTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
|
if (ImGui.BeginTable("##NearbySyncshellsTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Syncshell", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("GID", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Broadcaster", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
|
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
for (int i = 0; i < _nearbySyncshells.Count; i++)
|
foreach (var shell in _nearbySyncshells)
|
||||||
{
|
{
|
||||||
var shell = _nearbySyncshells[i];
|
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(shell.Group.Alias ?? "(No Alias)");
|
|
||||||
|
var displayName = !string.IsNullOrEmpty(shell.Group.Alias) ? shell.Group.Alias : shell.Group.GID;
|
||||||
|
ImGui.TextUnformatted(displayName);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(shell.Group.GID);
|
var broadcasterName = "Unknown";
|
||||||
|
var broadcast = _broadcastScannerService.GetActiveSyncshellBroadcasts()
|
||||||
|
.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
if (broadcast != null)
|
||||||
|
{
|
||||||
|
var (Name, Address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID);
|
||||||
|
if (!string.IsNullOrEmpty(Name))
|
||||||
|
{
|
||||||
|
var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(Address);
|
||||||
|
broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{Name} ({worldName})" : Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.TextUnformatted(broadcasterName);
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
||||||
var label = $"Join##{shell.Group.GID}";
|
var label = $"Join##{shell.Group.GID}";
|
||||||
@@ -123,6 +149,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
|
||||||
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
|
||||||
|
|
||||||
|
if (!_currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal)))
|
||||||
|
{
|
||||||
if (ImGui.Button(label))
|
if (ImGui.Button(label))
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
|
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
|
||||||
@@ -156,19 +184,25 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (ImRaii.Disabled())
|
||||||
|
{
|
||||||
|
ImGui.Button(label);
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Already a member or owner of this Syncshell.");
|
||||||
|
}
|
||||||
ImGui.PopStyleColor(3);
|
ImGui.PopStyleColor(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_joinDto != null && _joinInfo != null && _joinInfo.Success)
|
|
||||||
DrawConfirmation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawConfirmation()
|
private void DrawConfirmation()
|
||||||
|
{
|
||||||
|
if (_joinDto != null && _joinInfo != null)
|
||||||
{
|
{
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}");
|
ImGui.TextUnformatted($"Join Syncshell: {_joinDto.Group.AliasOrGID} by {_joinInfo.OwnerAliasOrUID}");
|
||||||
@@ -192,6 +226,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
|
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
|
||||||
_joinDto = null;
|
_joinDto = null;
|
||||||
_joinInfo = null;
|
_joinInfo = null;
|
||||||
|
_ = RefreshSyncshellsAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +260,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
private async Task RefreshSyncshellsAsync()
|
private async Task RefreshSyncshellsAsync()
|
||||||
{
|
{
|
||||||
var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
|
var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
|
||||||
|
_currentSyncshells = [.. _pairManager.GroupPairs.Select(g => g.Key)];
|
||||||
|
|
||||||
if (syncshellBroadcasts.Count == 0)
|
if (syncshellBroadcasts.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -231,11 +268,11 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GroupJoinDto> updatedList;
|
List<GroupJoinDto>? updatedList = [];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts);
|
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false);
|
||||||
updatedList = groups?.ToList() ?? new();
|
updatedList = groups?.ToList();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -243,8 +280,11 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet();
|
var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal);
|
||||||
var newGids = updatedList.Select(s => s.Group.GID).ToHashSet();
|
|
||||||
|
if (updatedList != null)
|
||||||
|
{
|
||||||
|
var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal);
|
||||||
|
|
||||||
if (currentGids.SetEquals(newGids))
|
if (currentGids.SetEquals(newGids))
|
||||||
return;
|
return;
|
||||||
@@ -256,13 +296,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (previousGid != null)
|
if (previousGid != null)
|
||||||
{
|
{
|
||||||
var newIndex = _nearbySyncshells.FindIndex(s => s.Group.GID == previousGid);
|
var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal));
|
||||||
if (newIndex >= 0)
|
if (newIndex >= 0)
|
||||||
{
|
{
|
||||||
_selectedNearbyIndex = newIndex;
|
_selectedNearbyIndex = newIndex;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
}
|
}
|
||||||
@@ -291,8 +332,4 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
|
return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,18 @@ using Dalamud.Interface.Utility.Raii;
|
|||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
using LightlessSync.API.Data.Extensions;
|
using LightlessSync.API.Data.Extensions;
|
||||||
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.PlayerData.Pairs;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
namespace LightlessSync.UI;
|
||||||
|
|
||||||
@@ -19,18 +27,25 @@ public class TopTabMenu
|
|||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
|
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly PairRequestService _pairRequestService;
|
||||||
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
|
private readonly HashSet<string> _pendingPairRequestActions = new(StringComparer.Ordinal);
|
||||||
|
private bool _pairRequestsExpanded; // useless for now
|
||||||
|
private int _lastRequestCount;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private string _filter = string.Empty;
|
private string _filter = string.Empty;
|
||||||
private int _globalControlCountdown = 0;
|
private int _globalControlCountdown = 0;
|
||||||
|
private float _pairRequestsHeight = 150f;
|
||||||
private string _pairToAdd = string.Empty;
|
private string _pairToAdd = string.Empty;
|
||||||
|
|
||||||
private SelectedTab _selectedTab = SelectedTab.None;
|
private SelectedTab _selectedTab = SelectedTab.None;
|
||||||
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService)
|
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService)
|
||||||
{
|
{
|
||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
|
_pairRequestService = pairRequestService;
|
||||||
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +55,8 @@ public class TopTabMenu
|
|||||||
Individual,
|
Individual,
|
||||||
Syncshell,
|
Syncshell,
|
||||||
Lightfinder,
|
Lightfinder,
|
||||||
UserConfig
|
UserConfig,
|
||||||
|
Settings
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Filter
|
public string Filter
|
||||||
@@ -67,7 +83,7 @@ public class TopTabMenu
|
|||||||
{
|
{
|
||||||
var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
var availableWidth = ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
||||||
var spacing = ImGui.GetStyle().ItemSpacing;
|
var spacing = ImGui.GetStyle().ItemSpacing;
|
||||||
var buttonX = (availableWidth - (spacing.X * 3)) / 4f;
|
var buttonX = (availableWidth - (spacing.X * 4)) / 5f;
|
||||||
var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y;
|
var buttonY = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Pause).Y;
|
||||||
var buttonSize = new Vector2(buttonX, buttonY);
|
var buttonSize = new Vector2(buttonX, buttonY);
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
@@ -144,6 +160,18 @@ public class TopTabMenu
|
|||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Your User Menu");
|
UiSharedService.AttachToolTip("Your User Menu");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
var x = ImGui.GetCursorScreenPos();
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Cog.ToIconString(), buttonSize))
|
||||||
|
{
|
||||||
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Open Lightless Settings");
|
||||||
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
btncolor.Dispose();
|
btncolor.Dispose();
|
||||||
|
|
||||||
@@ -169,6 +197,22 @@ public class TopTabMenu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
if (TabSelection != SelectedTab.None) ImGuiHelpers.ScaledDummy(3f);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (ImGui.Button("Add Test Pair Request"))
|
||||||
|
{
|
||||||
|
var fakeCid = Guid.NewGuid().ToString("N");
|
||||||
|
var display = _pairRequestService.RegisterIncomingRequest(fakeCid, "Debug pair request");
|
||||||
|
_lightlessMediator.Publish(new NotificationMessage(
|
||||||
|
"Pair request received (debug)",
|
||||||
|
display.Message,
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DrawIncomingPairRequests(availableWidth);
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
DrawFilter(availableWidth, spacing.X);
|
DrawFilter(availableWidth, spacing.X);
|
||||||
@@ -192,6 +236,207 @@ public class TopTabMenu
|
|||||||
UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd));
|
UiSharedService.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawIncomingPairRequests(float availableWidth)
|
||||||
|
{
|
||||||
|
var requests = _pairRequestService.GetActiveRequests();
|
||||||
|
var count = requests.Count;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
_pairRequestsExpanded = false;
|
||||||
|
_lastRequestCount = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > _lastRequestCount)
|
||||||
|
{
|
||||||
|
_pairRequestsExpanded = true;
|
||||||
|
}
|
||||||
|
_lastRequestCount = count;
|
||||||
|
|
||||||
|
var label = $"Incoming Pair Requests - {count}##IncomingPairRequestsHeader";
|
||||||
|
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Header, UIColors.Get("LightlessPurple")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, UIColors.Get("LightlessPurpleActive")))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.HeaderActive, UIColors.Get("LightlessPurple")))
|
||||||
|
{
|
||||||
|
bool open = ImGui.CollapsingHeader(label, ImGuiTreeNodeFlags.DefaultOpen);
|
||||||
|
_pairRequestsExpanded = open;
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
UiSharedService.AttachToolTip("Expand to view incoming pair requests.");
|
||||||
|
|
||||||
|
if (open)
|
||||||
|
{
|
||||||
|
var lineHeight = ImGui.GetTextLineHeightWithSpacing();
|
||||||
|
//var desiredHeight = Math.Clamp(count * lineHeight * 2f, 130f * ImGuiHelpers.GlobalScale, 185f * ImGuiHelpers.GlobalScale); we use resize bar instead
|
||||||
|
|
||||||
|
ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 2f);
|
||||||
|
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ChildBg, UIColors.Get("LightlessPurple")))
|
||||||
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 6f))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginChild("##IncomingPairRequestsOuter", new Vector2(availableWidth + 5f, _pairRequestsHeight), true))
|
||||||
|
{
|
||||||
|
var defaultChildBg = ImGui.GetStyle().Colors[(int)ImGuiCol.WindowBg];
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.ChildBg, defaultChildBg))
|
||||||
|
using (ImRaii.PushStyle(ImGuiStyleVar.ChildRounding, 5f))
|
||||||
|
using (ImRaii.PushStyle(ImGuiStyleVar.WindowPadding, new Vector2(6, 6)))
|
||||||
|
{
|
||||||
|
if (ImGui.BeginChild("##IncomingPairRequestsInner", new Vector2(0, 0), true))
|
||||||
|
{
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.TableBorderStrong, ImGui.GetStyle().Colors[(int)ImGuiCol.Border]))
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.TableBorderLight, ImGui.GetStyle().Colors[(int)ImGuiCol.Border]))
|
||||||
|
{
|
||||||
|
DrawPairRequestList(requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("ButtonDefault"));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessPurple"));
|
||||||
|
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessPurpleActive"));
|
||||||
|
ImGui.Button("##resizeHandle", new Vector2(availableWidth, 4f));
|
||||||
|
ImGui.PopStyleColor(3);
|
||||||
|
|
||||||
|
if (ImGui.IsItemActive())
|
||||||
|
{
|
||||||
|
_pairRequestsHeight += ImGui.GetIO().MouseDelta.Y;
|
||||||
|
_pairRequestsHeight = Math.Clamp(_pairRequestsHeight, 100f, 300f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPairRequestList(IReadOnlyList<PairRequestService.PairRequestDisplay> requests)
|
||||||
|
{
|
||||||
|
float playerColWidth = 207f * ImGuiHelpers.GlobalScale;
|
||||||
|
float receivedColWidth = 73f * ImGuiHelpers.GlobalScale;
|
||||||
|
float actionsColWidth = 50f * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted("Player");
|
||||||
|
ImGui.SameLine(playerColWidth + 2f);
|
||||||
|
ImGui.TextUnformatted("Received");
|
||||||
|
ImGui.SameLine(playerColWidth + receivedColWidth + 12f);
|
||||||
|
ImGui.TextUnformatted("Actions");
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
foreach (var request in requests)
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
|
||||||
|
var label = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(label.Truncate(26));
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
ImGui.TextUnformatted(label);
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine(playerColWidth);
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(GetRelativeTime(request.ReceivedAt));
|
||||||
|
ImGui.SameLine(playerColWidth + receivedColWidth);
|
||||||
|
|
||||||
|
DrawPairRequestActions(request);
|
||||||
|
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPairRequestActions(PairRequestService.PairRequestDisplay request)
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(request.HashedCid);
|
||||||
|
var label = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
||||||
|
var inFlight = _pendingPairRequestActions.Contains(request.HashedCid);
|
||||||
|
using (ImRaii.Disabled(inFlight))
|
||||||
|
{
|
||||||
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
|
||||||
|
{
|
||||||
|
_ = AcceptPairRequestAsync(request);
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Accept request");
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
|
||||||
|
{
|
||||||
|
RejectPairRequest(request.HashedCid, label);
|
||||||
|
}
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
ImGui.SetTooltip("Decline request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetRelativeTime(DateTime receivedAt)
|
||||||
|
{
|
||||||
|
var delta = DateTime.UtcNow - receivedAt;
|
||||||
|
if (delta <= TimeSpan.FromSeconds(10))
|
||||||
|
{
|
||||||
|
return "Just now";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.TotalMinutes >= 1)
|
||||||
|
{
|
||||||
|
return $"{Math.Floor(delta.TotalMinutes)}m {delta.Seconds:D2}s ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"{delta.Seconds}s ago";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AcceptPairRequestAsync(PairRequestService.PairRequestDisplay request)
|
||||||
|
{
|
||||||
|
if (!_pendingPairRequestActions.Add(request.HashedCid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var myCidHash = (await _dalamudUtilService.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||||
|
await _apiController.TryPairWithContentId(request.HashedCid, myCidHash).ConfigureAwait(false);
|
||||||
|
_pairRequestService.RemoveRequest(request.HashedCid);
|
||||||
|
|
||||||
|
var display = string.IsNullOrEmpty(request.DisplayName) ? request.HashedCid : request.DisplayName;
|
||||||
|
_lightlessMediator.Publish(new NotificationMessage(
|
||||||
|
"Pair request accepted",
|
||||||
|
$"Sent a pair request back to {display}.",
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(3)));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_lightlessMediator.Publish(new NotificationMessage(
|
||||||
|
"Failed to accept pair request",
|
||||||
|
ex.Message,
|
||||||
|
NotificationType.Error,
|
||||||
|
TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pendingPairRequestActions.Remove(request.HashedCid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RejectPairRequest(string hashedCid, string playerName)
|
||||||
|
{
|
||||||
|
if (!_pairRequestService.RemoveRequest(hashedCid))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lightlessMediator.Publish(new NotificationMessage("Pair request declined", "Declined " + playerName + "'s pending pair request.",
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(3)));
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawFilter(float availableWidth, float spacingX)
|
private void DrawFilter(float availableWidth, float spacingX)
|
||||||
{
|
{
|
||||||
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Ban, "Clear");
|
var buttonSize = _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.Ban, "Clear");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.GameFonts;
|
using Dalamud.Interface.GameFonts;
|
||||||
@@ -7,6 +7,7 @@ using Dalamud.Interface.ManagedFontAtlas;
|
|||||||
using Dalamud.Interface.Textures.TextureWraps;
|
using Dalamud.Interface.Textures.TextureWraps;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
|
using System;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
@@ -173,12 +174,14 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public static string ByteToString(long bytes, bool addSuffix = true)
|
public static string ByteToString(long bytes, bool addSuffix = true)
|
||||||
{
|
{
|
||||||
string[] suffix = ["B", "KiB", "MiB", "GiB", "TiB"];
|
string[] suffix = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
||||||
int i;
|
int i = 0;
|
||||||
double dblSByte = bytes;
|
double dblSByte = bytes;
|
||||||
for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024)
|
|
||||||
|
while (dblSByte >= 1000 && i < suffix.Length - 1)
|
||||||
{
|
{
|
||||||
dblSByte = bytes / 1024.0;
|
dblSByte /= 1000.0;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
||||||
@@ -510,10 +513,71 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ImGui.Dummy(new Vector2(0, thickness * scale));
|
ImGui.Dummy(new Vector2(0, thickness * scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool CheckboxWithBorder(string label, ref bool value, Vector4? borderColor = null, float borderThickness = 1.0f, float rounding = 3.0f)
|
||||||
|
{
|
||||||
|
var pos = ImGui.GetCursorScreenPos();
|
||||||
|
|
||||||
|
bool changed = ImGui.Checkbox(label, ref value);
|
||||||
|
|
||||||
|
var min = pos;
|
||||||
|
var max = ImGui.GetItemRectMax();
|
||||||
|
|
||||||
|
var col = ImGui.GetColorU32(borderColor ?? ImGuiColors.DalamudGrey);
|
||||||
|
ImGui.GetWindowDrawList().AddRect(min, max, col, rounding, ImDrawFlags.None, borderThickness);
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
public void MediumText(string text, Vector4? color = null)
|
public void MediumText(string text, Vector4? color = null)
|
||||||
{
|
{
|
||||||
FontText(text, MediumFont, color);
|
FontText(text, MediumFont, color);
|
||||||
}
|
}
|
||||||
|
public void DrawNoteLine(string icon, Vector4 color, string text)
|
||||||
|
{
|
||||||
|
MediumText(icon, color);
|
||||||
|
var iconHeight = ImGui.GetItemRectSize().Y;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
float textHeight = ImGui.GetTextLineHeight();
|
||||||
|
float offset = (iconHeight - textHeight) * 0.5f;
|
||||||
|
if (offset > 0)
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + offset);
|
||||||
|
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
ImGui.TextWrapped(text);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawNoteLine(string icon, Vector4 color, ReadOnlySpan<SeStringUtils.RichTextEntry> fragments)
|
||||||
|
{
|
||||||
|
if (fragments.Length == 0)
|
||||||
|
{
|
||||||
|
DrawNoteLine(icon, color, string.Empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MediumText(icon, color);
|
||||||
|
var iconHeight = ImGui.GetItemRectSize().Y;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
float textHeight = ImGui.GetTextLineHeight();
|
||||||
|
float offset = (iconHeight - textHeight) * 0.5f;
|
||||||
|
if (offset > 0)
|
||||||
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + offset);
|
||||||
|
|
||||||
|
var wrapWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
var richText = SeStringUtils.BuildRichText(fragments);
|
||||||
|
SeStringUtils.RenderSeStringWrapped(richText, wrapWidth);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawNoteLine(string icon, Vector4 color, params SeStringUtils.RichTextEntry[] fragments)
|
||||||
|
{
|
||||||
|
DrawNoteLine(icon, color, fragments.AsSpan());
|
||||||
|
}
|
||||||
|
|
||||||
public bool MediumTreeNode(string label, Vector4? textColor = null, float lineWidth = 2f, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.SpanAvailWidth)
|
public bool MediumTreeNode(string label, Vector4? textColor = null, float lineWidth = 2f, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.SpanAvailWidth)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer;
|
using Dalamud.Interface.ImGuiSeStringRenderer;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Lumina.Text;
|
||||||
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
|
using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
||||||
|
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||||
|
|
||||||
namespace LightlessSync.Utils;
|
namespace LightlessSync.Utils;
|
||||||
|
|
||||||
public static class SeStringUtils
|
public static class SeStringUtils
|
||||||
{
|
{
|
||||||
public static SeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
|
public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
|
||||||
{
|
{
|
||||||
var b = new SeStringBuilder();
|
var b = new DalamudSeStringBuilder();
|
||||||
|
|
||||||
if (glowColor is Vector4 glow)
|
if (glowColor is Vector4 glow)
|
||||||
b.Add(new GlowPayload(glow));
|
b.Add(new GlowPayload(glow));
|
||||||
@@ -30,14 +36,47 @@ public static class SeStringUtils
|
|||||||
return b.Build();
|
return b.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SeString BuildPlain(string text)
|
public static DalamudSeString BuildPlain(string text)
|
||||||
{
|
{
|
||||||
var b = new SeStringBuilder();
|
var b = new DalamudSeStringBuilder();
|
||||||
b.AddText(text ?? string.Empty);
|
b.AddText(text ?? string.Empty);
|
||||||
return b.Build();
|
return b.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RenderSeString(SeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
public static DalamudSeString BuildRichText(ReadOnlySpan<RichTextEntry> fragments)
|
||||||
|
{
|
||||||
|
var builder = new LuminaSeStringBuilder();
|
||||||
|
|
||||||
|
foreach (var fragment in fragments)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fragment.Text))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var hasColor = fragment.Color.HasValue;
|
||||||
|
Vector4 color = default;
|
||||||
|
if (hasColor)
|
||||||
|
{
|
||||||
|
color = fragment.Color!.Value;
|
||||||
|
builder.PushColorRgba(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragment.Bold)
|
||||||
|
builder.AppendSetBold(true);
|
||||||
|
|
||||||
|
builder.Append(fragment.Text.AsSpan());
|
||||||
|
|
||||||
|
if (fragment.Bold)
|
||||||
|
builder.AppendSetBold(false);
|
||||||
|
|
||||||
|
if (hasColor)
|
||||||
|
builder.PopColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DalamudSeString.Parse(builder.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DalamudSeString BuildRichText(params RichTextEntry[] fragments) => BuildRichText(fragments.AsSpan());
|
||||||
|
public static void RenderSeString(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
||||||
{
|
{
|
||||||
drawList ??= ImGui.GetWindowDrawList();
|
drawList ??= ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
@@ -51,9 +90,36 @@ public static class SeStringUtils
|
|||||||
|
|
||||||
ImGui.SetCursorScreenPos(position);
|
ImGui.SetCursorScreenPos(position);
|
||||||
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
||||||
|
|
||||||
|
var textSize = ImGui.CalcTextSize(seString.TextValue);
|
||||||
|
if (textSize.Y <= 0f)
|
||||||
|
textSize.Y = ImGui.GetTextLineHeight();
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(0f, textSize.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Vector2 RenderSeStringWithHitbox(SeString seString, Vector2 position, ImFontPtr? font = null)
|
public static void RenderSeStringWrapped(DalamudSeString seString, float wrapWidth, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
||||||
|
{
|
||||||
|
drawList ??= ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
|
var drawParams = new SeStringDrawParams
|
||||||
|
{
|
||||||
|
Font = font ?? ImGui.GetFont(),
|
||||||
|
Color = ImGui.GetColorU32(ImGuiCol.Text),
|
||||||
|
WrapWidth = wrapWidth,
|
||||||
|
TargetDrawList = drawList
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams);
|
||||||
|
|
||||||
|
var calcWrapWidth = wrapWidth > 0f ? wrapWidth : -1f;
|
||||||
|
var textSize = ImGui.CalcTextSize(seString.TextValue, wrapWidth: calcWrapWidth);
|
||||||
|
if (textSize.Y <= 0f)
|
||||||
|
textSize.Y = ImGui.GetTextLineHeight();
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(0f, textSize.Y));
|
||||||
|
}
|
||||||
|
public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null)
|
||||||
{
|
{
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
@@ -99,6 +165,8 @@ public static class SeStringUtils
|
|||||||
|
|
||||||
#region Internal Payloads
|
#region Internal Payloads
|
||||||
|
|
||||||
|
public readonly record struct RichTextEntry(string Text, Vector4? Color = null, bool Bold = false);
|
||||||
|
|
||||||
private abstract class AbstractColorPayload : Payload
|
private abstract class AbstractColorPayload : Payload
|
||||||
{
|
{
|
||||||
protected byte Red { get; init; }
|
protected byte Red { get; init; }
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ public partial class ApiController
|
|||||||
await _lightlessHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false);
|
await _lightlessHub!.InvokeAsync(nameof(UserSetProfile), userDescription).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UserUpdateVanityColors(UserVanityColorsDto dto)
|
||||||
|
{
|
||||||
|
if (!IsConnected) return;
|
||||||
|
await _lightlessHub!.InvokeAsync(nameof(UserUpdateVanityColors), dto).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto)
|
public async Task UserUpdateDefaultPermissions(DefaultPermissionsDto defaultPermissionsDto)
|
||||||
{
|
{
|
||||||
CheckConnection();
|
CheckConnection();
|
||||||
|
|||||||
@@ -105,6 +105,21 @@ public partial class ApiController
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Client_ReceiveBroadcastPairRequest(UserPairNotificationDto dto)
|
||||||
|
{
|
||||||
|
if (dto == null)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var request = _pairRequestService.RegisterIncomingRequest(dto.myHashedCid, dto.message ?? string.Empty);
|
||||||
|
|
||||||
|
Mediator.Publish(new NotificationMessage(
|
||||||
|
"Pair request received",
|
||||||
|
request.Message,
|
||||||
|
NotificationType.Info,
|
||||||
|
TimeSpan.FromSeconds(5)));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
public Task Client_UpdateSystemInfo(SystemInfoDto systemInfo)
|
public Task Client_UpdateSystemInfo(SystemInfoDto systemInfo)
|
||||||
{
|
{
|
||||||
SystemInfoDto = systemInfo;
|
SystemInfoDto = systemInfo;
|
||||||
@@ -277,12 +292,25 @@ public partial class ApiController
|
|||||||
_lightlessHub!.On(nameof(Client_GroupSendInfo), act);
|
_lightlessHub!.On(nameof(Client_GroupSendInfo), act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void OnGroupUpdateProfile(Action<GroupProfileDto> act)
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_lightlessHub!.On(nameof(Client_GroupSendProfile), act);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnReceiveServerMessage(Action<MessageSeverity, string> act)
|
public void OnReceiveServerMessage(Action<MessageSeverity, string> act)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
_lightlessHub!.On(nameof(Client_ReceiveServerMessage), act);
|
_lightlessHub!.On(nameof(Client_ReceiveServerMessage), act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnReceiveBroadcastPairRequest(Action<UserPairNotificationDto> act)
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
_lightlessHub!.On(nameof(Client_ReceiveBroadcastPairRequest), act);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnUpdateSystemInfo(Action<SystemInfoDto> act)
|
public void OnUpdateSystemInfo(Action<SystemInfoDto> act)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ public partial class ApiController
|
|||||||
CheckConnection();
|
CheckConnection();
|
||||||
await _lightlessHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
|
await _lightlessHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
public async Task GroupClearFinder(GroupDto group)
|
||||||
|
{
|
||||||
|
CheckConnection();
|
||||||
|
await _lightlessHub!.SendAsync(nameof(GroupClearFinder), group).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<GroupJoinDto> GroupCreate()
|
public async Task<GroupJoinDto> GroupCreate()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly HubFactory _hubFactory;
|
private readonly HubFactory _hubFactory;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
|
private readonly PairRequestService _pairRequestService;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly TokenProvider _tokenProvider;
|
private readonly TokenProvider _tokenProvider;
|
||||||
private readonly LightlessConfigService _lightlessConfigService;
|
private readonly LightlessConfigService _lightlessConfigService;
|
||||||
@@ -42,12 +43,13 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
private CensusUpdateMessage? _lastCensus;
|
private CensusUpdateMessage? _lastCensus;
|
||||||
|
|
||||||
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
|
public ApiController(ILogger<ApiController> logger, HubFactory hubFactory, DalamudUtilService dalamudUtil,
|
||||||
PairManager pairManager, ServerConfigurationManager serverManager, LightlessMediator mediator,
|
PairManager pairManager, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
|
||||||
TokenProvider tokenProvider, LightlessConfigService lightlessConfigService) : base(logger, mediator)
|
TokenProvider tokenProvider, LightlessConfigService lightlessConfigService) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_hubFactory = hubFactory;
|
_hubFactory = hubFactory;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
|
_pairRequestService = pairRequestService;
|
||||||
_serverManager = serverManager;
|
_serverManager = serverManager;
|
||||||
_tokenProvider = tokenProvider;
|
_tokenProvider = tokenProvider;
|
||||||
_lightlessConfigService = lightlessConfigService;
|
_lightlessConfigService = lightlessConfigService;
|
||||||
@@ -77,6 +79,10 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null;
|
public DefaultPermissionsDto? DefaultPermissions => _connectionDto?.DefaultPreferredPermissions ?? null;
|
||||||
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
|
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
|
||||||
|
|
||||||
|
public bool HasVanity => _connectionDto?.HasVanity ?? false;
|
||||||
|
public string TextColorHex => _connectionDto?.TextColorHex ?? string.Empty;
|
||||||
|
public string TextGlowColorHex => _connectionDto?.TextGlowColorHex ?? string.Empty;
|
||||||
|
|
||||||
public bool IsConnected => ServerState == ServerState.Connected;
|
public bool IsConnected => ServerState == ServerState.Connected;
|
||||||
|
|
||||||
public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0));
|
public bool IsCurrentVersion => (Assembly.GetExecutingAssembly().GetName().Version ?? new Version(0, 0, 0, 0)) >= (_connectionDto?.CurrentClientVersion ?? new Version(0, 0, 0, 0));
|
||||||
@@ -424,6 +430,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
Logger.LogDebug("Initializing data");
|
Logger.LogDebug("Initializing data");
|
||||||
OnDownloadReady((guid) => _ = Client_DownloadReady(guid));
|
OnDownloadReady((guid) => _ = Client_DownloadReady(guid));
|
||||||
OnReceiveServerMessage((sev, msg) => _ = Client_ReceiveServerMessage(sev, msg));
|
OnReceiveServerMessage((sev, msg) => _ = Client_ReceiveServerMessage(sev, msg));
|
||||||
|
OnReceiveBroadcastPairRequest(dto => _ = Client_ReceiveBroadcastPairRequest(dto));
|
||||||
OnUpdateSystemInfo((dto) => _ = Client_UpdateSystemInfo(dto));
|
OnUpdateSystemInfo((dto) => _ = Client_UpdateSystemInfo(dto));
|
||||||
|
|
||||||
OnUserSendOffline((dto) => _ = Client_UserSendOffline(dto));
|
OnUserSendOffline((dto) => _ = Client_UserSendOffline(dto));
|
||||||
@@ -445,6 +452,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto));
|
OnGroupPairLeft((dto) => _ = Client_GroupPairLeft(dto));
|
||||||
OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto));
|
OnGroupSendFullInfo((dto) => _ = Client_GroupSendFullInfo(dto));
|
||||||
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
OnGroupSendInfo((dto) => _ = Client_GroupSendInfo(dto));
|
||||||
|
OnGroupUpdateProfile((dto) => _ = Client_GroupSendProfile(dto));
|
||||||
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
|
OnGroupChangeUserPairPermissions((dto) => _ = Client_GroupChangeUserPairPermissions(dto));
|
||||||
|
|
||||||
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
|
OnGposeLobbyJoin((dto) => _ = Client_GposeLobbyJoin(dto));
|
||||||
|
|||||||
Submodule PenumbraAPI updated: dd14131793...648b6fc2ce
Reference in New Issue
Block a user