Compare commits
11 Commits
1.12.1
...
1.12.0-Dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73f130a95a | ||
|
|
7c4269b011 | ||
|
|
b0b149d8bc | ||
|
|
777e6b9d27 | ||
|
|
37c11e9d73 | ||
| e8f8512cdd | |||
| 7569b15993 | |||
| d91f1a3356 | |||
| 0c38b9397a | |||
| 9d850f8fa6 | |||
| 9eb2309018 |
@@ -41,9 +41,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Get version
|
- name: Get version
|
||||||
id: package_version
|
id: package_version
|
||||||
run: |
|
uses: KageKirin/get-csproj-version@v0
|
||||||
version=$(grep -oPm1 "(?<=<Version>)[^<]+" LightlessSync/LightlessSync.csproj)
|
with:
|
||||||
echo "version=$version" >> $GITHUB_OUTPUT
|
file: LightlessSync/LightlessSync.csproj
|
||||||
|
|
||||||
- name: Display version
|
- name: Display version
|
||||||
run: |
|
run: |
|
||||||
@@ -121,11 +121,8 @@ jobs:
|
|||||||
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases"
|
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases"
|
||||||
)
|
)
|
||||||
|
|
||||||
echo "API response: $response"
|
|
||||||
release_id=$(echo "$response" | jq -r .id)
|
release_id=$(echo "$response" | jq -r .id)
|
||||||
echo "release_id=$release_id"
|
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_id=$release_id" >> $GITHUB_OUTPUT || echo "::set-output name=release_id::$release_id"
|
|
||||||
echo "RELEASE_ID=$release_id" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Create Release (dev)
|
- name: Create Release (dev)
|
||||||
if: github.ref == 'refs/heads/dev'
|
if: github.ref == 'refs/heads/dev'
|
||||||
@@ -153,28 +150,15 @@ jobs:
|
|||||||
}' \
|
}' \
|
||||||
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases"
|
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases"
|
||||||
)
|
)
|
||||||
echo "API response: $response"
|
|
||||||
release_id=$(echo "$response" | jq -r .id)
|
release_id=$(echo "$response" | jq -r .id)
|
||||||
echo "release_id=$release_id"
|
echo "release_id=$release_id" >> "$GITHUB_OUTPUT"
|
||||||
echo "release_id=$release_id" >> $GITHUB_OUTPUT || echo "::set-output name=release_id::$release_id"
|
|
||||||
echo "RELEASE_ID=$release_id" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Check asset exists
|
|
||||||
run: |
|
|
||||||
if [ ! -f output/LightlessClient.zip ]; then
|
|
||||||
echo "output/LightlessClient.zip does not exist!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Upload Assets to release
|
- name: Upload Assets to release
|
||||||
env:
|
|
||||||
RELEASE_ID: ${{ env.RELEASE_ID }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "Uploading to release ID: $RELEASE_ID"
|
|
||||||
curl --fail-with-body -s -X POST \
|
curl --fail-with-body -s -X POST \
|
||||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||||
-F "attachment=@output/LightlessClient.zip" \
|
-F "attachment=@output/LightlessClient.zip" \
|
||||||
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases/$RELEASE_ID/assets"
|
"https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases/${{ steps.create_release.outputs.release_id }}/assets"
|
||||||
|
|
||||||
- name: Clone plugin hosting repo
|
- name: Clone plugin hosting repo
|
||||||
run: |
|
run: |
|
||||||
@@ -257,7 +241,6 @@ 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
Normal file
140
.github/workflows/lightless-tag-and-release.yml
vendored
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
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: 6c542c0ccc...aec2a5023e
@@ -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", ".kdb"];
|
public static readonly IImmutableList<string> AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk"];
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using K4os.Compression.LZ4.Legacy;
|
using K4os.Compression.LZ4.Legacy;
|
||||||
using LightlessSync.Interop.Ipc;
|
using LightlessSync.Interop.Ipc;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
@@ -19,8 +19,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
private readonly LightlessConfigService _configService;
|
private readonly LightlessConfigService _configService;
|
||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
private readonly string _csvPath;
|
private readonly string _csvPath;
|
||||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, FileCacheEntity>> _fileCaches = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, List<FileCacheEntity>> _fileCaches = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, FileCacheEntity> _fileCachesByPrefixedPath = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
private readonly SemaphoreSlim _getCachesByPathsSemaphore = new(1, 1);
|
private readonly SemaphoreSlim _getCachesByPathsSemaphore = new(1, 1);
|
||||||
private readonly Lock _fileWriteLock = new();
|
private readonly Lock _fileWriteLock = new();
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
@@ -38,57 +37,6 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
private string CsvBakPath => _csvPath + ".bak";
|
private string CsvBakPath => _csvPath + ".bak";
|
||||||
|
|
||||||
private static string NormalizeSeparators(string path)
|
|
||||||
{
|
|
||||||
return path.Replace("/", "\\", StringComparison.Ordinal)
|
|
||||||
.Replace("\\\\", "\\", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizePrefixedPathKey(string prefixedPath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(prefixedPath))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NormalizeSeparators(prefixedPath).ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string NormalizeToPrefixedPath(string path)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(path)) return string.Empty;
|
|
||||||
|
|
||||||
var normalized = NormalizeSeparators(path);
|
|
||||||
|
|
||||||
if (normalized.StartsWith(CachePrefix, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
normalized.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return NormalizePrefixedPathKey(normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
var penumbraDir = _ipcManager.Penumbra.ModDirectory;
|
|
||||||
if (!string.IsNullOrEmpty(penumbraDir))
|
|
||||||
{
|
|
||||||
var normalizedPenumbra = NormalizeSeparators(penumbraDir);
|
|
||||||
var replacement = normalizedPenumbra.EndsWith("\\", StringComparison.Ordinal)
|
|
||||||
? PenumbraPrefix + "\\"
|
|
||||||
: PenumbraPrefix;
|
|
||||||
normalized = normalized.Replace(normalizedPenumbra, replacement, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheFolder = _configService.Current.CacheFolder;
|
|
||||||
if (!string.IsNullOrEmpty(cacheFolder))
|
|
||||||
{
|
|
||||||
var normalizedCache = NormalizeSeparators(cacheFolder);
|
|
||||||
var replacement = normalizedCache.EndsWith("\\", StringComparison.Ordinal)
|
|
||||||
? CachePrefix + "\\"
|
|
||||||
: CachePrefix;
|
|
||||||
normalized = normalized.Replace(normalizedCache, replacement, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NormalizePrefixedPathKey(normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileCacheEntity? CreateCacheEntry(string path)
|
public FileCacheEntity? CreateCacheEntry(string path)
|
||||||
{
|
{
|
||||||
FileInfo fi = new(path);
|
FileInfo fi = new(path);
|
||||||
@@ -113,26 +61,20 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
return CreateFileCacheEntity(fi, prefixedPath);
|
return CreateFileCacheEntity(fi, prefixedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FileCacheEntity> GetAllFileCaches() => _fileCaches.Values.SelectMany(v => v.Values.Where(e => e != null)).ToList();
|
public List<FileCacheEntity> GetAllFileCaches() => _fileCaches.Values.SelectMany(v => v).ToList();
|
||||||
|
|
||||||
public List<FileCacheEntity> GetAllFileCachesByHash(string hash, bool ignoreCacheEntries = false, bool validate = true)
|
public List<FileCacheEntity> GetAllFileCachesByHash(string hash, bool ignoreCacheEntries = false, bool validate = true)
|
||||||
{
|
{
|
||||||
List<FileCacheEntity> output = [];
|
List<FileCacheEntity> output = [];
|
||||||
if (_fileCaches.TryGetValue(hash, out var fileCacheEntities))
|
if (_fileCaches.TryGetValue(hash, out var fileCacheEntities))
|
||||||
{
|
{
|
||||||
foreach (var fileCache in fileCacheEntities.Values.Where(c => !ignoreCacheEntries || !c.IsCacheEntry).ToList())
|
foreach (var fileCache in fileCacheEntities.Where(c => !ignoreCacheEntries || !c.IsCacheEntry).ToList())
|
||||||
{
|
{
|
||||||
if (!validate)
|
if (!validate) output.Add(fileCache);
|
||||||
{
|
|
||||||
output.Add(fileCache);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var validated = GetValidatedFileCache(fileCache);
|
var validated = GetValidatedFileCache(fileCache);
|
||||||
if (validated != null)
|
if (validated != null) output.Add(validated);
|
||||||
{
|
|
||||||
output.Add(validated);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +86,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
{
|
{
|
||||||
_lightlessMediator.Publish(new HaltScanMessage(nameof(ValidateLocalIntegrity)));
|
_lightlessMediator.Publish(new HaltScanMessage(nameof(ValidateLocalIntegrity)));
|
||||||
_logger.LogInformation("Validating local storage");
|
_logger.LogInformation("Validating local storage");
|
||||||
var cacheEntries = _fileCaches.Values.SelectMany(v => v.Values.Where(e => e != null)).Where(v => v.IsCacheEntry).ToList();
|
var cacheEntries = _fileCaches.SelectMany(v => v.Value).Where(v => v.IsCacheEntry).ToList();
|
||||||
List<FileCacheEntity> brokenEntities = [];
|
List<FileCacheEntity> brokenEntities = [];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var fileCache in cacheEntries)
|
foreach (var fileCache in cacheEntries)
|
||||||
@@ -209,40 +151,29 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
public FileCacheEntity? GetFileCacheByHash(string hash)
|
public FileCacheEntity? GetFileCacheByHash(string hash)
|
||||||
{
|
{
|
||||||
if (_fileCaches.TryGetValue(hash, out var entries))
|
if (_fileCaches.TryGetValue(hash, out var hashes))
|
||||||
{
|
{
|
||||||
var item = entries.Values
|
var item = hashes.OrderBy(p => p.PrefixedFilePath.Contains(PenumbraPrefix, StringComparison.Ordinal) ? 0 : 1).FirstOrDefault();
|
||||||
.OrderBy(p => p.PrefixedFilePath.Contains(PenumbraPrefix, StringComparison.Ordinal) ? 0 : 1)
|
if (item != null) return GetValidatedFileCache(item);
|
||||||
.FirstOrDefault();
|
|
||||||
if (item != null)
|
|
||||||
{
|
|
||||||
return GetValidatedFileCache(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileCacheEntity? GetFileCacheByPath(string path)
|
private FileCacheEntity? GetFileCacheByPath(string path)
|
||||||
{
|
{
|
||||||
var normalizedPrefixedPath = NormalizeToPrefixedPath(path);
|
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant()
|
||||||
if (string.IsNullOrEmpty(normalizedPrefixedPath))
|
.Replace(_ipcManager.Penumbra.ModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var entry = _fileCaches.SelectMany(v => v.Value).FirstOrDefault(f => f.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
{
|
{
|
||||||
return null;
|
_logger.LogDebug("Found no entries for {path}", cleanedPath);
|
||||||
|
return CreateFileEntry(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_fileCachesByPrefixedPath.TryGetValue(normalizedPrefixedPath, out var entry))
|
var validatedCacheEntry = GetValidatedFileCache(entry);
|
||||||
{
|
|
||||||
return GetValidatedFileCache(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug("Found no entries for {path}", normalizedPrefixedPath);
|
return validatedCacheEntry;
|
||||||
|
|
||||||
if (normalizedPrefixedPath.Contains(CachePrefix, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return CreateCacheEntry(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateFileEntry(path) ?? CreateCacheEntry(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, FileCacheEntity?> GetFileCachesByPaths(string[] paths)
|
public Dictionary<string, FileCacheEntity?> GetFileCachesByPaths(string[] paths)
|
||||||
@@ -251,55 +182,66 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, FileCacheEntity?>(StringComparer.OrdinalIgnoreCase);
|
var allEntities = _fileCaches.SelectMany(f => f.Value).ToArray();
|
||||||
var seenNormalized = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
foreach (var originalPath in paths)
|
var cacheDict = new ConcurrentDictionary<string, FileCacheEntity>(
|
||||||
{
|
StringComparer.OrdinalIgnoreCase);
|
||||||
if (string.IsNullOrEmpty(originalPath))
|
|
||||||
{
|
|
||||||
result[originalPath] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalized = NormalizeToPrefixedPath(originalPath);
|
Parallel.ForEach(allEntities, entity =>
|
||||||
if (seenNormalized.Add(normalized))
|
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(normalized))
|
cacheDict[entity.PrefixedFilePath] = entity;
|
||||||
{
|
});
|
||||||
_logger.LogDebug("Normalized path {cleaned}", normalized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(normalized))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Duplicate normalized path detected: {cleaned}", normalized);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_fileCachesByPrefixedPath.TryGetValue(normalized, out var entity))
|
var cleanedPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
{
|
var seenCleaned = new ConcurrentDictionary<string, byte>(StringComparer.OrdinalIgnoreCase);
|
||||||
result[originalPath] = GetValidatedFileCache(entity);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileCacheEntity? created = null;
|
Parallel.ForEach(paths, p =>
|
||||||
|
{
|
||||||
|
var cleaned = p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace(
|
||||||
|
_ipcManager.Penumbra.ModDirectory!,
|
||||||
|
_ipcManager.Penumbra.ModDirectory!.EndsWith('\\')
|
||||||
|
? PenumbraPrefix + '\\' : PenumbraPrefix,
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace(
|
||||||
|
_configService.Current.CacheFolder,
|
||||||
|
_configService.Current.CacheFolder.EndsWith('\\')
|
||||||
|
? CachePrefix + '\\' : CachePrefix,
|
||||||
|
StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||||
|
|
||||||
if (normalized.Contains(CachePrefix, StringComparison.Ordinal))
|
if (seenCleaned.TryAdd(cleaned, 0))
|
||||||
{
|
{
|
||||||
created = CreateCacheEntry(originalPath);
|
_logger.LogDebug("Adding to cleanedPaths: {cleaned}", cleaned);
|
||||||
}
|
cleanedPaths[p] = cleaned;
|
||||||
else if (normalized.Contains(PenumbraPrefix, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
created = CreateFileEntry(originalPath);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
created = CreateFileEntry(originalPath) ?? CreateCacheEntry(originalPath);
|
_logger.LogWarning("Duplicate found: {cleaned}", cleaned);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
result[originalPath] = created;
|
var result = new ConcurrentDictionary<string, FileCacheEntity?>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
Parallel.ForEach(cleanedPaths, entry =>
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Checking if in cache: {path}", entry.Value);
|
||||||
|
|
||||||
|
if (cacheDict.TryGetValue(entry.Value, out var entity))
|
||||||
|
{
|
||||||
|
var validatedCache = GetValidatedFileCache(entity);
|
||||||
|
result[entry.Key] = validatedCache;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!entry.Value.Contains(CachePrefix, StringComparison.Ordinal))
|
||||||
|
result[entry.Key] = CreateFileEntry(entry.Key);
|
||||||
|
else
|
||||||
|
result[entry.Key] = CreateCacheEntry(entry.Key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return result;
|
return new Dictionary<string, FileCacheEntity?>(result, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -309,24 +251,16 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
public void RemoveHashedFile(string hash, string prefixedFilePath)
|
public void RemoveHashedFile(string hash, string prefixedFilePath)
|
||||||
{
|
{
|
||||||
var normalizedPath = NormalizePrefixedPathKey(prefixedFilePath);
|
|
||||||
|
|
||||||
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));
|
||||||
|
_logger.LogTrace("Removed from DB: {count} file(s) with hash {hash} and file cache {path}", removedCount, hash, prefixedFilePath);
|
||||||
|
|
||||||
if (caches.TryRemove(normalizedPath, out var removedEntity))
|
if (caches?.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Removed from DB: {hash} => {path}", hash, removedEntity.PrefixedFilePath);
|
_fileCaches.Remove(hash, out var entity);
|
||||||
}
|
|
||||||
|
|
||||||
if (caches.IsEmpty)
|
|
||||||
{
|
|
||||||
_fileCaches.TryRemove(hash, out _);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fileCachesByPrefixedPath.TryRemove(normalizedPath, out _);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateHashedFile(FileCacheEntity fileCache, bool computeProperties = true)
|
public void UpdateHashedFile(FileCacheEntity fileCache, bool computeProperties = true)
|
||||||
@@ -367,7 +301,7 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
lock (_fileWriteLock)
|
lock (_fileWriteLock)
|
||||||
{
|
{
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
foreach (var entry in _fileCaches.Values.SelectMany(k => k.Values).OrderBy(f => f.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
|
foreach (var entry in _fileCaches.SelectMany(k => k.Value).OrderBy(f => f.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
sb.AppendLine(entry.CsvEntry);
|
sb.AppendLine(entry.CsvEntry);
|
||||||
}
|
}
|
||||||
@@ -412,11 +346,16 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
private void AddHashedFile(FileCacheEntity fileCache)
|
private void AddHashedFile(FileCacheEntity fileCache)
|
||||||
{
|
{
|
||||||
var normalizedPath = NormalizePrefixedPathKey(fileCache.PrefixedFilePath);
|
if (!_fileCaches.TryGetValue(fileCache.Hash, out var entries) || entries is null)
|
||||||
var entries = _fileCaches.GetOrAdd(fileCache.Hash, _ => new ConcurrentDictionary<string, FileCacheEntity>(StringComparer.OrdinalIgnoreCase));
|
{
|
||||||
|
_fileCaches[fileCache.Hash] = entries = [];
|
||||||
|
}
|
||||||
|
|
||||||
entries[normalizedPath] = fileCache;
|
if (!entries.Exists(u => string.Equals(u.PrefixedFilePath, fileCache.PrefixedFilePath, StringComparison.OrdinalIgnoreCase)))
|
||||||
_fileCachesByPrefixedPath[normalizedPath] = fileCache;
|
{
|
||||||
|
//_logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath);
|
||||||
|
entries.Add(fileCache);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
|
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
|
||||||
@@ -458,12 +397,6 @@ 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", "kdb"];
|
private readonly string[] _handledFileTypes = ["tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk"];
|
||||||
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 = [];
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Dalamud.Game.Text;
|
|
||||||
using LightlessSync.UtilsEnum.Enum;
|
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.UI;
|
using LightlessSync.UI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -35,9 +33,6 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
||||||
public bool OpenPopupOnAdd { get; set; } = true;
|
public bool OpenPopupOnAdd { get; set; } = true;
|
||||||
public int ParallelDownloads { get; set; } = 10;
|
public int ParallelDownloads { get; set; } = 10;
|
||||||
public int ParallelUploads { get; set; } = 8;
|
|
||||||
public bool EnablePairProcessingLimiter { get; set; } = true;
|
|
||||||
public int MaxConcurrentPairApplications { get; set; } = 3;
|
|
||||||
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
||||||
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
||||||
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
||||||
@@ -72,18 +67,7 @@ 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 short LightfinderLabelOffsetX { get; set; } = 0;
|
|
||||||
public short LightfinderLabelOffsetY { get; set; } = 0;
|
|
||||||
public bool LightfinderLabelUseIcon { get; set; } = false;
|
|
||||||
public bool LightfinderLabelShowOwn { get; set; } = true;
|
|
||||||
public bool LightfinderLabelShowPaired { get; set; } = true;
|
|
||||||
public string LightfinderLabelIconGlyph { get; set; } = SeIconCharExtensions.ToIconString(SeIconChar.Hyadelyn);
|
|
||||||
public float LightfinderLabelScale { get; set; } = 1.0f;
|
|
||||||
public bool LightfinderAutoAlign { get; set; } = true;
|
|
||||||
public LabelAlignment LabelAlignment { get; set; } = LabelAlignment.Left;
|
|
||||||
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;
|
||||||
public string? SelectedFinderSyncshell { get; set; } = null;
|
public string? SelectedFinderSyncshell { get; set; } = null;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using LightlessSync.LightlessConfiguration.Models;
|
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
|
|
||||||
public class ServerTagConfig : ILightlessConfiguration
|
|
||||||
{
|
|
||||||
public Dictionary<string, ServerTagStorage> ServerTagStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
public int Version { get; set; } = 0;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace LightlessSync.LightlessConfiguration.Models;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class ServerTagStorage
|
|
||||||
{
|
|
||||||
public HashSet<string> OpenPairTags { get; set; } = new(StringComparer.Ordinal);
|
|
||||||
public HashSet<string> ServerAvailablePairTags { get; set; } = new(StringComparer.Ordinal);
|
|
||||||
public Dictionary<string, List<string>> UidServerPairedUserTags { get; set; } = new(StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration;
|
|
||||||
|
|
||||||
public class ServerTagConfigService : ConfigurationServiceBase<ServerTagConfig>
|
|
||||||
{
|
|
||||||
public const string ConfigName = "servertags.json";
|
|
||||||
|
|
||||||
public ServerTagConfigService(string configDir) : base(configDir)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ConfigurationName => ConfigName;
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>1.12.1</Version>
|
<Version>1.12.0</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using LightlessSync.Interop.Ipc;
|
using LightlessSync.Interop.Ipc;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.PlayerData.Pairs;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
@@ -21,7 +21,6 @@ public class PairHandlerFactory
|
|||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly LightlessMediator _lightlessMediator;
|
private readonly LightlessMediator _lightlessMediator;
|
||||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
|
||||||
private readonly ServerConfigurationManager _serverConfigManager;
|
private readonly ServerConfigurationManager _serverConfigManager;
|
||||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@ public class PairHandlerFactory
|
|||||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||||
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
||||||
FileCacheManager fileCacheManager, LightlessMediator lightlessMediator, PlayerPerformanceService playerPerformanceService,
|
FileCacheManager fileCacheManager, LightlessMediator lightlessMediator, PlayerPerformanceService playerPerformanceService,
|
||||||
PairProcessingLimiter pairProcessingLimiter,
|
|
||||||
ServerConfigurationManager serverConfigManager)
|
ServerConfigurationManager serverConfigManager)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
@@ -42,7 +40,6 @@ public class PairHandlerFactory
|
|||||||
_fileCacheManager = fileCacheManager;
|
_fileCacheManager = fileCacheManager;
|
||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
_playerPerformanceService = playerPerformanceService;
|
_playerPerformanceService = playerPerformanceService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
|
||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +47,6 @@ public class PairHandlerFactory
|
|||||||
{
|
{
|
||||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory,
|
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory,
|
||||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
||||||
_fileCacheManager, _lightlessMediator, _playerPerformanceService, _pairProcessingLimiter, _serverConfigManager);
|
_fileCacheManager, _lightlessMediator, _playerPerformanceService, _serverConfigManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using LightlessSync.Interop.Ipc;
|
using LightlessSync.Interop.Ipc;
|
||||||
using LightlessSync.PlayerData.Factories;
|
using LightlessSync.PlayerData.Factories;
|
||||||
@@ -28,7 +28,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly IHostApplicationLifetime _lifetime;
|
private readonly IHostApplicationLifetime _lifetime;
|
||||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
|
||||||
private readonly ServerConfigurationManager _serverConfigManager;
|
private readonly ServerConfigurationManager _serverConfigManager;
|
||||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||||
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
||||||
@@ -51,7 +50,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
|
DalamudUtilService dalamudUtil, IHostApplicationLifetime lifetime,
|
||||||
FileCacheManager fileDbManager, LightlessMediator mediator,
|
FileCacheManager fileDbManager, LightlessMediator mediator,
|
||||||
PlayerPerformanceService playerPerformanceService,
|
PlayerPerformanceService playerPerformanceService,
|
||||||
PairProcessingLimiter pairProcessingLimiter,
|
|
||||||
ServerConfigurationManager serverConfigManager) : base(logger, mediator)
|
ServerConfigurationManager serverConfigManager) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
Pair = pair;
|
Pair = pair;
|
||||||
@@ -63,7 +61,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
_lifetime = lifetime;
|
_lifetime = lifetime;
|
||||||
_fileDbManager = fileDbManager;
|
_fileDbManager = fileDbManager;
|
||||||
_playerPerformanceService = playerPerformanceService;
|
_playerPerformanceService = playerPerformanceService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
|
||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
_penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, Pair.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult();
|
_penumbraCollection = _ipcManager.Penumbra.CreateTemporaryCollectionAsync(logger, Pair.UserData.UID).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
|
||||||
@@ -423,7 +420,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
||||||
bool updateModdedPaths, bool updateManip, CancellationToken downloadToken)
|
bool updateModdedPaths, bool updateManip, CancellationToken downloadToken)
|
||||||
{
|
{
|
||||||
await using var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
|
|
||||||
Dictionary<(string GamePath, string? Hash), string> moddedPaths = [];
|
Dictionary<(string GamePath, string? Hash), string> moddedPaths = [];
|
||||||
|
|
||||||
if (updateModdedPaths)
|
if (updateModdedPaths)
|
||||||
@@ -741,11 +737,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
Logger.LogTrace("[BASE-{appBase}] Modded path calculation cancelled", applicationBase);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "[BASE-{appBase}] Something went wrong during calculation replacements", applicationBase);
|
Logger.LogError(ex, "[BASE-{appBase}] Something went wrong during calculation replacements", applicationBase);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Data.Comparer;
|
using LightlessSync.API.Data.Comparer;
|
||||||
using LightlessSync.API.Data.Extensions;
|
using LightlessSync.API.Data.Extensions;
|
||||||
@@ -7,14 +7,10 @@ using LightlessSync.API.Dto.User;
|
|||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.PlayerData.Factories;
|
using LightlessSync.PlayerData.Factories;
|
||||||
using LightlessSync.Services;
|
|
||||||
|
|
||||||
using LightlessSync.Services.Events;
|
using LightlessSync.Services.Events;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LightlessSync.PlayerData.Pairs;
|
namespace LightlessSync.PlayerData.Pairs;
|
||||||
|
|
||||||
@@ -28,19 +24,14 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
private Lazy<List<Pair>> _directPairsInternal;
|
private Lazy<List<Pair>> _directPairsInternal;
|
||||||
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> _groupPairsInternal;
|
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> _groupPairsInternal;
|
||||||
private Lazy<Dictionary<Pair, List<GroupFullInfoDto>>> _pairsWithGroupsInternal;
|
private Lazy<Dictionary<Pair, List<GroupFullInfoDto>>> _pairsWithGroupsInternal;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
|
||||||
private readonly ConcurrentQueue<(Pair Pair, OnlineUserIdentDto? Ident)> _pairCreationQueue = new();
|
|
||||||
private CancellationTokenSource _pairCreationCts = new();
|
|
||||||
private int _pairCreationProcessorRunning;
|
|
||||||
|
|
||||||
public PairManager(ILogger<PairManager> logger, PairFactory pairFactory,
|
public PairManager(ILogger<PairManager> logger, PairFactory pairFactory,
|
||||||
LightlessConfigService configurationService, LightlessMediator mediator,
|
LightlessConfigService configurationService, LightlessMediator mediator,
|
||||||
IContextMenu dalamudContextMenu, PairProcessingLimiter pairProcessingLimiter) : base(logger, mediator)
|
IContextMenu dalamudContextMenu) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_pairFactory = pairFactory;
|
_pairFactory = pairFactory;
|
||||||
_configurationService = configurationService;
|
_configurationService = configurationService;
|
||||||
_dalamudContextMenu = dalamudContextMenu;
|
_dalamudContextMenu = dalamudContextMenu;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
|
||||||
Mediator.Subscribe<DisconnectedMessage>(this, (_) => ClearPairs());
|
Mediator.Subscribe<DisconnectedMessage>(this, (_) => ClearPairs());
|
||||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => ReapplyPairData());
|
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => ReapplyPairData());
|
||||||
_directPairsInternal = DirectPairsLazy();
|
_directPairsInternal = DirectPairsLazy();
|
||||||
@@ -121,7 +112,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
public void ClearPairs()
|
public void ClearPairs()
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Clearing all Pairs");
|
Logger.LogDebug("Clearing all Pairs");
|
||||||
ResetPairCreationQueue();
|
|
||||||
DisposePairs();
|
DisposePairs();
|
||||||
_allClientPairs.Clear();
|
_allClientPairs.Clear();
|
||||||
_allGroups.Clear();
|
_allGroups.Clear();
|
||||||
@@ -171,7 +161,7 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
Mediator.Publish(new NotificationMessage("User online", msg, NotificationType.Info, TimeSpan.FromSeconds(5)));
|
Mediator.Publish(new NotificationMessage("User online", msg, NotificationType.Info, TimeSpan.FromSeconds(5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QueuePairCreation(pair, dto);
|
pair.CreateCachedPlayer(dto);
|
||||||
|
|
||||||
RecreateLazy();
|
RecreateLazy();
|
||||||
}
|
}
|
||||||
@@ -342,7 +332,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
ResetPairCreationQueue();
|
|
||||||
_dalamudContextMenu.OnMenuOpened -= DalamudContextMenuOnOnOpenGameObjectContextMenu;
|
_dalamudContextMenu.OnMenuOpened -= DalamudContextMenuOnOnOpenGameObjectContextMenu;
|
||||||
|
|
||||||
DisposePairs();
|
DisposePairs();
|
||||||
@@ -401,84 +390,6 @@ public sealed class PairManager : DisposableMediatorSubscriberBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueuePairCreation(Pair pair, OnlineUserIdentDto? dto)
|
|
||||||
{
|
|
||||||
if (pair.HasCachedPlayer)
|
|
||||||
{
|
|
||||||
RecreateLazy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_pairCreationQueue.Enqueue((pair, dto));
|
|
||||||
StartPairCreationProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartPairCreationProcessor()
|
|
||||||
{
|
|
||||||
if (_pairCreationCts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Interlocked.CompareExchange(ref _pairCreationProcessorRunning, 1, 0) == 0)
|
|
||||||
{
|
|
||||||
_ = Task.Run(ProcessPairCreationQueueAsync);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessPairCreationQueueAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (!_pairCreationCts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
if (!_pairCreationQueue.TryDequeue(out var work))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var lease = await _pairProcessingLimiter.AcquireAsync(_pairCreationCts.Token).ConfigureAwait(false);
|
|
||||||
if (!work.Pair.HasCachedPlayer)
|
|
||||||
{
|
|
||||||
work.Pair.CreateCachedPlayer(work.Ident);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error creating cached player for {uid}", work.Pair.UserData.UID);
|
|
||||||
}
|
|
||||||
|
|
||||||
RecreateLazy();
|
|
||||||
await Task.Yield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _pairCreationProcessorRunning, 0);
|
|
||||||
if (!_pairCreationQueue.IsEmpty && !_pairCreationCts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
StartPairCreationProcessor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetPairCreationQueue()
|
|
||||||
{
|
|
||||||
_pairCreationCts.Cancel();
|
|
||||||
while (_pairCreationQueue.TryDequeue(out _))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
_pairCreationCts.Dispose();
|
|
||||||
_pairCreationCts = new CancellationTokenSource();
|
|
||||||
Interlocked.Exchange(ref _pairCreationProcessorRunning, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReapplyPairData()
|
private void ReapplyPairData()
|
||||||
{
|
{
|
||||||
foreach (var pair in _allClientPairs.Select(k => k.Value))
|
foreach (var pair in _allClientPairs.Select(k => k.Value))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
@@ -100,8 +100,6 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase
|
|||||||
if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return;
|
if (_lastCreatedData == null || _usersToPushDataTo.Count == 0) return;
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
forced |= _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash;
|
forced |= _uploadingCharacterData?.DataHash != _lastCreatedData.DataHash;
|
||||||
|
|
||||||
@@ -129,15 +127,6 @@ public class VisibleUserDataDistributor : DisposableMediatorSubscriberBase
|
|||||||
_pushDataSemaphore.Release();
|
_pushDataSemaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (OperationCanceledException) when (_runtimeCts.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("PushCharacterData cancelled");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Failed to push character data");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -106,7 +106,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<GameObjectHandlerFactory>();
|
collection.AddSingleton<GameObjectHandlerFactory>();
|
||||||
collection.AddSingleton<FileDownloadManagerFactory>();
|
collection.AddSingleton<FileDownloadManagerFactory>();
|
||||||
collection.AddSingleton<PairHandlerFactory>();
|
collection.AddSingleton<PairHandlerFactory>();
|
||||||
collection.AddSingleton<PairProcessingLimiter>();
|
|
||||||
collection.AddSingleton<PairFactory>();
|
collection.AddSingleton<PairFactory>();
|
||||||
collection.AddSingleton<XivDataAnalyzer>();
|
collection.AddSingleton<XivDataAnalyzer>();
|
||||||
collection.AddSingleton<CharacterAnalyzer>();
|
collection.AddSingleton<CharacterAnalyzer>();
|
||||||
@@ -114,7 +113,6 @@ 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>();
|
||||||
@@ -145,13 +143,11 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<LightlessConfigService>(),
|
collection.AddSingleton((s) => new DtrEntry(s.GetRequiredService<ILogger<DtrEntry>>(), dtrBar, s.GetRequiredService<LightlessConfigService>(),
|
||||||
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<ServerConfigurationManager>()));
|
s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<ServerConfigurationManager>()));
|
||||||
collection.AddSingleton(s => new PairManager(s.GetRequiredService<ILogger<PairManager>>(), s.GetRequiredService<PairFactory>(),
|
collection.AddSingleton(s => new PairManager(s.GetRequiredService<ILogger<PairManager>>(), s.GetRequiredService<PairFactory>(),
|
||||||
s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<LightlessMediator>(), contextMenu, s.GetRequiredService<PairProcessingLimiter>()));
|
s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<LightlessMediator>(), contextMenu));
|
||||||
collection.AddSingleton<RedrawManager>();
|
collection.AddSingleton<RedrawManager>();
|
||||||
collection.AddSingleton<BroadcastService>();
|
collection.AddSingleton<BroadcastService>();
|
||||||
collection.AddSingleton(addonLifecycle);
|
collection.AddSingleton(addonLifecycle);
|
||||||
collection.AddSingleton(p => new ContextMenuService(contextMenu, pluginInterface, gameData,
|
collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService<ILogger<ContextMenu>>(), p.GetRequiredService<DalamudUtilService>(), p.GetRequiredService<ApiController>(), objectTable));
|
||||||
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,
|
||||||
@@ -208,6 +204,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<ConfigurationMigrator>();
|
collection.AddSingleton<ConfigurationMigrator>();
|
||||||
collection.AddSingleton<ConfigurationSaveService>();
|
collection.AddSingleton<ConfigurationSaveService>();
|
||||||
collection.AddSingleton<HubFactory>();
|
collection.AddSingleton<HubFactory>();
|
||||||
|
collection.AddSingleton<NameplateHandler>();
|
||||||
collection.AddSingleton(s => new BroadcastScannerService( s.GetRequiredService<ILogger<BroadcastScannerService>>(), clientState, objectTable, framework, s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<NameplateHandler>(), s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessConfigService>()));
|
collection.AddSingleton(s => new BroadcastScannerService( s.GetRequiredService<ILogger<BroadcastScannerService>>(), clientState, objectTable, framework, s.GetRequiredService<BroadcastService>(), s.GetRequiredService<LightlessMediator>(), s.GetRequiredService<NameplateHandler>(), s.GetRequiredService<DalamudUtilService>(), s.GetRequiredService<LightlessConfigService>()));
|
||||||
|
|
||||||
|
|
||||||
@@ -233,7 +230,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<UiSharedService>(), s.GetRequiredService<ApiController>(), s.GetRequiredService<BroadcastScannerService>(), s.GetRequiredService<PairManager>(), s.GetRequiredService<DalamudUtilService>()));
|
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<IPopupHandler, BanUserPopupHandler>();
|
collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
||||||
collection.AddScoped<IPopupHandler, CensusPopupHandler>();
|
collection.AddScoped<IPopupHandler, CensusPopupHandler>();
|
||||||
collection.AddScoped<CacheCreationService>();
|
collection.AddScoped<CacheCreationService>();
|
||||||
@@ -252,8 +249,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
s.GetRequiredService<LightlessMediator>()));
|
s.GetRequiredService<LightlessMediator>()));
|
||||||
collection.AddScoped((s) => new NameplateService(s.GetRequiredService<ILogger<NameplateService>>(), s.GetRequiredService<LightlessConfigService>(), namePlateGui, clientState,
|
collection.AddScoped((s) => new NameplateService(s.GetRequiredService<ILogger<NameplateService>>(), s.GetRequiredService<LightlessConfigService>(), namePlateGui, clientState,
|
||||||
s.GetRequiredService<PairManager>(), s.GetRequiredService<LightlessMediator>()));
|
s.GetRequiredService<PairManager>(), s.GetRequiredService<LightlessMediator>()));
|
||||||
collection.AddScoped((s) => new NameplateHandler(s.GetRequiredService<ILogger<NameplateHandler>>(), addonLifecycle, gameGui, s.GetRequiredService<DalamudUtilService>(),
|
|
||||||
s.GetRequiredService<LightlessConfigService>(), s.GetRequiredService<LightlessMediator>(), clientState, s.GetRequiredService<PairManager>()));
|
|
||||||
|
|
||||||
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
collection.AddHostedService(p => p.GetRequiredService<ConfigurationSaveService>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<LightlessMediator>());
|
collection.AddHostedService(p => p.GetRequiredService<LightlessMediator>());
|
||||||
@@ -266,7 +261,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<ContextMenuService>());
|
collection.AddHostedService(p => p.GetRequiredService<ContextMenu>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<BroadcastService>());
|
collection.AddHostedService(p => p.GetRequiredService<BroadcastService>());
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ 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;
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ 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;
|
||||||
@@ -26,37 +29,35 @@ 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);
|
||||||
@@ -64,7 +65,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)
|
||||||
@@ -85,12 +86,13 @@ 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 _apiController.IsUserBroadcasting(dummy).ConfigureAwait(false);
|
await hub.InvokeAsync<BroadcastStatusInfoDto?>("IsUserBroadcasting", dummy, cancellationToken);
|
||||||
await _apiController.SetBroadcastStatus(dummy, true, null).ConfigureAwait(false);
|
await hub.InvokeAsync("SetBroadcastStatus", dummy, true, null, cancellationToken);
|
||||||
await _apiController.GetBroadcastTtl(dummy).ConfigureAwait(false);
|
await hub.InvokeAsync<TimeSpan?>("GetBroadcastTtl", dummy, cancellationToken);
|
||||||
await _apiController.AreUsersBroadcasting([dummy]).ConfigureAwait(false);
|
await hub.InvokeAsync<Dictionary<string, BroadcastStatusInfoDto?>>("AreUsersBroadcasting", new[] { dummy }, cancellationToken);
|
||||||
|
|
||||||
IsLightFinderAvailable = true;
|
IsLightFinderAvailable = true;
|
||||||
_logger.LogInformation("Lightfinder is available.");
|
_logger.LogInformation("Lightfinder is available.");
|
||||||
@@ -117,7 +119,6 @@ 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,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.LogDebug("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);
|
_logger.LogInformation("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);
|
||||||
|
|
||||||
if (!msg.Enabled)
|
if (!msg.Enabled)
|
||||||
{
|
{
|
||||||
@@ -156,7 +157,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
|
|
||||||
_waitingForTtlFetch = true;
|
_waitingForTtlFetch = true;
|
||||||
|
|
||||||
TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
|
var ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);
|
||||||
|
|
||||||
if (ttl is { } remaining && remaining > TimeSpan.Zero)
|
if (ttl is { } remaining && remaining > TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
@@ -164,7 +165,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_config.Current.BroadcastEnabled = true;
|
_config.Current.BroadcastEnabled = true;
|
||||||
_config.Save();
|
_config.Save();
|
||||||
|
|
||||||
_logger.LogDebug("Fetched TTL from server: {TTL}", remaining);
|
_logger.LogInformation("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}")));
|
||||||
}
|
}
|
||||||
@@ -201,13 +202,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("[BroadcastCheck] Checking CID: {cid}", targetCid);
|
_logger.LogInformation("[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.LogDebug("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID);
|
_logger.LogInformation("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -251,7 +252,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
result[kv.Key] = kv.Value;
|
result[kv.Key] = kv.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogTrace("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count);
|
_logger.LogInformation("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -291,10 +292,10 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
if (!newStatus)
|
if (!newStatus)
|
||||||
{
|
{
|
||||||
_lastForcedDisableTime = DateTime.UtcNow;
|
_lastForcedDisableTime = DateTime.UtcNow;
|
||||||
_logger.LogDebug("Manual disable: cooldown timer started.");
|
_logger.LogInformation("Manual disable: cooldown timer started.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
_logger.LogInformation("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);
|
||||||
|
|
||||||
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
_mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
|
||||||
}
|
}
|
||||||
@@ -324,15 +325,15 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
_syncedOnStartup = true;
|
_syncedOnStartup = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
var hashedCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||||
TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
|
var 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.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining);
|
_logger.LogInformation("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -356,12 +357,12 @@ public class BroadcastService : IHostedService, IMediatorSubscriber
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime expiry = _config.Current.BroadcastTtl;
|
var expiry = _config.Current.BroadcastTtl;
|
||||||
TimeSpan remaining = expiry - DateTime.UtcNow;
|
var remaining = expiry - DateTime.UtcNow;
|
||||||
_remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
|
_remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
|
||||||
if (_remainingTtl == null)
|
if (_remainingTtl == null)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
|
_logger.LogInformation("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();
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
"\t /light toggle on|off - Connects or disconnects to Lightless respectively" + Environment.NewLine +
|
"\t /light toggle on|off - Connects or disconnects to Lightless respectively" + Environment.NewLine +
|
||||||
"\t /light gpose - Opens the Lightless Character Data Hub window" + Environment.NewLine +
|
"\t /light gpose - Opens the Lightless Character Data Hub window" + Environment.NewLine +
|
||||||
"\t /light analyze - Opens the Lightless Character Data Analysis window" + Environment.NewLine +
|
"\t /light analyze - Opens the Lightless Character Data Analysis window" + Environment.NewLine +
|
||||||
"\t /light settings - Opens the Lightless Settings window" + Environment.NewLine +
|
"\t /light settings - Opens the Lightless Settings window"
|
||||||
"\t /light lightfinder - Opens the Lightfinder window"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,9 +122,5 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
{
|
{
|
||||||
_mediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
_mediator.Publish(new UiToggleMessage(typeof(SettingsUi)));
|
||||||
}
|
}
|
||||||
else if (string.Equals(splitArgs[0], "lightfinder", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_mediator.Publish(new UiToggleMessage(typeof(BroadcastUI)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
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;
|
|
||||||
private readonly LightlessConfigService _configService;
|
|
||||||
|
|
||||||
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;
|
|
||||||
_configService = configService;
|
|
||||||
_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;
|
|
||||||
|
|
||||||
if (!_configService.Current.EnableRightClickMenus)
|
|
||||||
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,6 +541,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
curWaitTime += tick;
|
curWaitTime += tick;
|
||||||
Thread.Sleep(tick);
|
Thread.Sleep(tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread.Sleep(tick * 2);
|
Thread.Sleep(tick * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,18 +557,6 @@ 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;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto;
|
using LightlessSync.API.Dto;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
using LightlessSync.API.Dto.CharaData;
|
||||||
@@ -77,7 +77,6 @@ public record OpenCensusPopupMessage() : MessageBase;
|
|||||||
public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase;
|
public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase;
|
||||||
public record OpenPermissionWindow(Pair Pair) : MessageBase;
|
public record OpenPermissionWindow(Pair Pair) : MessageBase;
|
||||||
public record DownloadLimitChangedMessage() : SameThreadMessage;
|
public record DownloadLimitChangedMessage() : SameThreadMessage;
|
||||||
public record PairProcessingLimitChangedMessage : SameThreadMessage;
|
|
||||||
public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase;
|
public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase;
|
||||||
public record TargetPairMessage(Pair Pair) : MessageBase;
|
public record TargetPairMessage(Pair Pair) : MessageBase;
|
||||||
public record CombatStartMessage : MessageBase;
|
public record CombatStartMessage : MessageBase;
|
||||||
@@ -101,7 +100,6 @@ 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,22 +1,15 @@
|
|||||||
using Dalamud.Game.Addon.Lifecycle;
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LightlessSync.LightlessConfiguration;
|
|
||||||
using LightlessSync.PlayerData.Pairs;
|
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.UI;
|
using LightlessSync.UI;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.UtilsEnum.Enum;
|
|
||||||
|
|
||||||
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
|
// Created using https://github.com/PunishedPineapple/Distance as a reference, thank you!
|
||||||
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Globalization;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
@@ -25,10 +18,7 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
private readonly ILogger<NameplateHandler> _logger;
|
private readonly ILogger<NameplateHandler> _logger;
|
||||||
private readonly IAddonLifecycle _addonLifecycle;
|
private readonly IAddonLifecycle _addonLifecycle;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly IClientState _clientState;
|
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly LightlessConfigService _configService;
|
|
||||||
private readonly PairManager _pairManager;
|
|
||||||
private readonly LightlessMediator _mediator;
|
private readonly LightlessMediator _mediator;
|
||||||
public LightlessMediator Mediator => _mediator;
|
public LightlessMediator Mediator => _mediator;
|
||||||
|
|
||||||
@@ -36,31 +26,18 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
private bool _needsLabelRefresh = false;
|
private bool _needsLabelRefresh = false;
|
||||||
private AddonNamePlate* mpNameplateAddon = null;
|
private AddonNamePlate* mpNameplateAddon = null;
|
||||||
private readonly AtkTextNode*[] mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
|
private readonly AtkTextNode*[] mTextNodes = new AtkTextNode*[AddonNamePlate.NumNamePlateObjects];
|
||||||
private readonly int[] _cachedNameplateTextWidths = new int[AddonNamePlate.NumNamePlateObjects];
|
|
||||||
private readonly int[] _cachedNameplateTextHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
|
||||||
private readonly int[] _cachedNameplateContainerHeights = new int[AddonNamePlate.NumNamePlateObjects];
|
|
||||||
private readonly int[] _cachedNameplateTextOffsets = new int[AddonNamePlate.NumNamePlateObjects];
|
|
||||||
|
|
||||||
internal const uint mNameplateNodeIDBase = 0x7D99D500;
|
internal const uint mNameplateNodeIDBase = 0x7D99D500;
|
||||||
private const string DefaultLabelText = "LightFinder";
|
|
||||||
private const SeIconChar DefaultIcon = SeIconChar.Hyadelyn;
|
|
||||||
private const int ContainerOffsetX = 50;
|
|
||||||
private static readonly string DefaultIconGlyph = SeIconCharExtensions.ToIconString(DefaultIcon);
|
|
||||||
|
|
||||||
private volatile HashSet<string> _activeBroadcastingCids = [];
|
private volatile HashSet<string> _activeBroadcastingCids = new();
|
||||||
|
|
||||||
public NameplateHandler(ILogger<NameplateHandler> logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessConfigService configService, LightlessMediator mediator, IClientState clientState, PairManager pairManager)
|
public NameplateHandler(ILogger<NameplateHandler> logger, IAddonLifecycle addonLifecycle, IGameGui gameGui, DalamudUtilService dalamudUtil, LightlessMediator mediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_addonLifecycle = addonLifecycle;
|
_addonLifecycle = addonLifecycle;
|
||||||
_gameGui = gameGui;
|
_gameGui = gameGui;
|
||||||
_dalamudUtil = dalamudUtil;
|
_dalamudUtil = dalamudUtil;
|
||||||
_configService = configService;
|
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_clientState = clientState;
|
|
||||||
_pairManager = pairManager;
|
|
||||||
|
|
||||||
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Init()
|
internal void Init()
|
||||||
@@ -119,10 +96,6 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
if (mpNameplateAddon != pNameplateAddon)
|
if (mpNameplateAddon != pNameplateAddon)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < mTextNodes.Length; ++i) mTextNodes[i] = null;
|
for (int i = 0; i < mTextNodes.Length; ++i) mTextNodes[i] = null;
|
||||||
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
|
|
||||||
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
|
||||||
mpNameplateAddon = pNameplateAddon;
|
mpNameplateAddon = pNameplateAddon;
|
||||||
if (mpNameplateAddon != null) CreateNameplateNodes();
|
if (mpNameplateAddon != null) CreateNameplateNodes();
|
||||||
}
|
}
|
||||||
@@ -183,11 +156,6 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
|
|
||||||
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideAllNameplateNodes()
|
private void HideAllNameplateNodes()
|
||||||
@@ -209,7 +177,6 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i)
|
for (int i = 0; i < ui3DModule->NamePlateObjectInfoCount; ++i)
|
||||||
{
|
{
|
||||||
var objectInfo = ui3DModule->NamePlateObjectInfoPointers[i].Value;
|
var objectInfo = ui3DModule->NamePlateObjectInfoPointers[i].Value;
|
||||||
|
|
||||||
if (objectInfo == null || objectInfo->GameObject == null)
|
if (objectInfo == null || objectInfo->GameObject == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -223,202 +190,30 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject);
|
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)objectInfo->GameObject);
|
||||||
|
|
||||||
|
//_logger.LogInformation($"checking cid: {cid}", cid);
|
||||||
|
|
||||||
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
||||||
{
|
{
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
pNode->AtkResNode.ToggleVisibility(false);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_configService.Current.LightfinderLabelShowOwn && (objectInfo->GameObject->GetGameObjectId() == _clientState.LocalPlayer.GameObjectId))
|
pNode->AtkResNode.ToggleVisibility(true);
|
||||||
{
|
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_configService.Current.LightfinderLabelShowPaired && VisibleUserIds.Any(u => u == objectInfo->GameObject->GetGameObjectId()))
|
|
||||||
{
|
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nameplateObject = mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
|
var nameplateObject = mpNameplateAddon->NamePlateObjectArray[nameplateIndex];
|
||||||
nameplateObject.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
|
nameplateObject.RootComponentNode->Component->UldManager.UpdateDrawNodeList();
|
||||||
|
|
||||||
var pNameplateIconNode = nameplateObject.MarkerIcon;
|
|
||||||
var pNameplateResNode = nameplateObject.NameContainer;
|
|
||||||
var pNameplateTextNode = nameplateObject.NameText;
|
|
||||||
bool IsVisible = pNameplateIconNode->AtkResNode.IsVisible() || (pNameplateResNode->IsVisible() && pNameplateTextNode->AtkResNode.IsVisible());
|
|
||||||
pNode->AtkResNode.ToggleVisibility(IsVisible);
|
|
||||||
|
|
||||||
var nameContainer = nameplateObject.NameContainer;
|
var nameContainer = nameplateObject.NameContainer;
|
||||||
var nameText = nameplateObject.NameText;
|
var nameText = nameplateObject.NameText;
|
||||||
|
|
||||||
if (nameContainer == null || nameText == null)
|
|
||||||
{
|
|
||||||
pNode->AtkResNode.ToggleVisibility(false);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var labelColor = UIColors.Get("LightlessPurple");
|
var labelColor = UIColors.Get("LightlessPurple");
|
||||||
var edgeColor = UIColors.Get("FullBlack");
|
var edgeColor = UIColors.Get("FullBlack");
|
||||||
var config = _configService.Current;
|
|
||||||
|
|
||||||
var scaleMultiplier = System.Math.Clamp(config.LightfinderLabelScale, 0.5f, 2.0f);
|
var labelY = nameContainer->Height - nameplateObject.TextH - (int)(24 * nameText->AtkResNode.ScaleY);
|
||||||
var baseScale = config.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
|
||||||
var effectiveScale = baseScale * scaleMultiplier;
|
|
||||||
var labelContent = config.LightfinderLabelUseIcon
|
|
||||||
? NormalizeIconGlyph(config.LightfinderLabelIconGlyph)
|
|
||||||
: DefaultLabelText;
|
|
||||||
|
|
||||||
pNode->FontType = config.LightfinderLabelUseIcon ? FontType.Axis : FontType.MiedingerMed;
|
pNode->AtkResNode.SetPositionShort(58, (short)labelY);
|
||||||
pNode->AtkResNode.SetScale(effectiveScale, effectiveScale);
|
|
||||||
var nodeWidth = (int)pNode->AtkResNode.GetWidth();
|
|
||||||
if (nodeWidth <= 0)
|
|
||||||
nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
|
||||||
var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
|
|
||||||
var baseFontSize = config.LightfinderLabelUseIcon ? 36f : 24f;
|
|
||||||
var computedFontSize = (int)System.Math.Round(baseFontSize * scaleMultiplier);
|
|
||||||
pNode->FontSize = (byte)System.Math.Clamp(computedFontSize, 1, 255);
|
|
||||||
AlignmentType alignment;
|
|
||||||
|
|
||||||
var textScaleY = nameText->AtkResNode.ScaleY;
|
|
||||||
if (textScaleY <= 0f)
|
|
||||||
textScaleY = 1f;
|
|
||||||
|
|
||||||
var blockHeight = System.Math.Abs((int)nameplateObject.TextH);
|
|
||||||
if (blockHeight > 0)
|
|
||||||
{
|
|
||||||
_cachedNameplateTextHeights[nameplateIndex] = blockHeight;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
blockHeight = _cachedNameplateTextHeights[nameplateIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blockHeight <= 0)
|
|
||||||
{
|
|
||||||
blockHeight = GetScaledTextHeight(nameText);
|
|
||||||
if (blockHeight <= 0)
|
|
||||||
blockHeight = nodeHeight;
|
|
||||||
|
|
||||||
_cachedNameplateTextHeights[nameplateIndex] = blockHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
var containerHeight = (int)nameContainer->Height;
|
|
||||||
if (containerHeight > 0)
|
|
||||||
{
|
|
||||||
_cachedNameplateContainerHeights[nameplateIndex] = containerHeight;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
containerHeight = _cachedNameplateContainerHeights[nameplateIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containerHeight <= 0)
|
|
||||||
{
|
|
||||||
containerHeight = blockHeight + (int)System.Math.Round(8 * textScaleY);
|
|
||||||
if (containerHeight <= blockHeight)
|
|
||||||
containerHeight = blockHeight + 1;
|
|
||||||
|
|
||||||
_cachedNameplateContainerHeights[nameplateIndex] = containerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
var blockTop = containerHeight - blockHeight;
|
|
||||||
if (blockTop < 0)
|
|
||||||
blockTop = 0;
|
|
||||||
var verticalPadding = (int)System.Math.Round(4 * effectiveScale);
|
|
||||||
|
|
||||||
var positionY = blockTop - verticalPadding - nodeHeight;
|
|
||||||
|
|
||||||
var textWidth = System.Math.Abs((int)nameplateObject.TextW);
|
|
||||||
if (textWidth <= 0)
|
|
||||||
{
|
|
||||||
textWidth = GetScaledTextWidth(nameText);
|
|
||||||
if (textWidth <= 0)
|
|
||||||
textWidth = nodeWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textWidth > 0)
|
|
||||||
{
|
|
||||||
_cachedNameplateTextWidths[nameplateIndex] = textWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
var textOffset = (int)System.Math.Round(nameText->AtkResNode.X);
|
|
||||||
var hasValidOffset = true;
|
|
||||||
|
|
||||||
if (System.Math.Abs((int)nameplateObject.TextW) > 0 || textOffset != 0)
|
|
||||||
{
|
|
||||||
_cachedNameplateTextOffsets[nameplateIndex] = textOffset;
|
|
||||||
}
|
|
||||||
else if (_cachedNameplateTextOffsets[nameplateIndex] != int.MinValue)
|
|
||||||
{
|
|
||||||
textOffset = _cachedNameplateTextOffsets[nameplateIndex];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
hasValidOffset = false;
|
|
||||||
}
|
|
||||||
int positionX;
|
|
||||||
|
|
||||||
if (config.LightfinderAutoAlign && nameContainer != null && hasValidOffset)
|
|
||||||
{
|
|
||||||
var nameplateWidth = (int)nameContainer->Width;
|
|
||||||
|
|
||||||
if (!config.LightfinderLabelUseIcon)
|
|
||||||
{
|
|
||||||
pNode->TextFlags &= ~TextFlags.AutoAdjustNodeSize;
|
|
||||||
pNode->AtkResNode.Width = 0;
|
|
||||||
pNode->SetText(labelContent);
|
|
||||||
|
|
||||||
nodeWidth = (int)pNode->AtkResNode.GetWidth();
|
|
||||||
if (nodeWidth <= 0)
|
|
||||||
nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
|
||||||
|
|
||||||
if (nodeWidth > nameplateWidth)
|
|
||||||
nodeWidth = nameplateWidth;
|
|
||||||
|
|
||||||
pNode->AtkResNode.Width = (ushort)nodeWidth;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pNode->TextFlags |= TextFlags.AutoAdjustNodeSize;
|
|
||||||
pNode->AtkResNode.Width = 0;
|
|
||||||
pNode->SetText(labelContent);
|
|
||||||
nodeWidth = (int)pNode->AtkResNode.GetWidth();
|
|
||||||
}
|
|
||||||
|
|
||||||
int leftPos = nameplateWidth / 8;
|
|
||||||
int rightPos = nameplateWidth - nodeWidth - (nameplateWidth / 8);
|
|
||||||
int centrePos = (nameplateWidth - nodeWidth) / 2;
|
|
||||||
int staticMargin = 24;
|
|
||||||
int calcMargin = (int)(nameplateWidth * 0.08f);
|
|
||||||
|
|
||||||
switch (config.LabelAlignment)
|
|
||||||
{
|
|
||||||
case LabelAlignment.Left:
|
|
||||||
positionX = config.LightfinderLabelUseIcon ? leftPos + staticMargin : leftPos;
|
|
||||||
alignment = AlignmentType.BottomLeft;
|
|
||||||
break;
|
|
||||||
case LabelAlignment.Right:
|
|
||||||
positionX = config.LightfinderLabelUseIcon ? rightPos - staticMargin : nameplateWidth - nodeWidth + calcMargin;
|
|
||||||
alignment = AlignmentType.BottomRight;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
positionX = config.LightfinderLabelUseIcon ? centrePos : centrePos + calcMargin;
|
|
||||||
alignment = AlignmentType.Bottom;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
positionX = 58 + config.LightfinderLabelOffsetX;
|
|
||||||
alignment = AlignmentType.Bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
positionY += config.LightfinderLabelOffsetY;
|
|
||||||
|
|
||||||
alignment = (AlignmentType)System.Math.Clamp((int)alignment, 0, 8);
|
|
||||||
pNode->AtkResNode.SetUseDepthBasedPriority(true);
|
pNode->AtkResNode.SetUseDepthBasedPriority(true);
|
||||||
|
pNode->AtkResNode.SetScale(0.5f, 0.5f);
|
||||||
|
|
||||||
pNode->AtkResNode.Color.A = 255;
|
pNode->AtkResNode.Color.A = 255;
|
||||||
|
|
||||||
@@ -432,98 +227,18 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
pNode->EdgeColor.B = (byte)(edgeColor.Z * 255);
|
pNode->EdgeColor.B = (byte)(edgeColor.Z * 255);
|
||||||
pNode->EdgeColor.A = (byte)(edgeColor.W * 255);
|
pNode->EdgeColor.A = (byte)(edgeColor.W * 255);
|
||||||
|
|
||||||
|
pNode->FontSize = 24;
|
||||||
if(!config.LightfinderLabelUseIcon)
|
pNode->AlignmentType = AlignmentType.Center;
|
||||||
{
|
pNode->FontType = FontType.MiedingerMed;
|
||||||
pNode->AlignmentType = AlignmentType.Bottom;
|
pNode->LineSpacing = 24;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pNode->AlignmentType = alignment;
|
|
||||||
}
|
|
||||||
pNode->AtkResNode.SetPositionShort(
|
|
||||||
(short)System.Math.Clamp(positionX, short.MinValue, short.MaxValue),
|
|
||||||
(short)System.Math.Clamp(positionY, short.MinValue, short.MaxValue)
|
|
||||||
);
|
|
||||||
var computedLineSpacing = (int)System.Math.Round(24 * scaleMultiplier);
|
|
||||||
pNode->LineSpacing = (byte)System.Math.Clamp(computedLineSpacing, 0, byte.MaxValue);
|
|
||||||
pNode->CharSpacing = 1;
|
pNode->CharSpacing = 1;
|
||||||
pNode->TextFlags = config.LightfinderLabelUseIcon
|
|
||||||
? TextFlags.Edge | TextFlags.Glare | TextFlags.AutoAdjustNodeSize
|
pNode->TextFlags = TextFlags.Edge | TextFlags.Glare;
|
||||||
: TextFlags.Edge | TextFlags.Glare;
|
|
||||||
|
pNode->SetText("Lightfinder");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe int GetScaledTextHeight(AtkTextNode* node)
|
|
||||||
{
|
|
||||||
if (node == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var resNode = &node->AtkResNode;
|
|
||||||
var rawHeight = (int)resNode->GetHeight();
|
|
||||||
if (rawHeight <= 0 && node->LineSpacing > 0)
|
|
||||||
rawHeight = node->LineSpacing;
|
|
||||||
if (rawHeight <= 0)
|
|
||||||
rawHeight = AtkNodeHelpers.DefaultTextNodeHeight;
|
|
||||||
|
|
||||||
var scale = resNode->ScaleY;
|
|
||||||
if (scale <= 0f)
|
|
||||||
scale = 1f;
|
|
||||||
|
|
||||||
var computed = (int)System.Math.Round(rawHeight * scale);
|
|
||||||
return System.Math.Max(1, computed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static unsafe int GetScaledTextWidth(AtkTextNode* node)
|
|
||||||
{
|
|
||||||
if (node == null)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
var resNode = &node->AtkResNode;
|
|
||||||
var rawWidth = (int)resNode->GetWidth();
|
|
||||||
if (rawWidth <= 0)
|
|
||||||
rawWidth = AtkNodeHelpers.DefaultTextNodeWidth;
|
|
||||||
|
|
||||||
var scale = resNode->ScaleX;
|
|
||||||
if (scale <= 0f)
|
|
||||||
scale = 1f;
|
|
||||||
|
|
||||||
var computed = (int)System.Math.Round(rawWidth * scale);
|
|
||||||
return System.Math.Max(1, computed);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string NormalizeIconGlyph(string? rawInput)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(rawInput))
|
|
||||||
return DefaultIconGlyph;
|
|
||||||
|
|
||||||
var trimmed = rawInput.Trim();
|
|
||||||
|
|
||||||
if (Enum.TryParse<SeIconChar>(trimmed, true, out var iconEnum))
|
|
||||||
return SeIconCharExtensions.ToIconString(iconEnum);
|
|
||||||
|
|
||||||
var hexCandidate = trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
|
|
||||||
? trimmed[2..]
|
|
||||||
: trimmed;
|
|
||||||
|
|
||||||
if (ushort.TryParse(hexCandidate, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var hexValue))
|
|
||||||
return char.ConvertFromUtf32(hexValue);
|
|
||||||
|
|
||||||
var enumerator = trimmed.EnumerateRunes();
|
|
||||||
if (enumerator.MoveNext())
|
|
||||||
return enumerator.Current.ToString();
|
|
||||||
|
|
||||||
return DefaultIconGlyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string ToIconEditorString(string? rawInput)
|
|
||||||
{
|
|
||||||
var normalized = NormalizeIconGlyph(rawInput);
|
|
||||||
var runeEnumerator = normalized.EnumerateRunes();
|
|
||||||
return runeEnumerator.MoveNext()
|
|
||||||
? runeEnumerator.Current.Value.ToString("X4", CultureInfo.InvariantCulture)
|
|
||||||
: DefaultIconGlyph;
|
|
||||||
}
|
|
||||||
private void HideNameplateTextNode(int i)
|
private void HideNameplateTextNode(int i)
|
||||||
{
|
{
|
||||||
var pNode = mTextNodes[i];
|
var pNode = mTextNodes[i];
|
||||||
@@ -552,9 +267,6 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
var nameplateObject = GetNameplateObject(i);
|
var nameplateObject = GetNameplateObject(i);
|
||||||
return nameplateObject != null ? nameplateObject.Value.RootComponentNode : null;
|
return nameplateObject != null ? nameplateObject.Value.RootComponentNode : null;
|
||||||
}
|
}
|
||||||
private HashSet<ulong> VisibleUserIds => [.. _pairManager.GetOnlineUserPairs()
|
|
||||||
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
|
||||||
.Select(u => (ulong)u.PlayerCharacterId)];
|
|
||||||
|
|
||||||
public void FlagRefresh()
|
public void FlagRefresh()
|
||||||
{
|
{
|
||||||
@@ -586,12 +298,4 @@ public unsafe class NameplateHandler : IMediatorSubscriber
|
|||||||
|
|
||||||
FlagRefresh();
|
FlagRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearNameplateCaches()
|
|
||||||
{
|
|
||||||
System.Array.Clear(_cachedNameplateTextWidths, 0, _cachedNameplateTextWidths.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateTextHeights, 0, _cachedNameplateTextHeights.Length);
|
|
||||||
System.Array.Clear(_cachedNameplateContainerHeights, 0, _cachedNameplateContainerHeights.Length);
|
|
||||||
System.Array.Fill(_cachedNameplateTextOffsets, int.MinValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -35,10 +35,12 @@ public class NameplateService : DisposableMediatorSubscriberBase
|
|||||||
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
|
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
|
||||||
_namePlateGui.RequestRedraw();
|
_namePlateGui.RequestRedraw();
|
||||||
Mediator.Subscribe<VisibilityChange>(this, (_) => _namePlateGui.RequestRedraw());
|
Mediator.Subscribe<VisibilityChange>(this, (_) => _namePlateGui.RequestRedraw());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -67,26 +69,16 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,220 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using LightlessSync.LightlessConfiguration;
|
|
||||||
using LightlessSync.Services.Mediator;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
|
||||||
|
|
||||||
public sealed class PairProcessingLimiter : DisposableMediatorSubscriberBase
|
|
||||||
{
|
|
||||||
private const int HardLimit = 32;
|
|
||||||
private readonly LightlessConfigService _configService;
|
|
||||||
private readonly object _limitLock = new();
|
|
||||||
private readonly SemaphoreSlim _semaphore;
|
|
||||||
private int _currentLimit;
|
|
||||||
private int _pendingReductions;
|
|
||||||
private int _waiting;
|
|
||||||
private int _inFlight;
|
|
||||||
|
|
||||||
public PairProcessingLimiter(ILogger<PairProcessingLimiter> logger, LightlessMediator mediator, LightlessConfigService configService)
|
|
||||||
: base(logger, mediator)
|
|
||||||
{
|
|
||||||
_configService = configService;
|
|
||||||
_currentLimit = CalculateLimit();
|
|
||||||
var initialCount = _configService.Current.EnablePairProcessingLimiter ? _currentLimit : HardLimit;
|
|
||||||
_semaphore = new SemaphoreSlim(initialCount, HardLimit);
|
|
||||||
|
|
||||||
Mediator.Subscribe<PairProcessingLimitChangedMessage>(this, _ => UpdateSemaphoreLimit());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask<IAsyncDisposable> AcquireAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
return WaitInternalAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PairProcessingLimiterSnapshot GetSnapshot()
|
|
||||||
{
|
|
||||||
lock (_limitLock)
|
|
||||||
{
|
|
||||||
var enabled = IsEnabled;
|
|
||||||
var limit = enabled ? _currentLimit : CalculateLimit();
|
|
||||||
var waiting = Math.Max(0, Volatile.Read(ref _waiting));
|
|
||||||
var inFlight = Math.Max(0, Volatile.Read(ref _inFlight));
|
|
||||||
return new PairProcessingLimiterSnapshot(enabled, limit, inFlight, waiting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsEnabled => _configService.Current.EnablePairProcessingLimiter;
|
|
||||||
|
|
||||||
private async ValueTask<IAsyncDisposable> WaitInternalAsync(CancellationToken token)
|
|
||||||
{
|
|
||||||
if (!IsEnabled)
|
|
||||||
{
|
|
||||||
return NoopReleaser.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref _waiting);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _semaphore.WaitAsync(token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Interlocked.Decrement(ref _waiting);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Decrement(ref _waiting);
|
|
||||||
|
|
||||||
if (!IsEnabled)
|
|
||||||
{
|
|
||||||
_semaphore.Release();
|
|
||||||
return NoopReleaser.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref _inFlight);
|
|
||||||
return new Releaser(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSemaphoreLimit()
|
|
||||||
{
|
|
||||||
lock (_limitLock)
|
|
||||||
{
|
|
||||||
var enabled = IsEnabled;
|
|
||||||
var desiredLimit = CalculateLimit();
|
|
||||||
|
|
||||||
if (!enabled)
|
|
||||||
{
|
|
||||||
var releaseAmount = HardLimit - _semaphore.CurrentCount;
|
|
||||||
if (releaseAmount > 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_semaphore.Release(releaseAmount);
|
|
||||||
}
|
|
||||||
catch (SemaphoreFullException)
|
|
||||||
{
|
|
||||||
// ignore, already at max
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentLimit = desiredLimit;
|
|
||||||
_pendingReductions = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (desiredLimit == _currentLimit)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (desiredLimit > _currentLimit)
|
|
||||||
{
|
|
||||||
var increment = desiredLimit - _currentLimit;
|
|
||||||
var allowed = Math.Min(increment, HardLimit - _semaphore.CurrentCount);
|
|
||||||
if (allowed > 0)
|
|
||||||
{
|
|
||||||
_semaphore.Release(allowed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var decrement = _currentLimit - desiredLimit;
|
|
||||||
var removed = 0;
|
|
||||||
while (removed < decrement && _semaphore.Wait(0))
|
|
||||||
{
|
|
||||||
removed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remaining = decrement - removed;
|
|
||||||
if (remaining > 0)
|
|
||||||
{
|
|
||||||
_pendingReductions += remaining;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentLimit = desiredLimit;
|
|
||||||
Logger.LogDebug("Pair processing concurrency updated to {limit} (pending reductions: {pending})", _currentLimit, _pendingReductions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CalculateLimit()
|
|
||||||
{
|
|
||||||
var configured = _configService.Current.MaxConcurrentPairApplications;
|
|
||||||
return Math.Clamp(configured, 1, HardLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReleaseOne()
|
|
||||||
{
|
|
||||||
var inFlight = Interlocked.Decrement(ref _inFlight);
|
|
||||||
if (inFlight < 0)
|
|
||||||
{
|
|
||||||
Interlocked.Exchange(ref _inFlight, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_limitLock)
|
|
||||||
{
|
|
||||||
if (_pendingReductions > 0)
|
|
||||||
{
|
|
||||||
_pendingReductions--;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_semaphore.Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (!disposing)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_semaphore.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class Releaser : IAsyncDisposable
|
|
||||||
{
|
|
||||||
private PairProcessingLimiter? _owner;
|
|
||||||
|
|
||||||
public Releaser(PairProcessingLimiter owner)
|
|
||||||
{
|
|
||||||
_owner = owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
var owner = Interlocked.Exchange(ref _owner, null);
|
|
||||||
owner?.ReleaseOne();
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class NoopReleaser : IAsyncDisposable
|
|
||||||
{
|
|
||||||
public static readonly NoopReleaser Instance = new();
|
|
||||||
|
|
||||||
private NoopReleaser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly record struct PairProcessingLimiterSnapshot(bool IsEnabled, int Limit, int InFlight, int Waiting)
|
|
||||||
{
|
|
||||||
public int Remaining => Math.Max(0, Limit - InFlight);
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -504,7 +504,7 @@ public class ServerConfigurationManager
|
|||||||
|
|
||||||
internal void RenameTag(Dictionary<string, List<string>> tags, HashSet<string> storage, string oldName, string newName)
|
internal void RenameTag(Dictionary<string, List<string>> tags, HashSet<string> storage, string oldName, string newName)
|
||||||
{
|
{
|
||||||
if (newName.Length < _maxCharactersFolder)
|
if (newName.Length > _maxCharactersFolder)
|
||||||
{
|
{
|
||||||
storage.Remove(oldName);
|
storage.Remove(oldName);
|
||||||
storage.Add(newName);
|
storage.Add(newName);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
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;
|
||||||
@@ -24,7 +22,7 @@ namespace LightlessSync.UI
|
|||||||
private IReadOnlyList<GroupFullInfoDto> _allSyncshells;
|
private IReadOnlyList<GroupFullInfoDto> _allSyncshells;
|
||||||
private string _userUid = string.Empty;
|
private string _userUid = string.Empty;
|
||||||
|
|
||||||
private readonly List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
|
private List<(string Label, string? GID, bool IsAvailable)> _syncshellOptions = new();
|
||||||
|
|
||||||
public BroadcastUI(
|
public BroadcastUI(
|
||||||
ILogger<BroadcastUI> logger,
|
ILogger<BroadcastUI> logger,
|
||||||
@@ -46,9 +44,11 @@ namespace LightlessSync.UI
|
|||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
this.SizeConstraints = new()
|
this.SizeConstraints = new()
|
||||||
{
|
{
|
||||||
MinimumSize = new(600, 465),
|
MinimumSize = new(600, 340),
|
||||||
MaximumSize = new(750, 525)
|
MaximumSize = new(750, 400)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mediator.Subscribe<RefreshUiMessage>(this, async _ => await RefreshSyncshells());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RebuildSyncshellDropdownOptions()
|
private void RebuildSyncshellDropdownOptions()
|
||||||
@@ -62,7 +62,7 @@ namespace LightlessSync.UI
|
|||||||
_syncshellOptions.Clear();
|
_syncshellOptions.Clear();
|
||||||
_syncshellOptions.Add(("None", null, true));
|
_syncshellOptions.Add(("None", null, true));
|
||||||
|
|
||||||
var addedGids = new HashSet<string>(StringComparer.Ordinal);
|
var addedGids = new HashSet<string>();
|
||||||
|
|
||||||
foreach (var shell in ownedSyncshells)
|
foreach (var shell in ownedSyncshells)
|
||||||
{
|
{
|
||||||
@@ -73,7 +73,7 @@ namespace LightlessSync.UI
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
if (!string.IsNullOrEmpty(selectedGid) && !addedGids.Contains(selectedGid))
|
||||||
{
|
{
|
||||||
var matching = allSyncshells.FirstOrDefault(g => string.Equals(g.GID, selectedGid, StringComparison.Ordinal));
|
var matching = allSyncshells.FirstOrDefault(g => g.GID == selectedGid);
|
||||||
if (matching != null)
|
if (matching != null)
|
||||||
{
|
{
|
||||||
var label = matching.GroupAliasOrGID ?? matching.GID;
|
var label = matching.GroupAliasOrGID ?? matching.GID;
|
||||||
@@ -97,7 +97,7 @@ namespace LightlessSync.UI
|
|||||||
{
|
{
|
||||||
if (!_apiController.IsConnected)
|
if (!_apiController.IsConnected)
|
||||||
{
|
{
|
||||||
_allSyncshells = [];
|
_allSyncshells = Array.Empty<GroupFullInfoDto>();
|
||||||
RebuildSyncshellDropdownOptions();
|
RebuildSyncshellDropdownOptions();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,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 = [];
|
_allSyncshells = Array.Empty<GroupFullInfoDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
RebuildSyncshellDropdownOptions();
|
RebuildSyncshellDropdownOptions();
|
||||||
@@ -119,7 +119,7 @@ namespace LightlessSync.UI
|
|||||||
public override void OnOpen()
|
public override void OnOpen()
|
||||||
{
|
{
|
||||||
_userUid = _apiController.UID;
|
_userUid = _apiController.UID;
|
||||||
_ = RefreshSyncshells();
|
_ = RefreshSyncshellsInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
@@ -131,66 +131,25 @@ namespace LightlessSync.UI
|
|||||||
ImGuiHelpers.ScaledDummy(0.25f);
|
ImGuiHelpers.ScaledDummy(0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTabBar("##BroadcastTabs"))
|
if (ImGui.BeginTabBar("##MyTabBar"))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTabItem("Lightfinder"))
|
if (ImGui.BeginTabItem("Lightfinder"))
|
||||||
{
|
{
|
||||||
_uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue"));
|
_uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue"));
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, -2));
|
ImGui.PushTextWrapPos();
|
||||||
|
ImGui.Text("This lets other Lightless users know you use 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("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.Indent(15f);
|
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.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
ImGui.Text("You can request to pair by right-clicking any (not yourself) character and using 'Send Pair Request'.");
|
||||||
ImGui.Text("- This is done using a 'Lightless' label above player nameplates.");
|
ImGui.PopTextWrapPos();
|
||||||
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.TextWrapped("Use Lightfinder when you're okay with being visible to other users and understand that you are responsible for your own experience.");
|
ImGui.Text("Use it only when you want to be visible.");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGui.PopStyleVar();
|
ImGuiHelpers.ScaledDummy(0.2f);
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(3f);
|
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
||||||
|
|
||||||
if (_configService.Current.BroadcastEnabled)
|
if (_configService.Current.BroadcastEnabled)
|
||||||
@@ -209,7 +168,7 @@ namespace LightlessSync.UI
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
ImGui.Text("The Lightfinder<EFBFBD>s light wanes, but not in vain."); // cringe..
|
ImGui.Text("The Lightfinder’s light wanes, but not in vain."); // cringe..
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +225,6 @@ namespace LightlessSync.UI
|
|||||||
if (_allSyncshells == null)
|
if (_allSyncshells == null)
|
||||||
{
|
{
|
||||||
ImGui.Text("Loading Syncshells...");
|
ImGui.Text("Loading Syncshells...");
|
||||||
_ = RefreshSyncshells();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,14 +260,14 @@ namespace LightlessSync.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
var selectedGid = _configService.Current.SelectedFinderSyncshell;
|
||||||
var currentOption = _syncshellOptions.FirstOrDefault(o => string.Equals(o.GID, selectedGid, StringComparison.Ordinal));
|
var currentOption = _syncshellOptions.FirstOrDefault(o => o.GID == selectedGid);
|
||||||
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 = string.Equals(gid, selectedGid, StringComparison.Ordinal);
|
bool isSelected = gid == selectedGid;
|
||||||
|
|
||||||
if (!available)
|
if (!available)
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed"));
|
||||||
@@ -318,7 +276,6 @@ namespace LightlessSync.UI
|
|||||||
{
|
{
|
||||||
_configService.Current.SelectedFinderSyncshell = gid;
|
_configService.Current.SelectedFinderSyncshell = gid;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
_ = RefreshSyncshells();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!available && ImGui.IsItemHovered())
|
if (!available && ImGui.IsItemHovered())
|
||||||
@@ -353,7 +310,6 @@ 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");
|
||||||
@@ -410,12 +366,17 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Bindings.ImGui;
|
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
@@ -25,7 +24,6 @@ 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;
|
||||||
|
|
||||||
@@ -87,7 +85,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
IpcManager ipcManager,
|
IpcManager ipcManager,
|
||||||
BroadcastService broadcastService,
|
BroadcastService broadcastService,
|
||||||
CharacterAnalyzer characterAnalyzer,
|
CharacterAnalyzer characterAnalyzer,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfig, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
PlayerPerformanceConfigService playerPerformanceConfig) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_uiSharedService = uiShared;
|
_uiSharedService = uiShared;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
@@ -105,7 +103,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_renamePairTagUi = renameTagUi;
|
_renamePairTagUi = renameTagUi;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
_broadcastService = broadcastService;
|
_broadcastService = broadcastService;
|
||||||
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService, pairRequestService, dalamudUtilService);
|
_tabMenu = new TopTabMenu(Mediator, _apiController, _pairManager, _uiSharedService);
|
||||||
|
|
||||||
AllowPinning = true;
|
AllowPinning = true;
|
||||||
AllowClickthrough = false;
|
AllowClickthrough = false;
|
||||||
@@ -143,7 +141,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
_drawFolders = [.. DrawFolders];
|
_drawFolders = [.. GetDrawFolders()];
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
string dev = "Dev Build";
|
string dev = "Dev Build";
|
||||||
@@ -160,7 +158,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 = DrawFolders.ToList());
|
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = GetDrawFolders().ToList());
|
||||||
|
|
||||||
Flags |= ImGuiWindowFlags.NoDocking;
|
Flags |= ImGuiWindowFlags.NoDocking;
|
||||||
|
|
||||||
@@ -377,7 +375,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
private void DrawTransfers()
|
private void DrawTransfers()
|
||||||
{
|
{
|
||||||
var currentUploads = _fileTransferManager.GetCurrentUploadsSnapshot();
|
var currentUploads = _fileTransferManager.CurrentUploads.ToList();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Upload);
|
_uiSharedService.IconText(FontAwesomeIcon.Upload);
|
||||||
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
||||||
@@ -387,12 +385,10 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
var totalUploads = currentUploads.Count;
|
var totalUploads = currentUploads.Count;
|
||||||
|
|
||||||
var doneUploads = currentUploads.Count(c => c.IsTransferred);
|
var doneUploads = currentUploads.Count(c => c.IsTransferred);
|
||||||
var activeUploads = currentUploads.Count(c => !c.IsTransferred);
|
|
||||||
var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8);
|
|
||||||
var totalUploaded = currentUploads.Sum(c => c.Transferred);
|
var totalUploaded = currentUploads.Sum(c => c.Transferred);
|
||||||
var totalToUpload = currentUploads.Sum(c => c.Total);
|
var totalToUpload = currentUploads.Sum(c => c.Total);
|
||||||
|
|
||||||
ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})");
|
ImGui.TextUnformatted($"{doneUploads}/{totalUploads}");
|
||||||
var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})";
|
var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})";
|
||||||
var textSize = ImGui.CalcTextSize(uploadText);
|
var textSize = ImGui.CalcTextSize(uploadText);
|
||||||
ImGui.SameLine(_windowContentWidth - textSize.X);
|
ImGui.SameLine(_windowContentWidth - textSize.X);
|
||||||
@@ -405,7 +401,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.TextUnformatted("No uploads in progress");
|
ImGui.TextUnformatted("No uploads in progress");
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentDownloads = BuildCurrentDownloadSnapshot();
|
var currentDownloads = _currentDownloads.SelectMany(d => d.Value.Values).ToList();
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
_uiSharedService.IconText(FontAwesomeIcon.Download);
|
||||||
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
ImGui.SameLine(35 * ImGuiHelpers.GlobalScale);
|
||||||
@@ -432,53 +428,10 @@ 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();
|
||||||
|
|
||||||
@@ -493,7 +446,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 && _apiController.IsConnected)
|
if (_configService.Current.BroadcastEnabled)
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -514,8 +467,14 @@ 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.TextWrapped("Use Lightfinder when you're okay with being visible to other users and understand that you are responsible for your own experience.");
|
ImGui.Text("Use it only when you want to be visible.");
|
||||||
ImGui.PopStyleColor();
|
ImGui.PopStyleColor();
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(0.2f);
|
ImGuiHelpers.ScaledDummy(0.2f);
|
||||||
@@ -565,30 +524,12 @@ 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)
|
||||||
@@ -620,7 +561,6 @@ 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 = "";
|
||||||
@@ -648,7 +588,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (_apiController.ServerState is ServerState.Connected)
|
if (_apiController.ServerState is ServerState.Connected)
|
||||||
{
|
{
|
||||||
if (headerItemClicked)
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
ImGui.SetClipboardText(_apiController.DisplayName);
|
ImGui.SetClipboardText(_apiController.DisplayName);
|
||||||
}
|
}
|
||||||
@@ -657,24 +597,11 @@ 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 (uidFooterClicked)
|
if (ImGui.IsItemClicked())
|
||||||
{
|
{
|
||||||
ImGui.SetClipboardText(_apiController.UID);
|
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -684,151 +611,152 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<IDrawFolder> DrawFolders
|
private IEnumerable<IDrawFolder> GetDrawFolders()
|
||||||
{
|
{
|
||||||
get
|
List<IDrawFolder> drawFolders = [];
|
||||||
|
|
||||||
|
var allPairs = _pairManager.PairsWithGroups
|
||||||
|
.ToDictionary(k => k.Key, k => k.Value);
|
||||||
|
var filteredPairs = allPairs
|
||||||
|
.Where(p =>
|
||||||
{
|
{
|
||||||
var drawFolders = new List<IDrawFolder>();
|
if (_tabMenu.Filter.IsNullOrEmpty()) return true;
|
||||||
var filter = _tabMenu.Filter;
|
return p.Key.UserData.AliasOrUID.Contains(_tabMenu.Filter, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
(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.Where(p => FilterVisibleUsers(p.Key)));
|
var allVisiblePairs = ImmutablePairList(allPairs
|
||||||
var filteredVisiblePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterVisibleUsers(p.Key)));
|
.Where(FilterVisibleUsers));
|
||||||
|
var filteredVisiblePairs = BasicSortedDictionary(filteredPairs
|
||||||
|
.Where(FilterVisibleUsers));
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomVisibleTag, filteredVisiblePairs, allVisiblePairs));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Filter of not foldered syncshells
|
List<IDrawFolder> groupFolders = new();
|
||||||
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,
|
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
|
||||||
_selectSyncshellForTagUi, _renameSyncshellTagUi, ""));
|
|
||||||
else
|
else
|
||||||
drawFolders.AddRange(groupFolders);
|
drawFolders.AddRange(groupFolders);
|
||||||
|
|
||||||
//Filter of grouped/foldered pairs
|
var tags = _tagHandler.GetAllPairTagsSorted();
|
||||||
foreach (var tag in _tagHandler.GetAllPairTagsSorted())
|
foreach (var tag in tags)
|
||||||
{
|
{
|
||||||
var allTagPairs = ImmutablePairList(allPairs.Where(p => FilterTagUsers(p.Key, tag)));
|
var allTagPairs = ImmutablePairList(allPairs
|
||||||
var filteredTagPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterTagUsers(p.Key, tag) && FilterOnlineOrPausedSelf(p.Key)));
|
.Where(u => FilterTagUsers(u, tag)));
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Filter of grouped/foldered syncshells
|
var syncshellTags = _tagHandler.GetAllSyncshellTagsSorted();
|
||||||
foreach (var syncshellTag in _tagHandler.GetAllSyncshellTagsSorted())
|
foreach (var syncshelltag in syncshellTags)
|
||||||
{
|
{
|
||||||
var syncshellFolderTags = new List<IDrawFolder>();
|
List<IDrawFolder> syncshellFolderTags = [];
|
||||||
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,
|
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
|
||||||
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshellTag));
|
if (syncshellFolderTags.Count > 0)
|
||||||
|
{
|
||||||
|
drawFolders.Add(new DrawGroupedGroupFolder(syncshellFolderTags, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, syncshelltag));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Filter of not grouped/foldered and offline pairs
|
var allOnlineNotTaggedPairs = ImmutablePairList(allPairs
|
||||||
var allOnlineNotTaggedPairs = ImmutablePairList(allPairs.Where(p => FilterNotTaggedUsers(p.Key)));
|
.Where(FilterNotTaggedUsers));
|
||||||
var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs.Where(p => FilterNotTaggedUsers(p.Key) && FilterOnlineOrPausedSelf(p.Key)));
|
var onlineNotTaggedPairs = BasicSortedDictionary(filteredPairs
|
||||||
|
.Where(u => FilterNotTaggedUsers(u) && FilterOnlineOrPausedSelf(u)));
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag), onlineNotTaggedPairs, allOnlineNotTaggedPairs));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder((_configService.Current.ShowOfflineUsersSeparately ? TagHandler.CustomOnlineTag : TagHandler.CustomAllTag),
|
||||||
|
onlineNotTaggedPairs, allOnlineNotTaggedPairs));
|
||||||
|
|
||||||
if (_configService.Current.ShowOfflineUsersSeparately)
|
if (_configService.Current.ShowOfflineUsersSeparately)
|
||||||
{
|
{
|
||||||
var allOfflinePairs = ImmutablePairList(allPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
|
var allOfflinePairs = ImmutablePairList(allPairs
|
||||||
var filteredOfflinePairs = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineUsers(p.Key, p.Value)));
|
.Where(FilterOfflineUsers));
|
||||||
|
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.Where(p => FilterOfflineSyncshellUsers(p.Key)));
|
var allOfflineSyncshellUsers = ImmutablePairList(allPairs
|
||||||
var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs.Where(p => FilterOfflineSyncshellUsers(p.Key)));
|
.Where(FilterOfflineSyncshellUsers));
|
||||||
|
var filteredOfflineSyncshellUsers = BasicSortedDictionary(filteredPairs
|
||||||
|
.Where(FilterOfflineSyncshellUsers));
|
||||||
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag, filteredOfflineSyncshellUsers, allOfflineSyncshellUsers));
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomOfflineSyncshellTag,
|
||||||
|
filteredOfflineSyncshellUsers,
|
||||||
|
allOfflineSyncshellUsers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Unpaired
|
|
||||||
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag,
|
drawFolders.Add(_drawEntityFactory.CreateDrawTagFolder(TagHandler.CustomUnpairedTag,
|
||||||
BasicSortedDictionary(filteredPairs.Where(p => p.Key.IsOneSidedPair)),
|
BasicSortedDictionary(filteredPairs.Where(u => u.Key.IsOneSidedPair)),
|
||||||
ImmutablePairList(allPairs.Where(p => p.Key.IsOneSidedPair))));
|
ImmutablePairList(allPairs.Where(u => u.Key.IsOneSidedPair))));
|
||||||
|
|
||||||
return drawFolders;
|
return drawFolders;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool PassesFilter(Pair pair, string filter)
|
void GetGroups(Dictionary<Pair, List<GroupFullInfoDto>> allPairs, Dictionary<Pair, List<GroupFullInfoDto>> filteredPairs, GroupFullInfoDto group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs)
|
||||||
{
|
|
||||||
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.Value, group)));
|
.Where(u => FilterGroupUsers(u, group)));
|
||||||
|
|
||||||
filteredGroupPairs = filteredPairs
|
filteredGroupPairs = filteredPairs
|
||||||
.Where(u => FilterGroupUsers(u.Value, group) && FilterOnlineOrPausedSelf(u.Key))
|
.Where(u => FilterGroupUsers(u, group) && FilterOnlineOrPausedSelf(u))
|
||||||
.OrderByDescending(u => u.Key.IsOnline)
|
.OrderByDescending(u => u.Key.IsOnline)
|
||||||
.ThenBy(u =>
|
.ThenBy(u =>
|
||||||
{
|
{
|
||||||
@@ -840,9 +768,10 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
return u.Key.IsVisible ? 3 : 4;
|
return u.Key.IsVisible ? 3 : 4;
|
||||||
})
|
})
|
||||||
.ThenBy(u => AlphabeticalSortKey(u.Key), StringComparer.OrdinalIgnoreCase)
|
.ThenBy(AlphabeticalSort, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToDictionary(k => k.Key, k => k.Value);
|
.ToDictionary(k => k.Key, k => k.Value);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetServerError()
|
private string GetServerError()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ 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 != "")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ public class DrawUserPair
|
|||||||
using (ImRaii.PushColor(ImGuiCol.Text, roleColor))
|
using (ImRaii.PushColor(ImGuiCol.Text, roleColor))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted(_pair.UserData.IsAdmin
|
ImGui.TextUnformatted(_pair.UserData.IsAdmin
|
||||||
? "Official Lightless Developer"
|
? "Official Lightless Admin"
|
||||||
: "Official Lightless Moderator");
|
: "Official Lightless Moderator");
|
||||||
}
|
}
|
||||||
ImGui.EndTooltip();
|
ImGui.EndTooltip();
|
||||||
|
|||||||
151
LightlessSync/UI/ContextMenu.cs
Normal file
151
LightlessSync/UI/ContextMenu.cs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
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,147 +547,73 @@ 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).OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
|
||||||
|
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
|
||||||
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(1f, 1f));
|
ImGui.TextUnformatted("Files for " + kvp.Key);
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1f, 1f));
|
ImGui.SameLine();
|
||||||
|
|
||||||
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.Join(Environment.NewLine, groupedfiles.Select(f =>
|
string text = "";
|
||||||
$"{f.Key}: {f.Count()} files, size: {UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))}, compressed: {UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))}"));
|
text = string.Join(Environment.NewLine, groupedfiles
|
||||||
|
.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.TableNextColumn();
|
ImGui.SameLine();
|
||||||
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):");
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
ImGui.SameLine();
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
ImGui.Separator();
|
||||||
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.TableNextColumn();
|
ImGui.SameLine();
|
||||||
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(
|
UiSharedService.ColorText($"You exceed your own threshold by " +
|
||||||
$"You exceed your own threshold by {UiSharedService.ByteToString(actualVramUsage - (currentVramWarning * 1024 * 1024))}",
|
$"{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.TableNextRow();
|
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {actualTriCount}");
|
||||||
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(
|
UiSharedService.ColorText($"You exceed your own threshold by " +
|
||||||
$"You exceed your own threshold by {actualTriCount - (currentTriWarning * 1000)}",
|
$"{actualTriCount - (currentTriWarning * 1000)} triangles.",
|
||||||
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;
|
||||||
@@ -766,6 +692,41 @@ 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()
|
||||||
@@ -894,7 +855,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 (UiSharedService.CheckboxWithBorder("###convert" + item.Hash, ref toConvert, UIColors.Get("LightlessPurple"), 1.5f))
|
if (ImGui.Checkbox("###convert" + item.Hash, ref toConvert))
|
||||||
{
|
{
|
||||||
if (toConvert && !_texturesToConvert.ContainsKey(filePath))
|
if (toConvert && !_texturesToConvert.ContainsKey(filePath))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Bindings.ImGui;
|
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
@@ -20,16 +19,14 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
private readonly DalamudUtilService _dalamudUtilService;
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
private readonly FileUploadManager _fileTransferManager;
|
private readonly FileUploadManager _fileTransferManager;
|
||||||
private readonly UiSharedService _uiShared;
|
private readonly UiSharedService _uiShared;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
|
||||||
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
|
private readonly ConcurrentDictionary<GameObjectHandler, bool> _uploadingPlayers = new();
|
||||||
|
|
||||||
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
|
public DownloadUi(ILogger<DownloadUi> logger, DalamudUtilService dalamudUtilService, LightlessConfigService configService,
|
||||||
PairProcessingLimiter pairProcessingLimiter, FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService)
|
FileUploadManager fileTransferManager, LightlessMediator mediator, UiSharedService uiShared, PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService)
|
: base(logger, mediator, "Lightless Sync Downloads", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
|
||||||
_fileTransferManager = fileTransferManager;
|
_fileTransferManager = fileTransferManager;
|
||||||
_uiShared = uiShared;
|
_uiShared = uiShared;
|
||||||
|
|
||||||
@@ -76,25 +73,11 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (_configService.Current.ShowTransferWindow)
|
if (_configService.Current.ShowTransferWindow)
|
||||||
{
|
{
|
||||||
var limiterSnapshot = _pairProcessingLimiter.GetSnapshot();
|
|
||||||
if (limiterSnapshot.IsEnabled)
|
|
||||||
{
|
|
||||||
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
|
||||||
var queueText = $"Pair queue {limiterSnapshot.InFlight}/{limiterSnapshot.Limit}";
|
|
||||||
queueText += limiterSnapshot.Waiting > 0 ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" : $" ({limiterSnapshot.Remaining} free)";
|
|
||||||
UiSharedService.DrawOutlinedFont(queueText, queueColor, new Vector4(0, 0, 0, 255), 1);
|
|
||||||
ImGui.NewLine();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UiSharedService.DrawOutlinedFont("Pair apply limiter disabled", ImGuiColors.DalamudGrey, new Vector4(0, 0, 0, 255), 1);
|
|
||||||
ImGui.NewLine();
|
|
||||||
}
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_fileTransferManager.IsUploading)
|
if (_fileTransferManager.CurrentUploads.Any())
|
||||||
{
|
{
|
||||||
var currentUploads = _fileTransferManager.GetCurrentUploadsSnapshot();
|
var currentUploads = _fileTransferManager.CurrentUploads.ToList();
|
||||||
var totalUploads = currentUploads.Count;
|
var totalUploads = currentUploads.Count;
|
||||||
|
|
||||||
var doneUploads = currentUploads.Count(c => c.IsTransferred);
|
var doneUploads = currentUploads.Count(c => c.IsTransferred);
|
||||||
@@ -231,7 +214,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (_uiShared.EditTrackerPosition) return true;
|
if (_uiShared.EditTrackerPosition) return true;
|
||||||
if (!_configService.Current.ShowTransferWindow && !_configService.Current.ShowTransferBars) return false;
|
if (!_configService.Current.ShowTransferWindow && !_configService.Current.ShowTransferBars) return false;
|
||||||
if (!_currentDownloads.Any() && !_fileTransferManager.IsUploading && !_uploadingPlayers.Any()) return false;
|
if (!_currentDownloads.Any() && !_fileTransferManager.CurrentUploads.Any() && !_uploadingPlayers.Any()) return false;
|
||||||
if (!IsOpen) return false;
|
if (!IsOpen) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,14 @@ 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;
|
||||||
|
|
||||||
@@ -34,16 +30,6 @@ 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)
|
||||||
@@ -52,8 +38,8 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
this.SizeConstraints = new()
|
this.SizeConstraints = new()
|
||||||
{
|
{
|
||||||
MinimumSize = new(850, 640),
|
MinimumSize = new(768, 512),
|
||||||
MaximumSize = new(850, 700)
|
MaximumSize = new(768, 2000)
|
||||||
};
|
};
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
@@ -71,52 +57,14 @@ 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.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow"));
|
_uiSharedService.BigText("Current Profile (as saved on server)");
|
||||||
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);
|
||||||
@@ -171,14 +119,18 @@ public class EditProfileUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Checkbox("Is NSFW", ref nsfw);
|
ImGui.Checkbox("Is NSFW", ref nsfw);
|
||||||
ImGui.EndDisabled();
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
ImGui.Separator();
|
||||||
ImGui.EndTabItem();
|
_uiSharedService.BigText("Notes and Rules for Profiles");
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Profile Settings"))
|
ImGui.TextWrapped($"- All users that are paired and unpaused with you will be able to see your profile picture and description.{Environment.NewLine}" +
|
||||||
{
|
$"- Other users have the possibility to report your profile for breaking the rules.{Environment.NewLine}" +
|
||||||
_uiSharedService.MediumText("Profile Settings", UIColors.Get("LightlessPurple"));
|
$"- !!! 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}" +
|
||||||
ImGui.Dummy(new Vector2(5));
|
$"- !!! AVOID: slurs of any kind in the description that can be considered highly offensive{Environment.NewLine}" +
|
||||||
|
$"- 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"))
|
||||||
{
|
{
|
||||||
@@ -271,120 +223,6 @@ 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,15 +1,12 @@
|
|||||||
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;
|
||||||
@@ -27,9 +24,6 @@ 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;
|
||||||
@@ -102,102 +96,31 @@ public class IdDisplayHandler
|
|||||||
{
|
{
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
|
|
||||||
var font = textIsUid ? UiBuilder.MonoFont : ImGui.GetFont();
|
var font = UiBuilder.MonoFont;
|
||||||
|
|
||||||
Vector4? textColor = null;
|
var isAdmin = pair.UserData.IsAdmin;
|
||||||
Vector4? glowColor = null;
|
var isModerator = pair.UserData.IsModerator;
|
||||||
|
|
||||||
if (pair.UserData.HasVanity)
|
Vector4? textColor = isAdmin
|
||||||
{
|
? UIColors.Get("LightlessAdminText")
|
||||||
if (!string.IsNullOrWhiteSpace(pair.UserData.TextColorHex))
|
: isModerator
|
||||||
{
|
? UIColors.Get("LightlessModeratorText")
|
||||||
textColor = UIColors.HexToRgba(pair.UserData.TextColorHex);
|
: null;
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(pair.UserData.TextGlowColorHex))
|
Vector4? glowColor = isAdmin
|
||||||
{
|
? UIColors.Get("LightlessAdminGlow")
|
||||||
glowColor = UIColors.HexToRgba(pair.UserData.TextGlowColorHex);
|
: isModerator
|
||||||
}
|
? UIColors.Get("LightlessModeratorGlow")
|
||||||
}
|
: null;
|
||||||
|
|
||||||
var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null);
|
var seString = (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))
|
||||||
{
|
{
|
||||||
SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font);
|
var pos = ImGui.GetCursorScreenPos();
|
||||||
itemMin = ImGui.GetItemRectMin();
|
SeStringUtils.RenderSeStringWithHitbox(seString, pos, font);
|
||||||
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 unnecessarily download files already present on your computer, Lightless Sync will have to scan your Penumbra mod directory. " +
|
UiSharedService.TextWrapped("To not unnecessary 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. LLS- is part of Syncshell IDs, unless using Vanity IDs.");
|
ImGui.TextUnformatted("Note: Syncshell ID and Password are case sensitive. MSS- is part of Syncshell IDs, unless using Vanity IDs.");
|
||||||
|
|
||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("Syncshell ID");
|
ImGui.TextUnformatted("Syncshell ID");
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
@@ -11,7 +10,6 @@ using LightlessSync.API.Routes;
|
|||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using LightlessSync.Interop.Ipc;
|
using LightlessSync.Interop.Ipc;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
using LightlessSync.LightlessConfiguration.Models;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.PlayerData.Pairs;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
@@ -19,18 +17,15 @@ using LightlessSync.Services;
|
|||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Services.ServerConfiguration;
|
using LightlessSync.Services.ServerConfiguration;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.UtilsEnum.Enum;
|
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using LightlessSync.WebAPI.Files;
|
using LightlessSync.WebAPI.Files;
|
||||||
using LightlessSync.WebAPI.Files.Models;
|
using LightlessSync.WebAPI.Files.Models;
|
||||||
using LightlessSync.WebAPI.SignalR.Utils;
|
using LightlessSync.WebAPI.SignalR.Utils;
|
||||||
using Microsoft.AspNetCore.Http.Connections;
|
using Microsoft.AspNetCore.Http.Connections;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@@ -55,12 +50,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private readonly PerformanceCollectorService _performanceCollector;
|
private readonly PerformanceCollectorService _performanceCollector;
|
||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly UiSharedService _uiShared;
|
private readonly UiSharedService _uiShared;
|
||||||
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
|
private readonly IProgress<(int, int, FileCacheEntity)> _validationProgress;
|
||||||
private readonly NameplateService _nameplateService;
|
private readonly NameplateService _nameplateService;
|
||||||
private readonly NameplateHandler _nameplateHandler;
|
|
||||||
private (int, int, FileCacheEntity) _currentProgress;
|
private (int, int, FileCacheEntity) _currentProgress;
|
||||||
private bool _deleteAccountPopupModalShown = false;
|
private bool _deleteAccountPopupModalShown = false;
|
||||||
private bool _deleteFilesPopupModalShown = false;
|
private bool _deleteFilesPopupModalShown = false;
|
||||||
@@ -70,23 +63,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private bool _readClearCache = false;
|
private bool _readClearCache = false;
|
||||||
private int _selectedEntry = -1;
|
private int _selectedEntry = -1;
|
||||||
private string _uidToAddForIgnore = string.Empty;
|
private string _uidToAddForIgnore = string.Empty;
|
||||||
private string _lightfinderIconInput = string.Empty;
|
|
||||||
private bool _lightfinderIconInputInitialized = false;
|
|
||||||
private int _lightfinderIconPresetIndex = -1;
|
|
||||||
private static readonly (string Label, SeIconChar Icon)[] LightfinderIconPresets = new[]
|
|
||||||
{
|
|
||||||
("Link Marker", SeIconChar.LinkMarker),
|
|
||||||
("Hyadelyn", SeIconChar.Hyadelyn),
|
|
||||||
("Gil", SeIconChar.Gil),
|
|
||||||
("Quest Sync", SeIconChar.QuestSync),
|
|
||||||
("Glamoured", SeIconChar.Glamoured),
|
|
||||||
("Glamoured (Dyed)", SeIconChar.GlamouredDyed),
|
|
||||||
("Auto-Translate Open", SeIconChar.AutoTranslateOpen),
|
|
||||||
("Auto-Translate Close", SeIconChar.AutoTranslateClose),
|
|
||||||
("Boxed Star", SeIconChar.BoxedStar),
|
|
||||||
("Boxed Plus", SeIconChar.BoxedPlus)
|
|
||||||
};
|
|
||||||
|
|
||||||
private CancellationTokenSource? _validationCts;
|
private CancellationTokenSource? _validationCts;
|
||||||
private Task<List<FileCacheEntity>>? _validationTask;
|
private Task<List<FileCacheEntity>>? _validationTask;
|
||||||
private bool _wasOpen = false;
|
private bool _wasOpen = false;
|
||||||
@@ -96,7 +72,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
PairManager pairManager,
|
PairManager pairManager,
|
||||||
ServerConfigurationManager serverConfigurationManager,
|
ServerConfigurationManager serverConfigurationManager,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfigService,
|
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||||
PairProcessingLimiter pairProcessingLimiter,
|
|
||||||
LightlessMediator mediator, PerformanceCollectorService performanceCollector,
|
LightlessMediator mediator, PerformanceCollectorService performanceCollector,
|
||||||
FileUploadManager fileTransferManager,
|
FileUploadManager fileTransferManager,
|
||||||
FileTransferOrchestrator fileTransferOrchestrator,
|
FileTransferOrchestrator fileTransferOrchestrator,
|
||||||
@@ -104,14 +79,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
FileCompactor fileCompactor, ApiController apiController,
|
FileCompactor fileCompactor, ApiController apiController,
|
||||||
IpcManager ipcManager, CacheMonitor cacheMonitor,
|
IpcManager ipcManager, CacheMonitor cacheMonitor,
|
||||||
DalamudUtilService dalamudUtilService, HttpClient httpClient,
|
DalamudUtilService dalamudUtilService, HttpClient httpClient,
|
||||||
NameplateService nameplateService,
|
NameplateService nameplateService) : base(logger, mediator, "Lightless Sync Settings", performanceCollector)
|
||||||
NameplateHandler nameplateHandler) : base(logger, mediator, "Lightless Sync Settings", performanceCollector)
|
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
|
||||||
_performanceCollector = performanceCollector;
|
_performanceCollector = performanceCollector;
|
||||||
_fileTransferManager = fileTransferManager;
|
_fileTransferManager = fileTransferManager;
|
||||||
_fileTransferOrchestrator = fileTransferOrchestrator;
|
_fileTransferOrchestrator = fileTransferOrchestrator;
|
||||||
@@ -124,7 +97,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_fileCompactor = fileCompactor;
|
_fileCompactor = fileCompactor;
|
||||||
_uiShared = uiShared;
|
_uiShared = uiShared;
|
||||||
_nameplateService = nameplateService;
|
_nameplateService = nameplateService;
|
||||||
_nameplateHandler = nameplateHandler;
|
|
||||||
AllowClickthrough = false;
|
AllowClickthrough = false;
|
||||||
AllowPinning = true;
|
AllowPinning = true;
|
||||||
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
|
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
|
||||||
@@ -246,9 +218,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGuiHelpers.ScaledDummy(5);
|
ImGuiHelpers.ScaledDummy(5);
|
||||||
|
|
||||||
int maxParallelDownloads = _configService.Current.ParallelDownloads;
|
int maxParallelDownloads = _configService.Current.ParallelDownloads;
|
||||||
int maxParallelUploads = _configService.Current.ParallelUploads;
|
|
||||||
int maxPairApplications = _configService.Current.MaxConcurrentPairApplications;
|
|
||||||
bool limitPairApplications = _configService.Current.EnablePairProcessingLimiter;
|
|
||||||
bool useAlternativeUpload = _configService.Current.UseAlternativeFileUpload;
|
bool useAlternativeUpload = _configService.Current.UseAlternativeFileUpload;
|
||||||
int downloadSpeedLimit = _configService.Current.DownloadSpeedLimitInBytes;
|
int downloadSpeedLimit = _configService.Current.DownloadSpeedLimitInBytes;
|
||||||
|
|
||||||
@@ -285,60 +254,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_configService.Current.ParallelDownloads = maxParallelDownloads;
|
_configService.Current.ParallelDownloads = maxParallelDownloads;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
Mediator.Publish(new DownloadLimitChangedMessage());
|
|
||||||
}
|
}
|
||||||
_uiShared.DrawHelpText("Controls how many download slots can be active at once.");
|
|
||||||
|
|
||||||
if (ImGui.SliderInt("Maximum Parallel Uploads", ref maxParallelUploads, 1, 8))
|
|
||||||
{
|
|
||||||
_configService.Current.ParallelUploads = maxParallelUploads;
|
|
||||||
_configService.Save();
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Controls how many uploads can run at once.");
|
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Enable Pair Download Limiter", ref limitPairApplications))
|
|
||||||
{
|
|
||||||
_configService.Current.EnablePairProcessingLimiter = limitPairApplications;
|
|
||||||
_configService.Save();
|
|
||||||
Mediator.Publish(new PairProcessingLimitChangedMessage());
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("When enabled we stagger pair downloads to avoid large network and game lag caused by attempting to download everyone at once.");
|
|
||||||
|
|
||||||
var limiterDisabledScope = !limitPairApplications;
|
|
||||||
if (limiterDisabledScope)
|
|
||||||
{
|
|
||||||
ImGui.BeginDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.SliderInt("Maximum Concurrent Pair Downloads", ref maxPairApplications, 1, 6))
|
|
||||||
{
|
|
||||||
_configService.Current.MaxConcurrentPairApplications = maxPairApplications;
|
|
||||||
_configService.Save();
|
|
||||||
Mediator.Publish(new PairProcessingLimitChangedMessage());
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("How many pair downloads/applications can run simultaneously when the limit is on.");
|
|
||||||
|
|
||||||
if (limiterDisabledScope)
|
|
||||||
{
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
var limiterSnapshot = _pairProcessingLimiter.GetSnapshot();
|
|
||||||
if (limiterSnapshot.IsEnabled)
|
|
||||||
{
|
|
||||||
var queueColor = limiterSnapshot.Waiting > 0 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudGrey;
|
|
||||||
var queueText = $"Pair queue {limiterSnapshot.InFlight}/{limiterSnapshot.Limit}";
|
|
||||||
queueText += limiterSnapshot.Waiting > 0 ? $" ({limiterSnapshot.Waiting} waiting, {limiterSnapshot.Remaining} free)" : $" ({limiterSnapshot.Remaining} free)";
|
|
||||||
ImGui.TextColored(queueColor, queueText);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextColored(ImGuiColors.DalamudGrey, "Pair apply limiter is disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f);
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Use Alternative Upload Method", ref useAlternativeUpload))
|
if (ImGui.Checkbox("Use Alternative Upload Method", ref useAlternativeUpload))
|
||||||
{
|
{
|
||||||
@@ -493,33 +409,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (ApiController.ServerState is ServerState.Connected && ImGui.BeginTabItem("Transfers"))
|
if (ApiController.ServerState is ServerState.Connected && ImGui.BeginTabItem("Transfers"))
|
||||||
{
|
{
|
||||||
var uploadsSnapshot = _fileTransferManager.GetCurrentUploadsSnapshot();
|
ImGui.TextUnformatted("Uploads");
|
||||||
var activeUploads = uploadsSnapshot.Count(c => !c.IsTransferred);
|
|
||||||
var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8);
|
|
||||||
ImGui.TextUnformatted($"Uploads (slots {activeUploads}/{uploadSlotLimit})");
|
|
||||||
if (ImGui.BeginTable("UploadsTable", 3))
|
if (ImGui.BeginTable("UploadsTable", 3))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("File");
|
ImGui.TableSetupColumn("File");
|
||||||
ImGui.TableSetupColumn("Uploaded");
|
ImGui.TableSetupColumn("Uploaded");
|
||||||
ImGui.TableSetupColumn("Size");
|
ImGui.TableSetupColumn("Size");
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
foreach (var transfer in uploadsSnapshot)
|
foreach (var transfer in _fileTransferManager.CurrentUploads.ToArray())
|
||||||
{
|
{
|
||||||
var color = UiSharedService.UploadColor((transfer.Transferred, transfer.Total));
|
var color = UiSharedService.UploadColor((transfer.Transferred, transfer.Total));
|
||||||
using var col = ImRaii.PushColor(ImGuiCol.Text, color);
|
var col = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (transfer is UploadFileTransfer uploadTransfer)
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(uploadTransfer.LocalFile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ImGui.TextUnformatted(transfer.Hash);
|
ImGui.TextUnformatted(transfer.Hash);
|
||||||
}
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(transfer.Transferred));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(transfer.Transferred));
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(UiSharedService.ByteToString(transfer.Total));
|
ImGui.TextUnformatted(UiSharedService.ByteToString(transfer.Total));
|
||||||
|
col.Dispose();
|
||||||
|
ImGui.TableNextRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
@@ -1032,7 +940,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_configService.Current.EnableRightClickMenus = enableRightClickMenu;
|
_configService.Current.EnableRightClickMenus = enableRightClickMenu;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
_uiShared.DrawHelpText("This will add all Lightless related right click menu entries in the game UI.");
|
_uiShared.DrawHelpText("This will add Lightless related right click menu entries in the game UI on paired players.");
|
||||||
|
|
||||||
if (ImGui.Checkbox("Display status and visible pair count in Server Info Bar", ref enableDtrEntry))
|
if (ImGui.Checkbox("Display status and visible pair count in Server Info Bar", ref enableDtrEntry))
|
||||||
{
|
{
|
||||||
@@ -1064,330 +972,44 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_uiShared.MediumTreeNode("Lightfinder", UIColors.Get("LightlessPurple")))
|
|
||||||
{
|
|
||||||
var autoAlign = _configService.Current.LightfinderAutoAlign;
|
|
||||||
var offsetX = (int)_configService.Current.LightfinderLabelOffsetX;
|
|
||||||
var offsetY = (int)_configService.Current.LightfinderLabelOffsetY;
|
|
||||||
var labelScale = _configService.Current.LightfinderLabelScale;
|
|
||||||
|
|
||||||
ImGui.TextUnformatted("Alignment");
|
|
||||||
ImGui.BeginDisabled(autoAlign);
|
|
||||||
if (ImGui.SliderInt("Label Offset X", ref offsetX, -200, 200))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelOffsetX = (short)offsetX;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelOffsetX = 0;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Right click to reset to default.");
|
|
||||||
ImGui.EndDisabled();
|
|
||||||
_uiShared.DrawHelpText("Moves the Lightfinder label horizontally on player nameplates.\nUnavailable when automatic alignment is enabled.");
|
|
||||||
|
|
||||||
|
|
||||||
if (ImGui.SliderInt("Label Offset Y", ref offsetY, -200, 200))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelOffsetY = (short)offsetY;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelOffsetY = 0;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Right click to reset to default.");
|
|
||||||
_uiShared.DrawHelpText("Moves the Lightfinder label vertically on player nameplates.");
|
|
||||||
|
|
||||||
if (ImGui.SliderFloat("Label Size", ref labelScale, 0.5f, 2.0f, "%.2fx"))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelScale = labelScale;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelScale = 1.0f;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip("Right click to reset to default.");
|
|
||||||
_uiShared.DrawHelpText("Adjusts the Lightfinder label size for both text and icon modes.");
|
|
||||||
|
|
||||||
ImGui.Dummy(new Vector2(8));
|
|
||||||
|
|
||||||
if (ImGui.Checkbox("Automatically align with nameplate", ref autoAlign))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderAutoAlign = autoAlign;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Automatically position the label relative to the in-game nameplate. Turn off to rely entirely on manual offsets.");
|
|
||||||
|
|
||||||
if (autoAlign)
|
|
||||||
{
|
|
||||||
var alignmentOption = _configService.Current.LabelAlignment;
|
|
||||||
var alignmentLabel = alignmentOption switch
|
|
||||||
{
|
|
||||||
LabelAlignment.Left => "Left",
|
|
||||||
LabelAlignment.Right => "Right",
|
|
||||||
_ => "Center",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Horizontal Alignment", alignmentLabel))
|
|
||||||
{
|
|
||||||
foreach (LabelAlignment option in Enum.GetValues<LabelAlignment>())
|
|
||||||
{
|
|
||||||
var optionLabel = option switch
|
|
||||||
{
|
|
||||||
LabelAlignment.Left => "Left",
|
|
||||||
LabelAlignment.Right => "Right",
|
|
||||||
_ => "Center",
|
|
||||||
};
|
|
||||||
var selected = option == alignmentOption;
|
|
||||||
if (ImGui.Selectable(optionLabel, selected))
|
|
||||||
{
|
|
||||||
_configService.Current.LabelAlignment = option;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected)
|
|
||||||
ImGui.SetItemDefaultFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
|
||||||
|
|
||||||
ImGui.TextUnformatted("Visibility");
|
|
||||||
var showOwn = _configService.Current.LightfinderLabelShowOwn;
|
|
||||||
if (ImGui.Checkbox("Show your own Lightfinder label", ref showOwn))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelShowOwn = showOwn;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Toggles your own Lightfinder label.");
|
|
||||||
|
|
||||||
var showPaired = _configService.Current.LightfinderLabelShowPaired;
|
|
||||||
if (ImGui.Checkbox("Show paired player(s) Lightfinder label", ref showPaired))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelShowPaired = showPaired;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Toggles paired player(s) Lightfinder label.");
|
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
|
||||||
|
|
||||||
ImGui.TextUnformatted("Label");
|
|
||||||
var useIcon = _configService.Current.LightfinderLabelUseIcon;
|
|
||||||
if (ImGui.Checkbox("Show icon instead of text", ref useIcon))
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelUseIcon = useIcon;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.ClearNameplateCaches();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
|
|
||||||
if (useIcon)
|
|
||||||
{
|
|
||||||
RefreshLightfinderIconState();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lightfinderIconInputInitialized = false;
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_uiShared.DrawHelpText("Switch between the Lightfinder text label and an icon on nameplates.");
|
|
||||||
|
|
||||||
if (useIcon)
|
|
||||||
{
|
|
||||||
if (!_lightfinderIconInputInitialized)
|
|
||||||
{
|
|
||||||
RefreshLightfinderIconState();
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentPresetLabel = _lightfinderIconPresetIndex >= 0
|
|
||||||
? $"{GetLightfinderPresetGlyph(_lightfinderIconPresetIndex)} {LightfinderIconPresets[_lightfinderIconPresetIndex].Label}"
|
|
||||||
: "Custom";
|
|
||||||
|
|
||||||
if (ImGui.BeginCombo("Preset Icon", currentPresetLabel))
|
|
||||||
{
|
|
||||||
for (int i = 0; i < LightfinderIconPresets.Length; i++)
|
|
||||||
{
|
|
||||||
var optionGlyph = GetLightfinderPresetGlyph(i);
|
|
||||||
var preview = $"{optionGlyph} {LightfinderIconPresets[i].Label}";
|
|
||||||
var selected = i == _lightfinderIconPresetIndex;
|
|
||||||
if (ImGui.Selectable(preview, selected))
|
|
||||||
{
|
|
||||||
ApplyLightfinderIcon(optionGlyph, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Selectable("Custom", _lightfinderIconPresetIndex == -1))
|
|
||||||
{
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.EndCombo();
|
|
||||||
}
|
|
||||||
|
|
||||||
var editorBuffer = _lightfinderIconInput;
|
|
||||||
if (ImGui.InputText("Icon Glyph", ref editorBuffer, 16))
|
|
||||||
{
|
|
||||||
_lightfinderIconInput = editorBuffer;
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.Button("Apply Icon"))
|
|
||||||
{
|
|
||||||
var normalized = NameplateHandler.NormalizeIconGlyph(_lightfinderIconInput);
|
|
||||||
ApplyLightfinderIcon(normalized, _lightfinderIconPresetIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (ImGui.Button("Reset Icon"))
|
|
||||||
{
|
|
||||||
var defaultGlyph = NameplateHandler.NormalizeIconGlyph(null);
|
|
||||||
var defaultIndex = -1;
|
|
||||||
for (int i = 0; i < LightfinderIconPresets.Length; i++)
|
|
||||||
{
|
|
||||||
if (string.Equals(GetLightfinderPresetGlyph(i), defaultGlyph, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
defaultIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (defaultIndex < 0)
|
|
||||||
{
|
|
||||||
defaultIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApplyLightfinderIcon(GetLightfinderPresetGlyph(defaultIndex), defaultIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var previewGlyph = NameplateHandler.NormalizeIconGlyph(_lightfinderIconInput);
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.AlignTextToFramePadding();
|
|
||||||
ImGui.Text($"Preview: {previewGlyph}");
|
|
||||||
_uiShared.DrawHelpText("Enter a hex code (e.g. E0BB), pick a preset, or paste an icon character directly.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lightfinderIconInputInitialized = false;
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
|
||||||
ImGui.TreePop();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.Separator();
|
|
||||||
|
|
||||||
if (_uiShared.MediumTreeNode("Colors", UIColors.Get("LightlessPurple")))
|
if (_uiShared.MediumTreeNode("Colors", UIColors.Get("LightlessPurple")))
|
||||||
{
|
{
|
||||||
ImGui.TextUnformatted("UI Theme Colors");
|
ImGui.TextUnformatted("UI Theme Colors");
|
||||||
|
|
||||||
var colorNames = new[]
|
var colorNames = new[]
|
||||||
{
|
{
|
||||||
("LightlessPurple", "Primary Purple", "Section titles and dividers"),
|
("LightlessPurple", "Lightless Purple", "Primary colors"),
|
||||||
("LightlessPurpleActive", "Primary Purple (Active)", "Active tabs and hover highlights"),
|
("LightlessBlue", "Lightless Blue", "Secondary colors"),
|
||||||
("LightlessPurpleDefault", "Primary Purple (Inactive)", "Inactive tabs and default dividers"),
|
("LightlessYellow", "Lightless Yellow", "Warning colors"),
|
||||||
("LightlessBlue", "Secondary Blue", "Secondary title colors, visable pairs"),
|
("PairBlue", "Pair Blue", "Pair UI elements"),
|
||||||
|
("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.AlignTextToFramePadding();
|
ImGui.TextUnformatted($"{displayName} - {description}");
|
||||||
ImGui.TextUnformatted(displayName);
|
|
||||||
|
|
||||||
// description column
|
if (UIColors.IsCustom(colorKey))
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
using (ImRaii.PushFont(UiBuilder.IconFont))
|
ImGui.SameLine();
|
||||||
{
|
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"))
|
||||||
@@ -1398,8 +1020,6 @@ 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))
|
||||||
@@ -1434,16 +1054,12 @@ 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))
|
||||||
{
|
{
|
||||||
@@ -1474,35 +1090,14 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Spacing();
|
if (ImGui.Checkbox("Use the complete redesign of the UI for Lightless client.", ref useLightlessRedesign))
|
||||||
|
|
||||||
_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();
|
||||||
}
|
}
|
||||||
@@ -1557,12 +1152,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))
|
||||||
@@ -2563,39 +2158,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
return (true, failedConversions.Count != 0, sb.ToString());
|
return (true, failedConversions.Count != 0, sb.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetLightfinderPresetGlyph(int index)
|
|
||||||
{
|
|
||||||
return NameplateHandler.NormalizeIconGlyph(SeIconCharExtensions.ToIconString(LightfinderIconPresets[index].Icon));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RefreshLightfinderIconState()
|
|
||||||
{
|
|
||||||
var normalized = NameplateHandler.NormalizeIconGlyph(_configService.Current.LightfinderLabelIconGlyph);
|
|
||||||
_lightfinderIconInput = NameplateHandler.ToIconEditorString(normalized);
|
|
||||||
_lightfinderIconInputInitialized = true;
|
|
||||||
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
|
||||||
for (int i = 0; i < LightfinderIconPresets.Length; i++)
|
|
||||||
{
|
|
||||||
if (string.Equals(GetLightfinderPresetGlyph(i), normalized, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
_lightfinderIconPresetIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyLightfinderIcon(string normalizedGlyph, int presetIndex)
|
|
||||||
{
|
|
||||||
_configService.Current.LightfinderLabelIconGlyph = normalizedGlyph;
|
|
||||||
_configService.Save();
|
|
||||||
_nameplateHandler.FlagRefresh();
|
|
||||||
_nameplateService.RequestRedraw();
|
|
||||||
_lightfinderIconInput = NameplateHandler.ToIconEditorString(normalizedGlyph);
|
|
||||||
_lightfinderIconPresetIndex = presetIndex;
|
|
||||||
_lightfinderIconInputInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSettingsContent()
|
private void DrawSettingsContent()
|
||||||
{
|
{
|
||||||
if (_apiController.ServerState is ServerState.Connected)
|
if (_apiController.ServerState is ServerState.Connected)
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
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("DimRed")))
|
if (_uiSharedService.MediumTreeNode("Mass Cleanup", UIColors.Get("LightlessPurple")))
|
||||||
{
|
{
|
||||||
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
|
using (ImRaii.Disabled(!UiSharedService.CtrlPressed()))
|
||||||
{
|
{
|
||||||
@@ -348,18 +348,6 @@ 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);
|
||||||
@@ -422,12 +410,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("DimRed"), 1.5f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f);
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
||||||
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessYellow")))
|
if (_uiSharedService.MediumTreeNode("User Bans", UIColors.Get("LightlessPurple")))
|
||||||
{
|
{
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
|
||||||
{
|
{
|
||||||
@@ -468,7 +456,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
|
_uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 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.PlayerData.Pairs;
|
using LightlessSync.LightlessConfiguration;
|
||||||
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,16 +20,13 @@ 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 = [];
|
private readonly List<GroupJoinDto> _nearbySyncshells = new();
|
||||||
private List<GroupFullInfoDto> _currentSyncshells = [];
|
|
||||||
private int _selectedNearbyIndex = -1;
|
private int _selectedNearbyIndex = -1;
|
||||||
private readonly HashSet<string> _recentlyJoined = new(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
private GroupJoinDto? _joinDto;
|
private GroupJoinDto? _joinDto;
|
||||||
private GroupJoinInfoDto? _joinInfo;
|
private GroupJoinInfoDto? _joinInfo;
|
||||||
@@ -40,18 +37,17 @@ 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
|
||||||
PairManager pairManager,
|
) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
|
||||||
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()
|
||||||
@@ -60,14 +56,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
MaximumSize = new(600, 550)
|
MaximumSize = new(600, 550)
|
||||||
};
|
};
|
||||||
|
|
||||||
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
|
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync());
|
||||||
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
|
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnOpen()
|
public override async void OnOpen()
|
||||||
{
|
{
|
||||||
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
|
_ownPermissions = _apiController.DefaultPermissions.DeepClone()!;
|
||||||
await RefreshSyncshellsAsync().ConfigureAwait(false);
|
await RefreshSyncshellsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawInternal()
|
protected override void DrawInternal()
|
||||||
@@ -104,45 +100,22 @@ 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("Syncshell", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Broadcaster", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("GID", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
|
ImGui.TableSetupColumn("Join", ImGuiTableColumnFlags.WidthFixed, 80f * ImGuiHelpers.GlobalScale);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
foreach (var shell in _nearbySyncshells)
|
for (int i = 0; i < _nearbySyncshells.Count; i++)
|
||||||
{
|
{
|
||||||
// Check if there is an active broadcast for this syncshell, if not, skipping this syncshell
|
var shell = _nearbySyncshells[i];
|
||||||
var broadcast = _broadcastScannerService.GetActiveSyncshellBroadcasts()
|
|
||||||
.FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal));
|
|
||||||
|
|
||||||
if (broadcast == null)
|
|
||||||
continue; // no active broadcasts
|
|
||||||
|
|
||||||
var (Name, Address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID);
|
|
||||||
if (string.IsNullOrEmpty(Name))
|
|
||||||
continue; // broadcaster not found in area, skipping
|
|
||||||
|
|
||||||
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();
|
||||||
var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(Address);
|
ImGui.TextUnformatted(shell.Group.GID);
|
||||||
var 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}";
|
||||||
@@ -150,11 +123,6 @@ 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));
|
||||||
|
|
||||||
var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal));
|
|
||||||
var isRecentlyJoined = _recentlyJoined.Contains(shell.GID);
|
|
||||||
|
|
||||||
if (!isAlreadyMember && !isRecentlyJoined)
|
|
||||||
{
|
|
||||||
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})");
|
||||||
@@ -188,25 +156,19 @@ 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}");
|
||||||
@@ -228,14 +190,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);
|
finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);
|
||||||
|
|
||||||
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
|
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
|
||||||
|
|
||||||
_recentlyJoined.Add(_joinDto.Group.GID);
|
|
||||||
|
|
||||||
_joinDto = null;
|
_joinDto = null;
|
||||||
_joinInfo = null;
|
_joinInfo = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawPermissionRow(string label, bool suggested, bool current, Action<bool> apply)
|
private void DrawPermissionRow(string label, bool suggested, bool current, Action<bool> apply)
|
||||||
{
|
{
|
||||||
@@ -266,9 +224,6 @@ 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)];
|
|
||||||
|
|
||||||
_recentlyJoined.RemoveWhere(gid => _currentSyncshells.Any(s => string.Equals(s.GID, gid, StringComparison.Ordinal)));
|
|
||||||
|
|
||||||
if (syncshellBroadcasts.Count == 0)
|
if (syncshellBroadcasts.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -276,11 +231,11 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GroupJoinDto>? updatedList = [];
|
List<GroupJoinDto> updatedList;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false);
|
var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts);
|
||||||
updatedList = groups?.ToList();
|
updatedList = groups?.ToList() ?? new();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -288,10 +243,12 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal);
|
var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet();
|
||||||
|
var newGids = updatedList.Select(s => s.Group.GID).ToHashSet();
|
||||||
|
|
||||||
|
if (currentGids.SetEquals(newGids))
|
||||||
|
return;
|
||||||
|
|
||||||
if (updatedList != null)
|
|
||||||
{
|
|
||||||
var previousGid = GetSelectedGid();
|
var previousGid = GetSelectedGid();
|
||||||
|
|
||||||
_nearbySyncshells.Clear();
|
_nearbySyncshells.Clear();
|
||||||
@@ -299,14 +256,13 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
if (previousGid != null)
|
if (previousGid != null)
|
||||||
{
|
{
|
||||||
var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal));
|
var newIndex = _nearbySyncshells.FindIndex(s => s.Group.GID == previousGid);
|
||||||
if (newIndex >= 0)
|
if (newIndex >= 0)
|
||||||
{
|
{
|
||||||
_selectedNearbyIndex = newIndex;
|
_selectedNearbyIndex = newIndex;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ClearSelection();
|
ClearSelection();
|
||||||
}
|
}
|
||||||
@@ -335,4 +291,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
|
|||||||
return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
|
return _nearbySyncshells[_selectedNearbyIndex].Group.GID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,10 @@ 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;
|
||||||
|
|
||||||
@@ -27,25 +19,18 @@ 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, PairRequestService pairRequestService, DalamudUtilService dalamudUtilService)
|
public TopTabMenu(LightlessMediator lightlessMediator, ApiController apiController, PairManager pairManager, UiSharedService uiSharedService)
|
||||||
{
|
{
|
||||||
_lightlessMediator = lightlessMediator;
|
_lightlessMediator = lightlessMediator;
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
_pairRequestService = pairRequestService;
|
|
||||||
_dalamudUtilService = dalamudUtilService;
|
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +40,7 @@ public class TopTabMenu
|
|||||||
Individual,
|
Individual,
|
||||||
Syncshell,
|
Syncshell,
|
||||||
Lightfinder,
|
Lightfinder,
|
||||||
UserConfig,
|
UserConfig
|
||||||
Settings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Filter
|
public string Filter
|
||||||
@@ -83,7 +67,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 * 4)) / 5f;
|
var buttonX = (availableWidth - (spacing.X * 3)) / 4f;
|
||||||
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();
|
||||||
@@ -160,18 +144,6 @@ 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();
|
||||||
|
|
||||||
@@ -197,22 +169,6 @@ 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);
|
||||||
@@ -236,207 +192,6 @@ 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,7 +7,6 @@ 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;
|
||||||
@@ -174,14 +173,12 @@ 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", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
string[] suffix = ["B", "KiB", "MiB", "GiB", "TiB"];
|
||||||
int i = 0;
|
int i;
|
||||||
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 /= 1000.0;
|
dblSByte = bytes / 1024.0;
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
return addSuffix ? $"{dblSByte:0.00} {suffix[i]}" : $"{dblSByte:0.00}";
|
||||||
@@ -513,71 +510,10 @@ 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,23 +1,17 @@
|
|||||||
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 DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
|
public static SeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor)
|
||||||
{
|
{
|
||||||
var b = new DalamudSeStringBuilder();
|
var b = new SeStringBuilder();
|
||||||
|
|
||||||
if (glowColor is Vector4 glow)
|
if (glowColor is Vector4 glow)
|
||||||
b.Add(new GlowPayload(glow));
|
b.Add(new GlowPayload(glow));
|
||||||
@@ -36,47 +30,14 @@ public static class SeStringUtils
|
|||||||
return b.Build();
|
return b.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DalamudSeString BuildPlain(string text)
|
public static SeString BuildPlain(string text)
|
||||||
{
|
{
|
||||||
var b = new DalamudSeStringBuilder();
|
var b = new SeStringBuilder();
|
||||||
b.AddText(text ?? string.Empty);
|
b.AddText(text ?? string.Empty);
|
||||||
return b.Build();
|
return b.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DalamudSeString BuildRichText(ReadOnlySpan<RichTextEntry> fragments)
|
public static void RenderSeString(SeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
||||||
{
|
|
||||||
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();
|
||||||
|
|
||||||
@@ -90,36 +51,9 @@ 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 void RenderSeStringWrapped(DalamudSeString seString, float wrapWidth, ImFontPtr? font = null, ImDrawListPtr? drawList = null)
|
public static Vector2 RenderSeStringWithHitbox(SeString seString, Vector2 position, ImFontPtr? font = 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();
|
||||||
|
|
||||||
@@ -165,8 +99,6 @@ 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; }
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LightlessSync.UtilsEnum.Enum
|
|
||||||
{
|
|
||||||
public enum LabelAlignment
|
|
||||||
{
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using K4os.Compression.LZ4.Legacy;
|
using K4os.Compression.LZ4.Legacy;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto.Files;
|
using LightlessSync.API.Dto.Files;
|
||||||
@@ -8,7 +8,6 @@ using LightlessSync.PlayerData.Handlers;
|
|||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.WebAPI.Files.Models;
|
using LightlessSync.WebAPI.Files.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly FileCompactor _fileCompactor;
|
private readonly FileCompactor _fileCompactor;
|
||||||
private readonly FileCacheManager _fileDbManager;
|
private readonly FileCacheManager _fileDbManager;
|
||||||
private readonly FileTransferOrchestrator _orchestrator;
|
private readonly FileTransferOrchestrator _orchestrator;
|
||||||
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
|
private readonly List<ThrottledStream> _activeDownloadStreams;
|
||||||
|
|
||||||
public FileDownloadManager(ILogger<FileDownloadManager> logger, LightlessMediator mediator,
|
public FileDownloadManager(ILogger<FileDownloadManager> logger, LightlessMediator mediator,
|
||||||
FileTransferOrchestrator orchestrator,
|
FileTransferOrchestrator orchestrator,
|
||||||
@@ -30,14 +29,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
_orchestrator = orchestrator;
|
_orchestrator = orchestrator;
|
||||||
_fileDbManager = fileCacheManager;
|
_fileDbManager = fileCacheManager;
|
||||||
_fileCompactor = fileCompactor;
|
_fileCompactor = fileCompactor;
|
||||||
_activeDownloadStreams = new();
|
_activeDownloadStreams = [];
|
||||||
|
|
||||||
Mediator.Subscribe<DownloadLimitChangedMessage>(this, (msg) =>
|
Mediator.Subscribe<DownloadLimitChangedMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_activeDownloadStreams.IsEmpty) return;
|
if (!_activeDownloadStreams.Any()) return;
|
||||||
var newLimit = _orchestrator.DownloadLimitPerSlot();
|
var newLimit = _orchestrator.DownloadLimitPerSlot();
|
||||||
Logger.LogTrace("Setting new Download Speed Limit to {newLimit}", newLimit);
|
Logger.LogTrace("Setting new Download Speed Limit to {newLimit}", newLimit);
|
||||||
foreach (var stream in _activeDownloadStreams.Keys)
|
foreach (var stream in _activeDownloadStreams)
|
||||||
{
|
{
|
||||||
stream.BandwidthLimit = newLimit;
|
stream.BandwidthLimit = newLimit;
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public List<FileTransfer> ForbiddenTransfers => _orchestrator.ForbiddenTransfers;
|
public List<FileTransfer> ForbiddenTransfers => _orchestrator.ForbiddenTransfers;
|
||||||
|
|
||||||
public bool IsDownloading => CurrentDownloads.Any();
|
public bool IsDownloading => !CurrentDownloads.Any();
|
||||||
|
|
||||||
public static void MungeBuffer(Span<byte> buffer)
|
public static void MungeBuffer(Span<byte> buffer)
|
||||||
{
|
{
|
||||||
@@ -85,7 +84,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
ClearDownload();
|
ClearDownload();
|
||||||
foreach (var stream in _activeDownloadStreams.Keys.ToList())
|
foreach (var stream in _activeDownloadStreams.ToList())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -96,10 +95,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
// do nothing
|
// do nothing
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
_activeDownloadStreams.TryRemove(stream, out _);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
@@ -147,14 +142,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
await WaitForDownloadReady(fileTransfer, requestId, ct).ConfigureAwait(false);
|
await WaitForDownloadReady(fileTransfer, requestId, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_downloadStatus.TryGetValue(downloadGroup, out var downloadStatus))
|
_downloadStatus[downloadGroup].DownloadStatus = DownloadStatus.Downloading;
|
||||||
{
|
|
||||||
downloadStatus.DownloadStatus = DownloadStatus.Downloading;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogWarning("Download status missing for {group} when starting download", downloadGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
const int maxRetries = 3;
|
const int maxRetries = 3;
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
@@ -216,7 +204,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
stream = new(await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false), limit);
|
stream = new(await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false), limit);
|
||||||
|
|
||||||
_activeDownloadStreams.TryAdd(stream, 0);
|
_activeDownloadStreams.Add(stream);
|
||||||
|
|
||||||
while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
|
while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
|
||||||
{
|
{
|
||||||
@@ -257,7 +245,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
{
|
{
|
||||||
_activeDownloadStreams.TryRemove(stream, out _);
|
_activeDownloadStreams.Remove(stream);
|
||||||
await stream.DisposeAsync().ConfigureAwait(false);
|
await stream.DisposeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,28 +253,11 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public async Task<List<DownloadFileTransfer>> InitiateDownloadList(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct)
|
public async Task<List<DownloadFileTransfer>> InitiateDownloadList(GameObjectHandler gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var objectName = gameObjectHandler?.Name ?? "Unknown";
|
Logger.LogDebug("Download start: {id}", gameObjectHandler.Name);
|
||||||
Logger.LogDebug("Download start: {id}", objectName);
|
|
||||||
|
|
||||||
if (fileReplacement == null || fileReplacement.Count == 0)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("{dlName}: No file replacements provided", objectName);
|
|
||||||
CurrentDownloads = [];
|
|
||||||
return CurrentDownloads;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hashes = fileReplacement.Where(f => f != null && !string.IsNullOrWhiteSpace(f.Hash)).Select(f => f.Hash).Distinct(StringComparer.Ordinal).ToList();
|
|
||||||
|
|
||||||
if (hashes.Count == 0)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("{dlName}: No valid hashes to download", objectName);
|
|
||||||
CurrentDownloads = [];
|
|
||||||
return CurrentDownloads;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DownloadFileDto> downloadFileInfoFromService =
|
List<DownloadFileDto> downloadFileInfoFromService =
|
||||||
[
|
[
|
||||||
.. await FilesGetSizes(hashes, ct).ConfigureAwait(false),
|
.. await FilesGetSizes(fileReplacement.Select(f => f.Hash).Distinct(StringComparer.Ordinal).ToList(), ct).ConfigureAwait(false),
|
||||||
];
|
];
|
||||||
|
|
||||||
Logger.LogDebug("Files with size 0 or less: {files}", string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
|
Logger.LogDebug("Files with size 0 or less: {files}", string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
|
||||||
@@ -344,24 +315,16 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
FileInfo fi = new(blockFile);
|
FileInfo fi = new(blockFile);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_downloadStatus.TryGetValue(fileGroup.Key, out var downloadStatus))
|
_downloadStatus[fileGroup.Key].DownloadStatus = DownloadStatus.WaitingForSlot;
|
||||||
{
|
|
||||||
Logger.LogWarning("Download status missing for {group}, aborting", fileGroup.Key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadStatus.DownloadStatus = DownloadStatus.WaitingForSlot;
|
|
||||||
await _orchestrator.WaitForDownloadSlotAsync(token).ConfigureAwait(false);
|
await _orchestrator.WaitForDownloadSlotAsync(token).ConfigureAwait(false);
|
||||||
downloadStatus.DownloadStatus = DownloadStatus.WaitingForQueue;
|
_downloadStatus[fileGroup.Key].DownloadStatus = DownloadStatus.WaitingForQueue;
|
||||||
Progress<long> progress = new((bytesDownloaded) =>
|
Progress<long> progress = new((bytesDownloaded) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_downloadStatus.TryGetValue(fileGroup.Key, out FileDownloadStatus? value))
|
if (!_downloadStatus.TryGetValue(fileGroup.Key, out FileDownloadStatus? value)) return;
|
||||||
{
|
|
||||||
value.TransferredBytes += bytesDownloaded;
|
value.TransferredBytes += bytesDownloaded;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogWarning(ex, "Could not set download progress");
|
Logger.LogWarning(ex, "Could not set download progress");
|
||||||
@@ -390,12 +353,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
status.TransferredFiles = 1;
|
status.TransferredFiles = 1;
|
||||||
status.DownloadStatus = DownloadStatus.Decompressing;
|
status.DownloadStatus = DownloadStatus.Decompressing;
|
||||||
}
|
}
|
||||||
if (!File.Exists(blockFile))
|
|
||||||
{
|
|
||||||
Logger.LogWarning("{dlName}: Block file missing before extraction, skipping", fi.Name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fileBlockStream = File.OpenRead(blockFile);
|
fileBlockStream = File.OpenRead(blockFile);
|
||||||
while (fileBlockStream.Position < fileBlockStream.Length)
|
while (fileBlockStream.Position < fileBlockStream.Length)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto.Files;
|
using LightlessSync.API.Dto.Files;
|
||||||
using LightlessSync.API.Routes;
|
using LightlessSync.API.Routes;
|
||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
@@ -10,8 +10,6 @@ using LightlessSync.WebAPI.Files.Models;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace LightlessSync.WebAPI.Files;
|
namespace LightlessSync.WebAPI.Files;
|
||||||
|
|
||||||
@@ -21,9 +19,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly LightlessConfigService _lightlessConfigService;
|
private readonly LightlessConfigService _lightlessConfigService;
|
||||||
private readonly FileTransferOrchestrator _orchestrator;
|
private readonly FileTransferOrchestrator _orchestrator;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly ConcurrentDictionary<string, DateTime> _verifiedUploadedHashes = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, DateTime> _verifiedUploadedHashes = new(StringComparer.Ordinal);
|
||||||
private readonly object _currentUploadsLock = new();
|
|
||||||
private readonly Dictionary<string, FileTransfer> _currentUploadsByHash = new(StringComparer.Ordinal);
|
|
||||||
private CancellationTokenSource? _uploadCancellationTokenSource = new();
|
private CancellationTokenSource? _uploadCancellationTokenSource = new();
|
||||||
|
|
||||||
public FileUploadManager(ILogger<FileUploadManager> logger, LightlessMediator mediator,
|
public FileUploadManager(ILogger<FileUploadManager> logger, LightlessMediator mediator,
|
||||||
@@ -44,38 +40,17 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<FileTransfer> CurrentUploads { get; } = [];
|
public List<FileTransfer> CurrentUploads { get; } = [];
|
||||||
public bool IsUploading
|
public bool IsUploading => CurrentUploads.Count > 0;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
return CurrentUploads.Count > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<FileTransfer> GetCurrentUploadsSnapshot()
|
|
||||||
{
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
return CurrentUploads.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CancelUpload()
|
public bool CancelUpload()
|
||||||
{
|
{
|
||||||
if (IsUploading)
|
if (CurrentUploads.Any())
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Cancelling current upload");
|
Logger.LogDebug("Cancelling current upload");
|
||||||
_uploadCancellationTokenSource?.Cancel();
|
_uploadCancellationTokenSource?.Cancel();
|
||||||
_uploadCancellationTokenSource?.Dispose();
|
_uploadCancellationTokenSource?.Dispose();
|
||||||
_uploadCancellationTokenSource = null;
|
_uploadCancellationTokenSource = null;
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
_currentUploadsByHash.Clear();
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,44 +83,22 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
return [.. filesToUpload.Where(f => f.IsForbidden).Select(f => f.Hash)];
|
return [.. filesToUpload.Where(f => f.IsForbidden).Select(f => f.Hash)];
|
||||||
}
|
}
|
||||||
|
|
||||||
var cancellationToken = ct ?? CancellationToken.None;
|
Task uploadTask = Task.CompletedTask;
|
||||||
var parallelUploads = Math.Clamp(_lightlessConfigService.Current.ParallelUploads, 1, 8);
|
|
||||||
using SemaphoreSlim uploadSlots = new(parallelUploads, parallelUploads);
|
|
||||||
List<Task> uploadTasks = new();
|
|
||||||
|
|
||||||
int i = 1;
|
int i = 1;
|
||||||
foreach (var file in filesToUpload)
|
foreach (var file in filesToUpload)
|
||||||
{
|
{
|
||||||
progress.Report($"Uploading file {i++}/{filesToUpload.Count}. Please wait until the upload is completed.");
|
progress.Report($"Uploading file {i++}/{filesToUpload.Count}. Please wait until the upload is completed.");
|
||||||
uploadTasks.Add(UploadSingleFileAsync(file, uploadSlots, cancellationToken));
|
Logger.LogDebug("[{hash}] Compressing", file);
|
||||||
|
var data = await _fileDbManager.GetCompressedFileData(file.Hash, ct ?? CancellationToken.None).ConfigureAwait(false);
|
||||||
|
Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, _fileDbManager.GetFileCacheByHash(data.Item1)!.ResolvedFilepath);
|
||||||
|
await uploadTask.ConfigureAwait(false);
|
||||||
|
uploadTask = UploadFile(data.Item2, file.Hash, postProgress: false, ct ?? CancellationToken.None);
|
||||||
|
(ct ?? CancellationToken.None).ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(uploadTasks).ConfigureAwait(false);
|
await uploadTask.ConfigureAwait(false);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
async Task UploadSingleFileAsync(UploadFileDto fileDto, SemaphoreSlim gate, CancellationToken token)
|
|
||||||
{
|
|
||||||
await gate.WaitAsync(token).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
Logger.LogDebug("[{hash}] Compressing", fileDto.Hash);
|
|
||||||
var data = await _fileDbManager.GetCompressedFileData(fileDto.Hash, token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var cacheEntry = _fileDbManager.GetFileCacheByHash(data.Item1);
|
|
||||||
if (cacheEntry != null)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, cacheEntry.ResolvedFilepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
await UploadFile(data.Item2, fileDto.Hash, postProgress: false, token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
gate.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CharacterData> UploadFiles(CharacterData data, List<UserData> visiblePlayers)
|
public async Task<CharacterData> UploadFiles(CharacterData data, List<UserData> visiblePlayers)
|
||||||
@@ -214,11 +167,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
_uploadCancellationTokenSource?.Cancel();
|
_uploadCancellationTokenSource?.Cancel();
|
||||||
_uploadCancellationTokenSource?.Dispose();
|
_uploadCancellationTokenSource?.Dispose();
|
||||||
_uploadCancellationTokenSource = null;
|
_uploadCancellationTokenSource = null;
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
_currentUploadsByHash.Clear();
|
|
||||||
}
|
|
||||||
_verifiedUploadedHashes.Clear();
|
_verifiedUploadedHashes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,17 +211,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
lock (_currentUploadsLock)
|
CurrentUploads.Single(f => string.Equals(f.Hash, fileHash, StringComparison.Ordinal)).Transferred = prog.Uploaded;
|
||||||
{
|
|
||||||
if (_currentUploadsByHash.TryGetValue(fileHash, out var transfer))
|
|
||||||
{
|
|
||||||
transfer.Transferred = prog.Uploaded;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.LogDebug("[{hash}] Could not find upload transfer during progress update", fileHash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -301,16 +240,10 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var uploadTransfer = new UploadFileTransfer(file)
|
CurrentUploads.Add(new UploadFileTransfer(file)
|
||||||
{
|
{
|
||||||
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length,
|
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length,
|
||||||
};
|
});
|
||||||
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
CurrentUploads.Add(uploadTransfer);
|
|
||||||
_currentUploadsByHash[file.Hash] = uploadTransfer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -331,75 +264,33 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
|
|||||||
_verifiedUploadedHashes[file.Hash] = DateTime.UtcNow;
|
_verifiedUploadedHashes[file.Hash] = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
long totalSize;
|
var totalSize = CurrentUploads.Sum(c => c.Total);
|
||||||
List<FileTransfer> pendingUploads;
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
totalSize = CurrentUploads.Sum(c => c.Total);
|
|
||||||
pendingUploads = CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
var parallelUploads = Math.Clamp(_lightlessConfigService.Current.ParallelUploads, 1, 8);
|
|
||||||
using SemaphoreSlim uploadSlots = new(parallelUploads, parallelUploads);
|
|
||||||
Logger.LogDebug("Compressing and uploading files");
|
Logger.LogDebug("Compressing and uploading files");
|
||||||
List<Task> uploadTasks = new();
|
Task uploadTask = Task.CompletedTask;
|
||||||
|
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
|
||||||
foreach (var transfer in pendingUploads)
|
|
||||||
{
|
{
|
||||||
uploadTasks.Add(UploadPendingFileAsync(transfer, uploadSlots, uploadToken));
|
Logger.LogDebug("[{hash}] Compressing", file);
|
||||||
|
var data = await _fileDbManager.GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false);
|
||||||
|
CurrentUploads.Single(e => string.Equals(e.Hash, data.Item1, StringComparison.Ordinal)).Total = data.Item2.Length;
|
||||||
|
Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, _fileDbManager.GetFileCacheByHash(data.Item1)!.ResolvedFilepath);
|
||||||
|
await uploadTask.ConfigureAwait(false);
|
||||||
|
uploadTask = UploadFile(data.Item2, file.Hash, true, uploadToken);
|
||||||
|
uploadToken.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(uploadTasks).ConfigureAwait(false);
|
if (CurrentUploads.Any())
|
||||||
|
|
||||||
long compressedSize;
|
|
||||||
HashSet<string> uploadedHashes;
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
{
|
||||||
compressedSize = CurrentUploads.Sum(c => c.Total);
|
await uploadTask.ConfigureAwait(false);
|
||||||
uploadedHashes = CurrentUploads.Select(u => u.Hash).ToHashSet(StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var compressedSize = CurrentUploads.Sum(c => c.Total);
|
||||||
Logger.LogDebug("Upload complete, compressed {size} to {compressed}", UiSharedService.ByteToString(totalSize), UiSharedService.ByteToString(compressedSize));
|
Logger.LogDebug("Upload complete, compressed {size} to {compressed}", UiSharedService.ByteToString(totalSize), UiSharedService.ByteToString(compressedSize));
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var file in unverifiedUploadHashes.Where(c => !uploadedHashes.Contains(c)))
|
foreach (var file in unverifiedUploadHashes.Where(c => !CurrentUploads.Exists(u => string.Equals(u.Hash, c, StringComparison.Ordinal))))
|
||||||
{
|
{
|
||||||
_verifiedUploadedHashes[file] = DateTime.UtcNow;
|
_verifiedUploadedHashes[file] = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
_currentUploadsByHash.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task UploadPendingFileAsync(FileTransfer transfer, SemaphoreSlim gate, CancellationToken token)
|
|
||||||
{
|
|
||||||
await gate.WaitAsync(token).ConfigureAwait(false);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token.ThrowIfCancellationRequested();
|
|
||||||
Logger.LogDebug("[{hash}] Compressing", transfer.Hash);
|
|
||||||
var data = await _fileDbManager.GetCompressedFileData(transfer.Hash, token).ConfigureAwait(false);
|
|
||||||
lock (_currentUploadsLock)
|
|
||||||
{
|
|
||||||
if (_currentUploadsByHash.TryGetValue(data.Item1, out var trackedUpload))
|
|
||||||
{
|
|
||||||
trackedUpload.Total = data.Item2.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheEntry = _fileDbManager.GetFileCacheByHash(data.Item1);
|
|
||||||
if (cacheEntry != null)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, cacheEntry.ResolvedFilepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
await UploadFile(data.Item2, transfer.Hash, true, token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
gate.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,12 +134,6 @@ 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,21 +105,6 @@ 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;
|
||||||
@@ -292,25 +277,12 @@ 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,11 +45,6 @@ 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,7 +28,6 @@ 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;
|
||||||
@@ -43,13 +42,12 @@ 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, PairRequestService pairRequestService, ServerConfigurationManager serverManager, LightlessMediator mediator,
|
PairManager pairManager, 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;
|
||||||
@@ -79,10 +77,6 @@ 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));
|
||||||
@@ -430,7 +424,6 @@ 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));
|
||||||
@@ -452,7 +445,6 @@ 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));
|
||||||
|
|||||||
Reference in New Issue
Block a user