Merge remote-tracking branch 'origin/2.0.0-crashing-bugfixes' into 2.0.0-crashing-bugfixes
# Conflicts: # LightlessSync/Services/DalamudUtilService.cs # LightlessSync/UI/DtrEntry.cs
This commit is contained in:
@@ -1,11 +1,39 @@
|
|||||||
tagline: "Lightless Sync v2.0.1"
|
tagline: "Lightless Sync v2.0.1"
|
||||||
subline: "LIGHTLESS IS EVOLVING!!"
|
subline: "LIGHTLESS IS EVOLVING!!"
|
||||||
changelog:
|
changelog:
|
||||||
|
- name: "v2.0.2"
|
||||||
|
tagline: "Last update of 2025!... ... ... If Nothing breaks"
|
||||||
|
date: "December 28 2025"
|
||||||
|
# be sure to set this every new version
|
||||||
|
isCurrent: true
|
||||||
|
versions:
|
||||||
|
- number: "Chat"
|
||||||
|
icon: ""
|
||||||
|
items:
|
||||||
|
- "Added a 7TV emote picker to chat. You’ll now see a new button next to Send that opens an emote selector."
|
||||||
|
- "Pin User, Remove User, and Ban User (including Syncshell) have been added when you right click a user in chat."
|
||||||
|
- "Chatters now show status icons/labels in the Syncshell (e.g., Owner, Moderator, and Pinned when applicable)."
|
||||||
|
- "The Rules page no longer blocks input for other open Lightless UI windows."
|
||||||
|
- number: "LightFinder"
|
||||||
|
icon: ""
|
||||||
|
items:
|
||||||
|
- "If the ImGui Lightfinder icons aren’t working correctly, you can switch back to the Nameplate signature hook. Important warning - USE AT YOUR OWN RISK: The native nameplate hook can crash the game if multiple plugins hook the nameplate function at the same time. We will not provide support about Nameplate crashes, nor will the Dalamud team, **DO NOT BOTHER THEM.**"
|
||||||
|
- "The LightFinder label in the menu has a counter next to it showing the number of broadcasting users."
|
||||||
|
- "There is less interference of hidden UI elements for the imGui renderer of LightFinder."
|
||||||
|
- number: "Miscellaneous fixes"
|
||||||
|
icon: ""
|
||||||
|
items:
|
||||||
|
- "Overhauled transient resources in an attempt to mitigate mount and minion problems."
|
||||||
|
- "Some file cache entries will now be cached to reduce load on your game."
|
||||||
|
- "Downloading and decompressing have been redone to fix the locking issues."
|
||||||
|
- "Disabling the context menu will now hide the context menu on right clicks again. (Thanks @infiniti)"
|
||||||
|
- "Temporary collections that were not cleared before will now be cleared when the plugin starts."
|
||||||
|
- "Pair requests will now appear in chat if notifications are not enabled or on chat mode."
|
||||||
|
- "Fixed an instance were an object may be null in the Download UI."
|
||||||
|
- "API 14 - Migrate to IPlayerState service"
|
||||||
- name: "v2.0.1"
|
- name: "v2.0.1"
|
||||||
tagline: "Some Fixes"
|
tagline: "Some Fixes"
|
||||||
date: "December 23 2025"
|
date: "December 23 2025"
|
||||||
# be sure to set this every new version
|
|
||||||
isCurrent: true
|
|
||||||
versions:
|
versions:
|
||||||
- number: "Chat"
|
- number: "Chat"
|
||||||
icon: ""
|
icon: ""
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace LightlessSync.FileCache;
|
namespace LightlessSync.FileCache;
|
||||||
|
|
||||||
public class FileCacheEntity
|
public class FileCacheEntity
|
||||||
{
|
{
|
||||||
public FileCacheEntity(string hash, string path, string lastModifiedDateTicks, long? size = null, long? compressedSize = null)
|
[JsonConstructor]
|
||||||
|
public FileCacheEntity(
|
||||||
|
string hash,
|
||||||
|
string prefixedFilePath,
|
||||||
|
string lastModifiedDateTicks,
|
||||||
|
long? size = null,
|
||||||
|
long? compressedSize = null)
|
||||||
{
|
{
|
||||||
Size = size;
|
Size = size;
|
||||||
CompressedSize = compressedSize;
|
CompressedSize = compressedSize;
|
||||||
Hash = hash;
|
Hash = hash;
|
||||||
PrefixedFilePath = path;
|
PrefixedFilePath = prefixedFilePath;
|
||||||
LastModifiedDateTicks = lastModifiedDateTicks;
|
LastModifiedDateTicks = lastModifiedDateTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +31,5 @@ public class FileCacheEntity
|
|||||||
public long? Size { get; set; }
|
public long? Size { get; set; }
|
||||||
|
|
||||||
public void SetResolvedFilePath(string filePath)
|
public void SetResolvedFilePath(string filePath)
|
||||||
{
|
=> ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||||
ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ using Microsoft.Extensions.Hosting;
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace LightlessSync.FileCache;
|
namespace LightlessSync.FileCache;
|
||||||
@@ -31,6 +33,14 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
private bool _csvHeaderEnsured;
|
private bool _csvHeaderEnsured;
|
||||||
public string CacheFolder => _configService.Current.CacheFolder;
|
public string CacheFolder => _configService.Current.CacheFolder;
|
||||||
|
|
||||||
|
private const string _compressedCacheExtension = ".llz4";
|
||||||
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> _compressLocks = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private readonly ConcurrentDictionary<string, SizeInfo> _sizeCache =
|
||||||
|
new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
|
public readonly record struct SizeInfo(long Original, long Compressed);
|
||||||
|
|
||||||
public FileCacheManager(ILogger<FileCacheManager> logger, IpcManager ipcManager, LightlessConfigService configService, LightlessMediator lightlessMediator)
|
public FileCacheManager(ILogger<FileCacheManager> logger, IpcManager ipcManager, LightlessConfigService configService, LightlessMediator lightlessMediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@@ -45,6 +55,18 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
private static string NormalizeSeparators(string path) => path.Replace("/", "\\", StringComparison.Ordinal)
|
private static string NormalizeSeparators(string path) => path.Replace("/", "\\", StringComparison.Ordinal)
|
||||||
.Replace("\\\\", "\\", StringComparison.Ordinal);
|
.Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
private SemaphoreSlim GetCompressLock(string hash)
|
||||||
|
=> _compressLocks.GetOrAdd(hash, _ => new SemaphoreSlim(1, 1));
|
||||||
|
|
||||||
|
public void SetSizeInfo(string hash, long original, long compressed)
|
||||||
|
=> _sizeCache[hash] = new SizeInfo(original, compressed);
|
||||||
|
|
||||||
|
public bool TryGetSizeInfo(string hash, out SizeInfo info)
|
||||||
|
=> _sizeCache.TryGetValue(hash, out info);
|
||||||
|
|
||||||
|
private string GetCompressedCachePath(string hash)
|
||||||
|
=> Path.Combine(CacheFolder, hash + _compressedCacheExtension);
|
||||||
|
|
||||||
private static string NormalizePrefixedPathKey(string prefixedPath)
|
private static string NormalizePrefixedPathKey(string prefixedPath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(prefixedPath))
|
if (string.IsNullOrEmpty(prefixedPath))
|
||||||
@@ -111,6 +133,114 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
return int.TryParse(versionSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out version);
|
return int.TryParse(versionSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateSizeInfo(string hash, long? original = null, long? compressed = null)
|
||||||
|
{
|
||||||
|
_sizeCache.AddOrUpdate(
|
||||||
|
hash,
|
||||||
|
_ => new SizeInfo(original ?? 0, compressed ?? 0),
|
||||||
|
(_, old) => new SizeInfo(original ?? old.Original, compressed ?? old.Compressed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateEntitiesSizes(string hash, long original, long compressed)
|
||||||
|
{
|
||||||
|
if (_fileCaches.TryGetValue(hash, out var dict))
|
||||||
|
{
|
||||||
|
foreach (var e in dict.Values)
|
||||||
|
{
|
||||||
|
e.Size = original;
|
||||||
|
e.CompressedSize = compressed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ApplySizesToEntries(IEnumerable<FileCacheEntity?> entries, long original, long compressed)
|
||||||
|
{
|
||||||
|
foreach (var e in entries)
|
||||||
|
{
|
||||||
|
if (e == null) continue;
|
||||||
|
e.Size = original;
|
||||||
|
e.CompressedSize = compressed > 0 ? compressed : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetCompressedSizeAsync(string hash, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (_sizeCache.TryGetValue(hash, out var info) && info.Compressed > 0)
|
||||||
|
return info.Compressed;
|
||||||
|
|
||||||
|
if (_fileCaches.TryGetValue(hash, out var dict))
|
||||||
|
{
|
||||||
|
var any = dict.Values.FirstOrDefault();
|
||||||
|
if (any != null && any.CompressedSize > 0)
|
||||||
|
{
|
||||||
|
UpdateSizeInfo(hash, original: any.Size > 0 ? any.Size : null, compressed: any.CompressedSize);
|
||||||
|
return (long)any.CompressedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(CacheFolder))
|
||||||
|
{
|
||||||
|
var path = GetCompressedCachePath(hash);
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var len = new FileInfo(path).Length;
|
||||||
|
UpdateSizeInfo(hash, compressed: len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = await EnsureCompressedCacheBytesAsync(hash, token).ConfigureAwait(false);
|
||||||
|
return bytes.LongLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallback = await GetCompressedFileData(hash, token).ConfigureAwait(false);
|
||||||
|
return fallback.Item2.LongLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<byte[]> EnsureCompressedCacheBytesAsync(string hash, CancellationToken token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(CacheFolder))
|
||||||
|
throw new InvalidOperationException("CacheFolder is not set; cannot persist compressed cache.");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(CacheFolder);
|
||||||
|
|
||||||
|
var compressedPath = GetCompressedCachePath(hash);
|
||||||
|
|
||||||
|
if (File.Exists(compressedPath))
|
||||||
|
return await File.ReadAllBytesAsync(compressedPath, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var sem = GetCompressLock(hash);
|
||||||
|
await sem.WaitAsync(token).ConfigureAwait(false);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(compressedPath))
|
||||||
|
return await File.ReadAllBytesAsync(compressedPath, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var entity = GetFileCacheByHash(hash);
|
||||||
|
if (entity == null || string.IsNullOrWhiteSpace(entity.ResolvedFilepath))
|
||||||
|
throw new InvalidOperationException($"No local file cache found for hash {hash}.");
|
||||||
|
|
||||||
|
var sourcePath = entity.ResolvedFilepath;
|
||||||
|
var originalSize = new FileInfo(sourcePath).Length;
|
||||||
|
|
||||||
|
var raw = await File.ReadAllBytesAsync(sourcePath, token).ConfigureAwait(false);
|
||||||
|
var compressed = LZ4Wrapper.WrapHC(raw, 0, raw.Length);
|
||||||
|
|
||||||
|
var tmpPath = compressedPath + ".tmp";
|
||||||
|
await File.WriteAllBytesAsync(tmpPath, compressed, token).ConfigureAwait(false);
|
||||||
|
File.Move(tmpPath, compressedPath, overwrite: true);
|
||||||
|
|
||||||
|
var compressedSize = compressed.LongLength;
|
||||||
|
SetSizeInfo(hash, originalSize, compressedSize);
|
||||||
|
UpdateEntitiesSizes(hash, originalSize, compressedSize);
|
||||||
|
|
||||||
|
return compressed;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
sem.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string NormalizeToPrefixedPath(string path)
|
private string NormalizeToPrefixedPath(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path)) return string.Empty;
|
if (string.IsNullOrEmpty(path)) return string.Empty;
|
||||||
@@ -318,9 +448,18 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
|
|
||||||
public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(CacheFolder))
|
||||||
|
{
|
||||||
|
var bytes = await EnsureCompressedCacheBytesAsync(fileHash, uploadToken).ConfigureAwait(false);
|
||||||
|
UpdateSizeInfo(fileHash, compressed: bytes.LongLength);
|
||||||
|
return (fileHash, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath;
|
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath;
|
||||||
return (fileHash, LZ4Wrapper.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false), 0,
|
var raw = await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false);
|
||||||
(int)new FileInfo(fileCache).Length));
|
var compressed = LZ4Wrapper.WrapHC(raw, 0, raw.Length);
|
||||||
|
UpdateSizeInfo(fileHash, original: raw.LongLength, compressed: compressed.LongLength);
|
||||||
|
return (fileHash, compressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileCacheEntity? GetFileCacheByHash(string hash)
|
public FileCacheEntity? GetFileCacheByHash(string hash)
|
||||||
@@ -891,6 +1030,14 @@ public sealed class FileCacheManager : IHostedService
|
|||||||
compressed = resultCompressed;
|
compressed = resultCompressed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size > 0 || compressed > 0)
|
||||||
|
{
|
||||||
|
UpdateSizeInfo(hash,
|
||||||
|
original: size > 0 ? size : null,
|
||||||
|
compressed: compressed > 0 ? compressed : null);
|
||||||
|
}
|
||||||
|
|
||||||
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
|
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
private void RefreshPlayerRelatedAddressMap()
|
private void RefreshPlayerRelatedAddressMap()
|
||||||
{
|
{
|
||||||
_playerRelatedByAddress.Clear();
|
_playerRelatedByAddress.Clear();
|
||||||
|
var updatedFrameAddresses = new ConcurrentDictionary<nint, ObjectKind>();
|
||||||
lock (_playerRelatedLock)
|
lock (_playerRelatedLock)
|
||||||
{
|
{
|
||||||
foreach (var handler in _playerRelatedPointers)
|
foreach (var handler in _playerRelatedPointers)
|
||||||
@@ -360,9 +361,12 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
if (address != nint.Zero)
|
if (address != nint.Zero)
|
||||||
{
|
{
|
||||||
_playerRelatedByAddress[address] = handler;
|
_playerRelatedByAddress[address] = handler;
|
||||||
|
updatedFrameAddresses[address] = handler.ObjectKind;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cachedFrameAddresses = updatedFrameAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
||||||
@@ -497,9 +501,16 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var gameObjectAddress = msg.GameObject;
|
var gameObjectAddress = msg.GameObject;
|
||||||
if (!_cachedFrameAddresses.TryGetValue(gameObjectAddress, out var objectKind))
|
if (!_cachedFrameAddresses.TryGetValue(gameObjectAddress, out var objectKind))
|
||||||
|
{
|
||||||
|
if (_actorObjectService.TryGetOwnedKind(gameObjectAddress, out var ownedKind))
|
||||||
|
{
|
||||||
|
objectKind = ownedKind;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var gamePath = NormalizeGamePath(msg.GamePath);
|
var gamePath = NormalizeGamePath(msg.GamePath);
|
||||||
if (string.IsNullOrEmpty(gamePath))
|
if (string.IsNullOrEmpty(gamePath))
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ public sealed class IpcCallerPenumbra : IpcServiceBase
|
|||||||
public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
|
public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] reverse)
|
||||||
=> _resources.ResolvePathsAsync(forward, reverse);
|
=> _resources.ResolvePathsAsync(forward, reverse);
|
||||||
|
|
||||||
|
public string ResolveGameObjectPath(string gamePath, int objectIndex)
|
||||||
|
=> _resources.ResolveGameObjectPath(gamePath, objectIndex);
|
||||||
|
|
||||||
|
public string[] ReverseResolveGameObjectPath(string moddedPath, int objectIndex)
|
||||||
|
=> _resources.ReverseResolveGameObjectPath(moddedPath, objectIndex);
|
||||||
|
|
||||||
public Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
public Task RedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
|
||||||
=> _redraw.RedrawAsync(logger, handler, applicationId, token);
|
=> _redraw.RedrawAsync(logger, handler, applicationId, token);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public sealed class PenumbraResource : PenumbraBase
|
|||||||
{
|
{
|
||||||
private readonly ActorObjectService _actorObjectService;
|
private readonly ActorObjectService _actorObjectService;
|
||||||
private readonly GetGameObjectResourcePaths _gameObjectResourcePaths;
|
private readonly GetGameObjectResourcePaths _gameObjectResourcePaths;
|
||||||
|
private readonly ResolveGameObjectPath _resolveGameObjectPath;
|
||||||
|
private readonly ReverseResolveGameObjectPath _reverseResolveGameObjectPath;
|
||||||
private readonly ResolvePlayerPathsAsync _resolvePlayerPaths;
|
private readonly ResolvePlayerPathsAsync _resolvePlayerPaths;
|
||||||
private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations;
|
private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations;
|
||||||
private readonly EventSubscriber<nint, string, string> _gameObjectResourcePathResolved;
|
private readonly EventSubscriber<nint, string, string> _gameObjectResourcePathResolved;
|
||||||
@@ -27,6 +29,8 @@ public sealed class PenumbraResource : PenumbraBase
|
|||||||
{
|
{
|
||||||
_actorObjectService = actorObjectService;
|
_actorObjectService = actorObjectService;
|
||||||
_gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface);
|
_gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface);
|
||||||
|
_resolveGameObjectPath = new ResolveGameObjectPath(pluginInterface);
|
||||||
|
_reverseResolveGameObjectPath = new ReverseResolveGameObjectPath(pluginInterface);
|
||||||
_resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface);
|
_resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface);
|
||||||
_getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface);
|
_getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface);
|
||||||
_gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded);
|
_gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded);
|
||||||
@@ -67,7 +71,13 @@ public sealed class PenumbraResource : PenumbraBase
|
|||||||
return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false);
|
return await _resolvePlayerPaths.Invoke(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleResourceLoaded(nint ptr, string resolvedPath, string gamePath)
|
public string ResolveGameObjectPath(string gamePath, int gameObjectIndex)
|
||||||
|
=> IsAvailable ? _resolveGameObjectPath.Invoke(gamePath, gameObjectIndex) : gamePath;
|
||||||
|
|
||||||
|
public string[] ReverseResolveGameObjectPath(string moddedPath, int gameObjectIndex)
|
||||||
|
=> IsAvailable ? _reverseResolveGameObjectPath.Invoke(moddedPath, gameObjectIndex) : Array.Empty<string>();
|
||||||
|
|
||||||
|
private void HandleResourceLoaded(nint ptr, string gamePath, string resolvedPath)
|
||||||
{
|
{
|
||||||
if (ptr == nint.Zero)
|
if (ptr == nint.Zero)
|
||||||
{
|
{
|
||||||
@@ -79,12 +89,12 @@ public sealed class PenumbraResource : PenumbraBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Compare(resolvedPath, gamePath, StringComparison.OrdinalIgnoreCase) == 0)
|
if (string.Compare(gamePath, resolvedPath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Mediator.Publish(new PenumbraResourceLoadMessage(ptr, resolvedPath, gamePath));
|
Mediator.Publish(new PenumbraResourceLoadMessage(ptr, gamePath, resolvedPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
// get all remaining paths and resolve them
|
// get all remaining paths and resolve them
|
||||||
var transientPaths = ManageSemiTransientData(objectKind);
|
var transientPaths = ManageSemiTransientData(objectKind);
|
||||||
var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
var resolvedTransientPaths = await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (logDebug)
|
if (logDebug)
|
||||||
{
|
{
|
||||||
@@ -373,11 +373,73 @@ public class PlayerDataFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IReadOnlyDictionary<string, string[]>> GetFileReplacementsFromPaths(HashSet<string> forwardResolve, HashSet<string> reverseResolve)
|
private async Task<IReadOnlyDictionary<string, string[]>> GetFileReplacementsFromPaths(GameObjectHandler handler, HashSet<string> forwardResolve, HashSet<string> reverseResolve)
|
||||||
{
|
{
|
||||||
var forwardPaths = forwardResolve.ToArray();
|
var forwardPaths = forwardResolve.ToArray();
|
||||||
var reversePaths = reverseResolve.ToArray();
|
var reversePaths = reverseResolve.ToArray();
|
||||||
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
||||||
|
if (handler.ObjectKind != ObjectKind.Player)
|
||||||
|
{
|
||||||
|
var (objectIndex, forwardResolved, reverseResolved) = await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
var idx = handler.GetGameObject()?.ObjectIndex;
|
||||||
|
if (!idx.HasValue)
|
||||||
|
{
|
||||||
|
return ((int?)null, Array.Empty<string>(), Array.Empty<string[]>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedForward = new string[forwardPaths.Length];
|
||||||
|
for (int i = 0; i < forwardPaths.Length; i++)
|
||||||
|
{
|
||||||
|
resolvedForward[i] = _ipcManager.Penumbra.ResolveGameObjectPath(forwardPaths[i], idx.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var resolvedReverse = new string[reversePaths.Length][];
|
||||||
|
for (int i = 0; i < reversePaths.Length; i++)
|
||||||
|
{
|
||||||
|
resolvedReverse[i] = _ipcManager.Penumbra.ReverseResolveGameObjectPath(reversePaths[i], idx.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (idx, resolvedForward, resolvedReverse);
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (objectIndex.HasValue)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < forwardPaths.Length; i++)
|
||||||
|
{
|
||||||
|
var filePath = forwardResolved[i]?.ToLowerInvariant();
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
|
{
|
||||||
|
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < reversePaths.Length; i++)
|
||||||
|
{
|
||||||
|
var filePath = reversePaths[i].ToLowerInvariant();
|
||||||
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
|
{
|
||||||
|
list.AddRange(reverseResolved[i].Select(c => c.ToLowerInvariant()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resolvedPaths[filePath] = new List<string>(reverseResolved[i].Select(c => c.ToLowerInvariant()).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (forward, reverse) = await _ipcManager.Penumbra.ResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false);
|
var (forward, reverse) = await _ipcManager.Penumbra.ResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||||
for (int i = 0; i < forwardPaths.Length; i++)
|
for (int i = 0; i < forwardPaths.Length; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -484,7 +484,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
sp.GetRequiredService<PairUiService>(),
|
sp.GetRequiredService<PairUiService>(),
|
||||||
sp.GetRequiredService<DalamudUtilService>(),
|
sp.GetRequiredService<DalamudUtilService>(),
|
||||||
sp.GetRequiredService<LightlessProfileManager>(),
|
sp.GetRequiredService<LightlessProfileManager>(),
|
||||||
sp.GetRequiredService<ActorObjectService>()));
|
sp.GetRequiredService<ActorObjectService>(),
|
||||||
|
sp.GetRequiredService<LightFinderPlateHandler>()));
|
||||||
|
|
||||||
services.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
services.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
||||||
services.AddScoped<IPopupHandler, CensusPopupHandler>();
|
services.AddScoped<IPopupHandler, CensusPopupHandler>();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
@@ -98,11 +99,13 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
_analysisCts = null;
|
_analysisCts = null;
|
||||||
if (print) PrintAnalysis();
|
if (print) PrintAnalysis();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_analysisCts.CancelDispose();
|
_analysisCts.CancelDispose();
|
||||||
_baseAnalysisCts.Dispose();
|
_baseAnalysisCts.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateFileEntriesAsync(IEnumerable<string> filePaths, CancellationToken token)
|
public async Task UpdateFileEntriesAsync(IEnumerable<string> filePaths, CancellationToken token)
|
||||||
{
|
{
|
||||||
var normalized = new HashSet<string>(
|
var normalized = new HashSet<string>(
|
||||||
@@ -125,6 +128,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
|
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
|
||||||
@@ -136,29 +140,47 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var fileCacheEntries = (await _fileCacheManager.GetAllFileCachesByHashAsync(fileEntry.Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false)).ToList();
|
var fileCacheEntries = (await _fileCacheManager
|
||||||
if (fileCacheEntries.Count == 0) continue;
|
.GetAllFileCachesByHashAsync(fileEntry.Hash, ignoreCacheEntries: true, validate: false, token)
|
||||||
var filePath = fileCacheEntries[0].ResolvedFilepath;
|
.ConfigureAwait(false))
|
||||||
FileInfo fi = new(filePath);
|
.ToList();
|
||||||
string ext = "unk?";
|
|
||||||
try
|
if (fileCacheEntries.Count == 0)
|
||||||
{
|
continue;
|
||||||
ext = fi.Extension[1..];
|
|
||||||
}
|
var resolved = fileCacheEntries[0].ResolvedFilepath;
|
||||||
catch (Exception ex)
|
|
||||||
{
|
var extWithDot = Path.GetExtension(resolved);
|
||||||
Logger.LogWarning(ex, "Could not identify extension for {path}", filePath);
|
var ext = string.IsNullOrEmpty(extWithDot) ? "unk?" : extWithDot.TrimStart('.');
|
||||||
}
|
|
||||||
var tris = await _xivDataAnalyzer.GetTrianglesByHash(fileEntry.Hash).ConfigureAwait(false);
|
var tris = await _xivDataAnalyzer.GetTrianglesByHash(fileEntry.Hash).ConfigureAwait(false);
|
||||||
foreach (var entry in fileCacheEntries)
|
|
||||||
|
var distinctFilePaths = fileCacheEntries
|
||||||
|
.Select(c => c.ResolvedFilepath)
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
long orig = 0, comp = 0;
|
||||||
|
var first = fileCacheEntries[0];
|
||||||
|
if (first.Size > 0) orig = first.Size.Value;
|
||||||
|
if (first.CompressedSize > 0) comp = first.CompressedSize.Value;
|
||||||
|
|
||||||
|
if (_fileCacheManager.TryGetSizeInfo(fileEntry.Hash, out var cached))
|
||||||
{
|
{
|
||||||
data[fileEntry.Hash] = new FileDataEntry(fileEntry.Hash, ext,
|
if (orig <= 0 && cached.Original > 0) orig = cached.Original;
|
||||||
[.. fileEntry.GamePaths],
|
if (comp <= 0 && cached.Compressed > 0) comp = cached.Compressed;
|
||||||
[.. fileCacheEntries.Select(c => c.ResolvedFilepath).Distinct(StringComparer.Ordinal)],
|
|
||||||
entry.Size > 0 ? entry.Size.Value : 0,
|
|
||||||
entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0,
|
|
||||||
tris);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data[fileEntry.Hash] = new FileDataEntry(
|
||||||
|
fileEntry.Hash,
|
||||||
|
ext,
|
||||||
|
[.. fileEntry.GamePaths],
|
||||||
|
distinctFilePaths,
|
||||||
|
orig,
|
||||||
|
comp,
|
||||||
|
tris,
|
||||||
|
fileCacheEntries);
|
||||||
}
|
}
|
||||||
LastAnalysis[obj.Key] = data;
|
LastAnalysis[obj.Key] = data;
|
||||||
}
|
}
|
||||||
@@ -167,6 +189,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||||
_lastDataHash = charaData.DataHash.Value;
|
_lastDataHash = charaData.DataHash.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RecalculateSummary()
|
private void RecalculateSummary()
|
||||||
{
|
{
|
||||||
var builder = ImmutableDictionary.CreateBuilder<ObjectKind, CharacterAnalysisObjectSummary>();
|
var builder = ImmutableDictionary.CreateBuilder<ObjectKind, CharacterAnalysisObjectSummary>();
|
||||||
@@ -192,6 +215,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
|
|
||||||
_latestSummary = new CharacterAnalysisSummary(builder.ToImmutable());
|
_latestSummary = new CharacterAnalysisSummary(builder.ToImmutable());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintAnalysis()
|
private void PrintAnalysis()
|
||||||
{
|
{
|
||||||
if (LastAnalysis.Count == 0) return;
|
if (LastAnalysis.Count == 0) return;
|
||||||
@@ -235,42 +259,79 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
|||||||
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize))));
|
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize))));
|
||||||
Logger.LogInformation("IMPORTANT NOTES:\n\r- For Lightless up- and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
|
Logger.LogInformation("IMPORTANT NOTES:\n\r- For Lightless up- and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
|
||||||
}
|
}
|
||||||
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize, long Triangles)
|
|
||||||
|
internal sealed class FileDataEntry
|
||||||
{
|
{
|
||||||
|
public string Hash { get; }
|
||||||
|
public string FileType { get; }
|
||||||
|
public List<string> GamePaths { get; }
|
||||||
|
public List<string> FilePaths { get; }
|
||||||
|
|
||||||
|
public long OriginalSize { get; private set; }
|
||||||
|
public long CompressedSize { get; private set; }
|
||||||
|
public long Triangles { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<FileCacheEntity> CacheEntries { get; }
|
||||||
|
|
||||||
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
||||||
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token)
|
|
||||||
|
public FileDataEntry(
|
||||||
|
string hash,
|
||||||
|
string fileType,
|
||||||
|
List<string> gamePaths,
|
||||||
|
List<string> filePaths,
|
||||||
|
long originalSize,
|
||||||
|
long compressedSize,
|
||||||
|
long triangles,
|
||||||
|
IReadOnlyList<FileCacheEntity> cacheEntries)
|
||||||
{
|
{
|
||||||
var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false);
|
Hash = hash;
|
||||||
var normalSize = new FileInfo(FilePaths[0]).Length;
|
FileType = fileType;
|
||||||
var entries = await fileCacheManager.GetAllFileCachesByHashAsync(Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false);
|
GamePaths = gamePaths;
|
||||||
foreach (var entry in entries)
|
FilePaths = filePaths;
|
||||||
{
|
OriginalSize = originalSize;
|
||||||
entry.Size = normalSize;
|
CompressedSize = compressedSize;
|
||||||
entry.CompressedSize = compressedsize.Item2.LongLength;
|
Triangles = triangles;
|
||||||
|
CacheEntries = cacheEntries;
|
||||||
}
|
}
|
||||||
OriginalSize = normalSize;
|
|
||||||
CompressedSize = compressedsize.Item2.LongLength;
|
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token, bool force = false)
|
||||||
|
{
|
||||||
|
if (!force && IsComputed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (FilePaths.Count == 0 || string.IsNullOrWhiteSpace(FilePaths[0]))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var path = FilePaths[0];
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var original = new FileInfo(path).Length;
|
||||||
|
|
||||||
|
var compressedLen = await fileCacheManager.GetCompressedSizeAsync(Hash, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
fileCacheManager.SetSizeInfo(Hash, original, compressedLen);
|
||||||
|
FileCacheManager.ApplySizesToEntries(CacheEntries, original, compressedLen);
|
||||||
|
|
||||||
|
OriginalSize = original;
|
||||||
|
CompressedSize = compressedLen;
|
||||||
|
|
||||||
|
if (string.Equals(FileType, "tex", StringComparison.OrdinalIgnoreCase))
|
||||||
RefreshFormat();
|
RefreshFormat();
|
||||||
}
|
}
|
||||||
public long OriginalSize { get; private set; } = OriginalSize;
|
|
||||||
public long CompressedSize { get; private set; } = CompressedSize;
|
|
||||||
public long Triangles { get; private set; } = Triangles;
|
|
||||||
public Lazy<string> Format => _format ??= CreateFormatValue();
|
|
||||||
|
|
||||||
|
public Lazy<string> Format => _format ??= CreateFormatValue();
|
||||||
private Lazy<string>? _format;
|
private Lazy<string>? _format;
|
||||||
|
|
||||||
public void RefreshFormat()
|
public void RefreshFormat() => _format = CreateFormatValue();
|
||||||
{
|
|
||||||
_format = CreateFormatValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Lazy<string> CreateFormatValue()
|
private Lazy<string> CreateFormatValue()
|
||||||
=> new(() =>
|
=> new(() =>
|
||||||
{
|
{
|
||||||
if (!string.Equals(FileType, "tex", StringComparison.Ordinal))
|
if (!string.Equals(FileType, "tex", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Text;
|
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
using LightlessSync.API.Dto.CharaData;
|
||||||
@@ -28,7 +26,6 @@ using System.Text;
|
|||||||
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
|
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
|
||||||
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||||
using Map = Lumina.Excel.Sheets.Map;
|
|
||||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
namespace LightlessSync.Services;
|
||||||
@@ -85,18 +82,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
_configService = configService;
|
_configService = configService;
|
||||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
_pairFactory = pairFactory;
|
_pairFactory = pairFactory;
|
||||||
|
var clientLanguage = _clientState.ClientLanguage;
|
||||||
WorldData = new(() =>
|
WorldData = new(() =>
|
||||||
{
|
{
|
||||||
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(Dalamud.Game.ClientLanguage.English)!
|
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(clientLanguage)!
|
||||||
.Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0])))
|
.Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0])))
|
||||||
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
||||||
});
|
});
|
||||||
JobData = new(() =>
|
JobData = new(() =>
|
||||||
{
|
{
|
||||||
return gameData.GetExcelSheet<ClassJob>(Dalamud.Game.ClientLanguage.English)!
|
return gameData.GetExcelSheet<ClassJob>(clientLanguage)!
|
||||||
.ToDictionary(k => k.RowId, k => k.NameEnglish.ToString());
|
.ToDictionary(k => k.RowId, k => k.Name.ToString());
|
||||||
});
|
});
|
||||||
var clientLanguage = _clientState.ClientLanguage;
|
|
||||||
TerritoryData = new(() => BuildTerritoryData(clientLanguage));
|
TerritoryData = new(() => BuildTerritoryData(clientLanguage));
|
||||||
TerritoryDataEnglish = new(() => BuildTerritoryData(Dalamud.Game.ClientLanguage.English));
|
TerritoryDataEnglish = new(() => BuildTerritoryData(Dalamud.Game.ClientLanguage.English));
|
||||||
MapData = new(() => BuildMapData(clientLanguage));
|
MapData = new(() => BuildMapData(clientLanguage));
|
||||||
@@ -662,7 +659,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
|
|
||||||
var location = new LocationInfo();
|
var location = new LocationInfo();
|
||||||
location.ServerId = _playerState.CurrentWorld.RowId;
|
location.ServerId = _playerState.CurrentWorld.RowId;
|
||||||
location.InstanceId = UIState.Instance()->PublicInstance.InstanceId;
|
//location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first
|
||||||
location.TerritoryId = _clientState.TerritoryType;
|
location.TerritoryId = _clientState.TerritoryType;
|
||||||
location.MapId = _clientState.MapId;
|
location.MapId = _clientState.MapId;
|
||||||
if (houseMan != null)
|
if (houseMan != null)
|
||||||
@@ -716,10 +713,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.InstanceId is not 0)
|
// if (location.InstanceId is not 0)
|
||||||
{
|
// {
|
||||||
str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
// str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (location.WardId is not 0)
|
if (location.WardId is not 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ using Pictomancy;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Task = System.Threading.Tasks.Task;
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
private readonly LightlessConfigService _configService;
|
private readonly LightlessConfigService _configService;
|
||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly LightlessMediator _mediator;
|
private readonly LightlessMediator _mediator;
|
||||||
|
|
||||||
public LightlessMediator Mediator => _mediator;
|
public LightlessMediator Mediator => _mediator;
|
||||||
|
|
||||||
private readonly IUiBuilder _uiBuilder;
|
private readonly IUiBuilder _uiBuilder;
|
||||||
@@ -71,6 +73,12 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
private readonly List<RectF> _uiRects = new(128);
|
private readonly List<RectF> _uiRects = new(128);
|
||||||
private ImmutableHashSet<string> _activeBroadcastingCids = [];
|
private ImmutableHashSet<string> _activeBroadcastingCids = [];
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// Debug controls
|
||||||
|
|
||||||
|
// Debug counters (read-only from UI)
|
||||||
|
#endif
|
||||||
|
|
||||||
private bool IsPictomancyRenderer => _configService.Current.LightfinderLabelRenderer == LightfinderLabelRenderer.Pictomancy;
|
private bool IsPictomancyRenderer => _configService.Current.LightfinderLabelRenderer == LightfinderLabelRenderer.Pictomancy;
|
||||||
|
|
||||||
public LightFinderPlateHandler(
|
public LightFinderPlateHandler(
|
||||||
@@ -96,7 +104,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
_uiBuilder = pluginInterface.UiBuilder ?? throw new ArgumentNullException(nameof(pluginInterface));
|
_uiBuilder = pluginInterface.UiBuilder ?? throw new ArgumentNullException(nameof(pluginInterface));
|
||||||
_ = pictomancyService ?? throw new ArgumentNullException(nameof(pictomancyService));
|
_ = pictomancyService ?? throw new ArgumentNullException(nameof(pictomancyService));
|
||||||
_lastRenderer = _configService.Current.LightfinderLabelRenderer;
|
_lastRenderer = _configService.Current.LightfinderLabelRenderer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshRendererState()
|
private void RefreshRendererState()
|
||||||
@@ -187,8 +194,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Draw detour for nameplate addon.
|
/// Draw detour for nameplate addon.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type"></param>
|
|
||||||
/// <param name="args"></param>
|
|
||||||
private void NameplateDrawDetour(AddonEvent type, AddonArgs args)
|
private void NameplateDrawDetour(AddonEvent type, AddonArgs args)
|
||||||
{
|
{
|
||||||
RefreshRendererState();
|
RefreshRendererState();
|
||||||
@@ -199,6 +204,16 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide our overlay when the user hides the entire game UI (ScrollLock).
|
||||||
|
if (_gameGui.GameUiHidden)
|
||||||
|
{
|
||||||
|
ClearLabelBuffer();
|
||||||
|
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||||
|
_lastNamePlateDrawFrame = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gpose: do not draw.
|
||||||
if (_clientState.IsGPosing)
|
if (_clientState.IsGPosing)
|
||||||
{
|
{
|
||||||
ClearLabelBuffer();
|
ClearLabelBuffer();
|
||||||
@@ -218,6 +233,10 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (fw != null)
|
if (fw != null)
|
||||||
_lastNamePlateDrawFrame = fw->FrameCounter;
|
_lastNamePlateDrawFrame = fw->FrameCounter;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||||
|
#endif
|
||||||
|
|
||||||
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
|
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
|
||||||
|
|
||||||
if (_mpNameplateAddon != pNameplateAddon)
|
if (_mpNameplateAddon != pNameplateAddon)
|
||||||
@@ -234,6 +253,13 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void UpdateNameplateNodes()
|
private void UpdateNameplateNodes()
|
||||||
{
|
{
|
||||||
|
// If the user has hidden the UI, don't compute any labels.
|
||||||
|
if (_gameGui.GameUiHidden)
|
||||||
|
{
|
||||||
|
ClearLabelBuffer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var currentHandle = _gameGui.GetAddonByName("NamePlate");
|
var currentHandle = _gameGui.GetAddonByName("NamePlate");
|
||||||
if (currentHandle.Address == nint.Zero)
|
if (currentHandle.Address == nint.Zero)
|
||||||
{
|
{
|
||||||
@@ -297,7 +323,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
|
|
||||||
for (int i = 0; i < safeCount; ++i)
|
for (int i = 0; i < safeCount; ++i)
|
||||||
{
|
{
|
||||||
|
|
||||||
var objectInfoPtr = vec[i];
|
var objectInfoPtr = vec[i];
|
||||||
if (objectInfoPtr == null)
|
if (objectInfoPtr == null)
|
||||||
continue;
|
continue;
|
||||||
@@ -314,7 +339,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if ((ObjectKind)gameObject->ObjectKind != ObjectKind.Player)
|
if ((ObjectKind)gameObject->ObjectKind != ObjectKind.Player)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// CID gating
|
// CID gating - only show for active broadcasters
|
||||||
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)gameObject);
|
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)gameObject);
|
||||||
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
||||||
continue;
|
continue;
|
||||||
@@ -350,12 +375,12 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (!currentConfig.LightfinderLabelShowHidden && !isNameplateVisible)
|
if (!currentConfig.LightfinderLabelShowHidden && !isNameplateVisible)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Prepare label content and scaling
|
// Prepare label content and scaling factors
|
||||||
var scaleMultiplier = System.Math.Clamp(currentConfig.LightfinderLabelScale, 0.5f, 2.0f);
|
var scaleMultiplier = Math.Clamp(currentConfig.LightfinderLabelScale, 0.5f, 2.0f);
|
||||||
var baseScale = currentConfig.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
var baseScale = currentConfig.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
||||||
var effectiveScale = baseScale * scaleMultiplier;
|
var effectiveScale = baseScale * scaleMultiplier;
|
||||||
var baseFontSize = currentConfig.LightfinderLabelUseIcon ? 36f : 24f;
|
var baseFontSize = currentConfig.LightfinderLabelUseIcon ? 36f : 24f;
|
||||||
var targetFontSize = (int)System.Math.Round(baseFontSize * scaleMultiplier);
|
var targetFontSize = (int)Math.Round(baseFontSize * scaleMultiplier);
|
||||||
var labelContent = currentConfig.LightfinderLabelUseIcon
|
var labelContent = currentConfig.LightfinderLabelUseIcon
|
||||||
? NormalizeIconGlyph(currentConfig.LightfinderLabelIconGlyph)
|
? NormalizeIconGlyph(currentConfig.LightfinderLabelIconGlyph)
|
||||||
: _defaultLabelText;
|
: _defaultLabelText;
|
||||||
@@ -363,8 +388,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (!currentConfig.LightfinderLabelUseIcon && (string.IsNullOrWhiteSpace(labelContent) || string.Equals(labelContent, "-", StringComparison.Ordinal)))
|
if (!currentConfig.LightfinderLabelUseIcon && (string.IsNullOrWhiteSpace(labelContent) || string.Equals(labelContent, "-", StringComparison.Ordinal)))
|
||||||
labelContent = _defaultLabelText;
|
labelContent = _defaultLabelText;
|
||||||
|
|
||||||
var nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
var nodeWidth = (int)Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
||||||
var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
|
var nodeHeight = (int)Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
|
||||||
AlignmentType alignment;
|
AlignmentType alignment;
|
||||||
|
|
||||||
var textScaleY = nameText->AtkResNode.ScaleY;
|
var textScaleY = nameText->AtkResNode.ScaleY;
|
||||||
@@ -374,7 +399,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
var blockHeight = ResolveCache(
|
var blockHeight = ResolveCache(
|
||||||
_buffers.TextHeights,
|
_buffers.TextHeights,
|
||||||
nameplateIndex,
|
nameplateIndex,
|
||||||
System.Math.Abs((int)nameplateObject.TextH),
|
Math.Abs((int)nameplateObject.TextH),
|
||||||
() => GetScaledTextHeight(nameText),
|
() => GetScaledTextHeight(nameText),
|
||||||
nodeHeight);
|
nodeHeight);
|
||||||
|
|
||||||
@@ -384,7 +409,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
(int)nameContainer->Height,
|
(int)nameContainer->Height,
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
var computed = blockHeight + (int)System.Math.Round(8 * textScaleY);
|
var computed = blockHeight + (int)Math.Round(8 * textScaleY);
|
||||||
return computed <= blockHeight ? blockHeight + 1 : computed;
|
return computed <= blockHeight ? blockHeight + 1 : computed;
|
||||||
},
|
},
|
||||||
blockHeight + 1);
|
blockHeight + 1);
|
||||||
@@ -392,7 +417,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
var blockTop = containerHeight - blockHeight;
|
var blockTop = containerHeight - blockHeight;
|
||||||
if (blockTop < 0)
|
if (blockTop < 0)
|
||||||
blockTop = 0;
|
blockTop = 0;
|
||||||
var verticalPadding = (int)System.Math.Round(4 * effectiveScale);
|
var verticalPadding = (int)Math.Round(4 * effectiveScale);
|
||||||
|
|
||||||
var positionY = blockTop - verticalPadding;
|
var positionY = blockTop - verticalPadding;
|
||||||
|
|
||||||
@@ -400,21 +425,14 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
var textWidth = ResolveCache(
|
var textWidth = ResolveCache(
|
||||||
_buffers.TextWidths,
|
_buffers.TextWidths,
|
||||||
nameplateIndex,
|
nameplateIndex,
|
||||||
System.Math.Abs(rawTextWidth),
|
Math.Abs(rawTextWidth),
|
||||||
() => GetScaledTextWidth(nameText),
|
() => GetScaledTextWidth(nameText),
|
||||||
nodeWidth);
|
nodeWidth);
|
||||||
|
|
||||||
// Text offset caching
|
// Text offset caching
|
||||||
var textOffset = (int)System.Math.Round(nameText->AtkResNode.X);
|
var textOffset = (int)Math.Round(nameText->AtkResNode.X);
|
||||||
var hasValidOffset = TryCacheTextOffset(nameplateIndex, rawTextWidth, textOffset);
|
var hasValidOffset = TryCacheTextOffset(nameplateIndex, rawTextWidth, textOffset);
|
||||||
|
|
||||||
if (nameContainer == null)
|
|
||||||
{
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
|
||||||
_logger.LogDebug("Nameplate {Index} container became unavailable during update, skipping.", nameplateIndex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = nameContainer;
|
var res = nameContainer;
|
||||||
|
|
||||||
// X scale
|
// X scale
|
||||||
@@ -450,7 +468,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
|
|
||||||
var offsetXScreen = currentConfig.LightfinderLabelOffsetX * worldScaleX;
|
var offsetXScreen = currentConfig.LightfinderLabelOffsetX * worldScaleX;
|
||||||
|
|
||||||
// alignment based on config
|
// alignment based on config setting
|
||||||
switch (currentConfig.LabelAlignment)
|
switch (currentConfig.LabelAlignment)
|
||||||
{
|
{
|
||||||
case LabelAlignment.Left:
|
case LabelAlignment.Left:
|
||||||
@@ -469,7 +487,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// manual X positioning
|
// manual X positioning with optional cached offset
|
||||||
var cachedTextOffset = _buffers.TextOffsets[nameplateIndex];
|
var cachedTextOffset = _buffers.TextOffsets[nameplateIndex];
|
||||||
var hasCachedOffset = cachedTextOffset != int.MinValue;
|
var hasCachedOffset = cachedTextOffset != int.MinValue;
|
||||||
var baseOffsetXLocal = (!currentConfig.LightfinderLabelUseIcon && hasValidOffset && hasCachedOffset)
|
var baseOffsetXLocal = (!currentConfig.LightfinderLabelUseIcon && hasValidOffset && hasCachedOffset)
|
||||||
@@ -489,16 +507,16 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
|
|
||||||
// final position before smoothing
|
// final position before smoothing
|
||||||
var finalPosition = new Vector2(finalX, res->ScreenY + positionYScreen);
|
var finalPosition = new Vector2(finalX, res->ScreenY + positionYScreen);
|
||||||
var dpiScale = ImGui.GetIO().DisplayFramebufferScale.X; // often same for Y
|
var dpiScale = ImGui.GetIO().DisplayFramebufferScale.X;
|
||||||
var fw = Framework.Instance();
|
var fw = Framework.Instance();
|
||||||
float dt = fw->RealFrameDeltaTime;
|
float dt = fw->RealFrameDeltaTime;
|
||||||
|
|
||||||
//smoothing..
|
//smoothing.. snap.. smooth.. snap
|
||||||
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
||||||
finalPosition = SmoothPosition(nameplateIndex, finalPosition, dt);
|
finalPosition = SmoothPosition(nameplateIndex, finalPosition, dt);
|
||||||
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
||||||
|
|
||||||
// prepare label info
|
// prepare label info for rendering
|
||||||
var pivot = (currentConfig.LightfinderAutoAlign || currentConfig.LightfinderLabelUseIcon)
|
var pivot = (currentConfig.LightfinderAutoAlign || currentConfig.LightfinderLabelUseIcon)
|
||||||
? AlignmentToPivot(alignment)
|
? AlignmentToPivot(alignment)
|
||||||
: _defaultPivot;
|
: _defaultPivot;
|
||||||
@@ -545,7 +563,23 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (fw == null)
|
if (fw == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Frame skip check
|
// If UI is hidden, do not render.
|
||||||
|
if (_gameGui.GameUiHidden)
|
||||||
|
{
|
||||||
|
ClearLabelBuffer();
|
||||||
|
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||||
|
_lastNamePlateDrawFrame = 0;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = 0;
|
||||||
|
DebugUiRectCountLastFrame = 0;
|
||||||
|
DebugOccludedCountLastFrame = 0;
|
||||||
|
DebugLastNameplateFrame = 0;
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame skip check - skip if more than 1 frame has passed since last nameplate draw.
|
||||||
var frame = fw->FrameCounter;
|
var frame = fw->FrameCounter;
|
||||||
|
|
||||||
if (_lastNamePlateDrawFrame == 0 || (frame - _lastNamePlateDrawFrame) > 1)
|
if (_lastNamePlateDrawFrame == 0 || (frame - _lastNamePlateDrawFrame) > 1)
|
||||||
@@ -553,34 +587,62 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
ClearLabelBuffer();
|
ClearLabelBuffer();
|
||||||
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = 0;
|
||||||
|
DebugUiRectCountLastFrame = 0;
|
||||||
|
DebugOccludedCountLastFrame = 0;
|
||||||
|
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Gpose Check
|
// Gpose Check - do not render.
|
||||||
if (_clientState.IsGPosing)
|
if (_clientState.IsGPosing)
|
||||||
{
|
{
|
||||||
ClearLabelBuffer();
|
ClearLabelBuffer();
|
||||||
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||||
_lastNamePlateDrawFrame = 0;
|
_lastNamePlateDrawFrame = 0;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = 0;
|
||||||
|
DebugUiRectCountLastFrame = 0;
|
||||||
|
DebugOccludedCountLastFrame = 0;
|
||||||
|
DebugLastNameplateFrame = 0;
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If nameplate addon is not visible, skip rendering
|
// If nameplate addon is not visible, skip rendering entirely.
|
||||||
if (!IsNamePlateAddonVisible())
|
if (!IsNamePlateAddonVisible())
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = 0;
|
||||||
|
DebugUiRectCountLastFrame = 0;
|
||||||
|
DebugOccludedCountLastFrame = 0;
|
||||||
|
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int copyCount;
|
int copyCount;
|
||||||
lock (_labelLock)
|
lock (_labelLock)
|
||||||
{
|
{
|
||||||
copyCount = _labelRenderCount;
|
copyCount = _labelRenderCount;
|
||||||
if (copyCount == 0)
|
if (copyCount == 0)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = 0;
|
||||||
|
DebugUiRectCountLastFrame = 0;
|
||||||
|
DebugOccludedCountLastFrame = 0;
|
||||||
|
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Array.Copy(_buffers.LabelRender, _buffers.LabelCopy, copyCount);
|
Array.Copy(_buffers.LabelRender, _buffers.LabelCopy, copyCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
var uiModule = fw != null ? fw->GetUIModule() : null;
|
var uiModule = fw->GetUIModule();
|
||||||
|
|
||||||
if (uiModule != null)
|
if (uiModule != null)
|
||||||
{
|
{
|
||||||
var rapture = uiModule->GetRaptureAtkModule();
|
var rapture = uiModule->GetRaptureAtkModule();
|
||||||
@@ -599,7 +661,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
var vpPos = vp.Pos;
|
var vpPos = vp.Pos;
|
||||||
|
|
||||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||||
|
|
||||||
ImGui.SetNextWindowPos(vp.Pos);
|
ImGui.SetNextWindowPos(vp.Pos);
|
||||||
ImGui.SetNextWindowSize(vp.Size);
|
ImGui.SetNextWindowSize(vp.Size);
|
||||||
|
|
||||||
@@ -610,19 +671,39 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
|
|
||||||
ImGui.PopStyleVar(2);
|
ImGui.PopStyleVar(2);
|
||||||
|
|
||||||
|
// Debug flags
|
||||||
|
bool dbgEnabled = false;
|
||||||
|
bool dbgDisableOcc = false;
|
||||||
|
bool dbgDrawUiRects = false;
|
||||||
|
bool dbgDrawLabelRects = false;
|
||||||
|
#if DEBUG
|
||||||
|
dbgEnabled = DebugEnabled;
|
||||||
|
dbgDisableOcc = DebugDisableOcclusion;
|
||||||
|
dbgDrawUiRects = DebugDrawUiRects;
|
||||||
|
dbgDrawLabelRects = DebugDrawLabelRects;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int occludedThisFrame = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
using var drawList = PictoService.Draw();
|
using var drawList = PictoService.Draw();
|
||||||
if (drawList == null)
|
if (drawList == null)
|
||||||
{
|
|
||||||
ImGui.End();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
// Debug drawing uses the window drawlist (so it always draws in the correct viewport).
|
||||||
|
var dbgDl = ImGui.GetWindowDrawList();
|
||||||
|
var useViewportOffset = ImGui.GetIO().ConfigFlags.HasFlag(ImGuiConfigFlags.ViewportsEnable);
|
||||||
|
|
||||||
for (int i = 0; i < copyCount; ++i)
|
for (int i = 0; i < copyCount; ++i)
|
||||||
{
|
{
|
||||||
ref var info = ref _buffers.LabelCopy[i];
|
ref var info = ref _buffers.LabelCopy[i];
|
||||||
|
|
||||||
// final draw position with viewport offset
|
// final draw position with viewport offset (only when viewports are enabled)
|
||||||
var drawPos = info.ScreenPosition + vpPos;
|
var drawPos = info.ScreenPosition;
|
||||||
|
if (useViewportOffset)
|
||||||
|
drawPos += vpPos;
|
||||||
|
|
||||||
var font = default(ImFontPtr);
|
var font = default(ImFontPtr);
|
||||||
if (info.UseIcon)
|
if (info.UseIcon)
|
||||||
{
|
{
|
||||||
@@ -648,16 +729,60 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
var scale = baseFontSize > 0 ? (info.FontSize / baseFontSize) : 1f;
|
var scale = baseFontSize > 0 ? (info.FontSize / baseFontSize) : 1f;
|
||||||
var size = baseSize * scale;
|
var size = baseSize * scale;
|
||||||
|
|
||||||
// label rect for occlusion checking
|
|
||||||
var topLeft = info.ScreenPosition - new Vector2(size.X * info.Pivot.X, size.Y * info.Pivot.Y);
|
var topLeft = info.ScreenPosition - new Vector2(size.X * info.Pivot.X, size.Y * info.Pivot.Y);
|
||||||
var labelRect = new RectF(topLeft.X, topLeft.Y, topLeft.X + size.X, topLeft.Y + size.Y);
|
var labelRect = new RectF(topLeft.X, topLeft.Y, topLeft.X + size.X, topLeft.Y + size.Y);
|
||||||
|
|
||||||
// occlusion check
|
bool wouldOcclude = IsOccludedByAnyUi(labelRect);
|
||||||
if (IsOccludedByAnyUi(labelRect))
|
if (wouldOcclude)
|
||||||
|
occludedThisFrame++;
|
||||||
|
|
||||||
|
// Debug: draw label rects
|
||||||
|
if (dbgEnabled && dbgDrawLabelRects)
|
||||||
|
{
|
||||||
|
var tl = new Vector2(labelRect.L, labelRect.T);
|
||||||
|
var br = new Vector2(labelRect.R, labelRect.B);
|
||||||
|
|
||||||
|
if (useViewportOffset) { tl += vpPos; br += vpPos; }
|
||||||
|
|
||||||
|
// green = visible, red = would be occluded (even if forced)
|
||||||
|
var col = wouldOcclude
|
||||||
|
? ImGui.GetColorU32(new Vector4(1f, 0f, 0f, 0.6f))
|
||||||
|
: ImGui.GetColorU32(new Vector4(0f, 1f, 0f, 0.6f));
|
||||||
|
|
||||||
|
dbgDl.AddRect(tl, br, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// occlusion check (allow debug to disable)
|
||||||
|
if (!dbgDisableOcc && wouldOcclude)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
drawList.AddScreenText(drawPos, info.Text, info.TextColor, info.FontSize, info.Pivot, info.EdgeColor, font);
|
drawList.AddScreenText(drawPos, info.Text, info.TextColor, info.FontSize, info.Pivot, info.EdgeColor, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug: draw UI rects if any
|
||||||
|
if (dbgEnabled && dbgDrawUiRects && _uiRects.Count > 0)
|
||||||
|
{
|
||||||
|
var useOff = useViewportOffset ? vpPos : Vector2.Zero;
|
||||||
|
var col = ImGui.GetColorU32(new Vector4(1f, 1f, 1f, 0.35f));
|
||||||
|
|
||||||
|
for (int i = 0; i < _uiRects.Count; i++)
|
||||||
|
{
|
||||||
|
var r = _uiRects[i];
|
||||||
|
dbgDl.AddRect(new Vector2(r.L, r.T) + useOff, new Vector2(r.R, r.B) + useOff, col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugLabelCountLastFrame = copyCount;
|
||||||
|
DebugUiRectCountLastFrame = _uiRects.Count;
|
||||||
|
DebugOccludedCountLastFrame = occludedThisFrame;
|
||||||
|
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector2 AlignmentToPivot(AlignmentType alignment) => alignment switch
|
private static Vector2 AlignmentToPivot(AlignmentType alignment) => alignment switch
|
||||||
@@ -705,8 +830,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (scale <= 0f)
|
if (scale <= 0f)
|
||||||
scale = 1f;
|
scale = 1f;
|
||||||
|
|
||||||
var computed = (int)System.Math.Round(rawHeight * scale);
|
var computed = (int)Math.Round(rawHeight * scale);
|
||||||
return System.Math.Max(1, computed);
|
return Math.Max(1, computed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe int GetScaledTextWidth(AtkTextNode* node)
|
private static unsafe int GetScaledTextWidth(AtkTextNode* node)
|
||||||
@@ -730,12 +855,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves a cached value for the given index.
|
/// Resolves a cached value for the given index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cache"></param>
|
|
||||||
/// <param name="index"></param>
|
|
||||||
/// <param name="rawValue"></param>
|
|
||||||
/// <param name="fallback"></param>
|
|
||||||
/// <param name="fallbackWhenZero"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static int ResolveCache(
|
private static int ResolveCache(
|
||||||
int[] cache,
|
int[] cache,
|
||||||
int index,
|
int index,
|
||||||
@@ -775,9 +894,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Snapping a position to pixel grid based on DPI scale.
|
/// Snapping a position to pixel grid based on DPI scale.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="p">Position</param>
|
|
||||||
/// <param name="dpiScale">DPI Scale</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static Vector2 SnapToPixels(Vector2 p, float dpiScale)
|
private static Vector2 SnapToPixels(Vector2 p, float dpiScale)
|
||||||
{
|
{
|
||||||
// snap to pixel grid
|
// snap to pixel grid
|
||||||
@@ -786,15 +902,9 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
return new Vector2(x, y);
|
return new Vector2(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Smooths the position using exponential smoothing.
|
/// Smooths the position using exponential smoothing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="idx">Nameplate Index</param>
|
|
||||||
/// <param name="target">Final position</param>
|
|
||||||
/// <param name="dt">Delta Time</param>
|
|
||||||
/// <param name="responsiveness">How responssive the smooting should be</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Vector2 SmoothPosition(int idx, Vector2 target, float dt, float responsiveness = 24f)
|
private Vector2 SmoothPosition(int idx, Vector2 target, float dt, float responsiveness = 24f)
|
||||||
{
|
{
|
||||||
// exponential smoothing
|
// exponential smoothing
|
||||||
@@ -821,73 +931,193 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
return cur;
|
return cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
/// Tries to get a valid screen rect for the given addon.
|
private static bool IsFinite(float f) => !(float.IsNaN(f) || float.IsInfinity(f));
|
||||||
/// </summary>
|
|
||||||
/// <param name="addon">Addon UI</param>
|
|
||||||
/// <param name="screen">Screen positioning/param>
|
|
||||||
/// <param name="rect">RectF of Addon</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private static bool TryGetAddonRect(AtkUnitBase* addon, Vector2 screen, out RectF rect)
|
private static bool TryGetAddonRect(AtkUnitBase* addon, Vector2 screen, out RectF rect)
|
||||||
{
|
{
|
||||||
// Addon existence
|
|
||||||
rect = default;
|
rect = default;
|
||||||
if (addon == null)
|
if (addon == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Visibility check
|
// Addon must be visible
|
||||||
|
if (!addon->IsVisible)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Root must be visible
|
||||||
var root = addon->RootNode;
|
var root = addon->RootNode;
|
||||||
if (root == null || !root->IsVisible())
|
if (root == null || !root->IsVisible())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Size check
|
// Must have multiple nodes to be useful
|
||||||
float w = root->Width;
|
var nodeCount = addon->UldManager.NodeListCount;
|
||||||
float h = root->Height;
|
var nodeList = addon->UldManager.NodeList;
|
||||||
if (w <= 0 || h <= 0)
|
if (nodeCount <= 1 || nodeList == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Local scale
|
float rsx = GetWorldScaleX(root);
|
||||||
float sx = root->ScaleX; if (sx <= 0f) sx = 1f;
|
float rsy = GetWorldScaleY(root);
|
||||||
float sy = root->ScaleY; if (sy <= 0f) sy = 1f;
|
if (!IsFinite(rsx) || rsx <= 0f) rsx = 1f;
|
||||||
|
if (!IsFinite(rsy) || rsy <= 0f) rsy = 1f;
|
||||||
|
|
||||||
// World/composed scale from Transform
|
// clamp insane root scales (rare but prevents explosions)
|
||||||
float wsx = GetWorldScaleX(root);
|
rsx = MathF.Min(rsx, 6f);
|
||||||
float wsy = GetWorldScaleY(root);
|
rsy = MathF.Min(rsy, 6f);
|
||||||
if (wsx <= 0f) wsx = 1f;
|
|
||||||
if (wsy <= 0f) wsy = 1f;
|
|
||||||
|
|
||||||
// World scale may include parent scaling; use it if meaningfully different.
|
float rw = root->Width * rsx;
|
||||||
float useX = MathF.Abs(wsx - sx) > 0.01f ? wsx : sx;
|
float rh = root->Height * rsy;
|
||||||
float useY = MathF.Abs(wsy - sy) > 0.01f ? wsy : sy;
|
if (!IsFinite(rw) || !IsFinite(rh) || rw <= 2f || rh <= 2f)
|
||||||
|
|
||||||
w *= useX;
|
|
||||||
h *= useY;
|
|
||||||
|
|
||||||
if (w < 4f || h < 4f)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Screen coords
|
float rl = root->ScreenX;
|
||||||
float l = root->ScreenX;
|
float rt = root->ScreenY;
|
||||||
float t = root->ScreenY;
|
if (!IsFinite(rl) || !IsFinite(rt))
|
||||||
float r = l + w;
|
|
||||||
float b = t + h;
|
|
||||||
|
|
||||||
// Drop fullscreen-ish / insane rects
|
|
||||||
if (w >= screen.X * 0.98f && h >= screen.Y * 0.98f)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Drop offscreen rects
|
float rr = rl + rw;
|
||||||
if (l < -screen.X || t < -screen.Y || r > screen.X * 2f || b > screen.Y * 2f)
|
float rb = rt + rh;
|
||||||
|
|
||||||
|
// If root is basically fullscreen, it<69>s not a useful occluder for our purpose.
|
||||||
|
if (rw >= screen.X * 0.98f && rh >= screen.Y * 0.98f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Clip root to screen so it stays sane
|
||||||
|
float rootL = MathF.Max(0f, rl);
|
||||||
|
float rootT = MathF.Max(0f, rt);
|
||||||
|
float rootR = MathF.Min(screen.X, rr);
|
||||||
|
float rootB = MathF.Min(screen.Y, rb);
|
||||||
|
if (rootR <= rootL || rootB <= rootT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Root dimensions
|
||||||
|
var rootW = rootR - rootL;
|
||||||
|
var rootH = rootB - rootT;
|
||||||
|
|
||||||
|
// Find union of all probably-drawable nodes intersecting root
|
||||||
|
bool any = false;
|
||||||
|
float l = float.MaxValue, t = float.MaxValue, r = float.MinValue, b = float.MinValue;
|
||||||
|
|
||||||
|
// Allow a small bleed outside root; some addons draw small bits outside their root container.
|
||||||
|
const float rootPad = 24f;
|
||||||
|
float padL = rootL - rootPad;
|
||||||
|
float padT = rootT - rootPad;
|
||||||
|
float padR = rootR + rootPad;
|
||||||
|
float padB = rootB + rootPad;
|
||||||
|
|
||||||
|
for (int i = 1; i < nodeCount; i++)
|
||||||
|
{
|
||||||
|
var n = nodeList[i];
|
||||||
|
if (!IsProbablyDrawableNode(n))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float w = n->Width;
|
||||||
|
float h = n->Height;
|
||||||
|
if (!IsFinite(w) || !IsFinite(h) || w <= 1f || h <= 1f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float sx = GetWorldScaleX(n);
|
||||||
|
float sy = GetWorldScaleY(n);
|
||||||
|
|
||||||
|
if (!IsFinite(sx) || sx <= 0f) sx = 1f;
|
||||||
|
if (!IsFinite(sy) || sy <= 0f) sy = 1f;
|
||||||
|
|
||||||
|
sx = MathF.Min(sx, 6f);
|
||||||
|
sy = MathF.Min(sy, 6f);
|
||||||
|
|
||||||
|
w *= sx;
|
||||||
|
h *= sy;
|
||||||
|
|
||||||
|
if (!IsFinite(w) || !IsFinite(h) || w < 2f || h < 2f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float nl = n->ScreenX;
|
||||||
|
float nt = n->ScreenY;
|
||||||
|
if (!IsFinite(nl) || !IsFinite(nt))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float nr = nl + w;
|
||||||
|
float nb = nt + h;
|
||||||
|
|
||||||
|
// Must intersect root (with padding). This is the big mitigation.
|
||||||
|
if (nr <= padL || nb <= padT || nl >= padR || nt >= padB)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Reject nodes that are wildly larger than the root (common on targeting).
|
||||||
|
if (w > rootW * 2.0f || h > rootH * 2.0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Clip node to root and then to screen (prevents offscreen junk stretching union)
|
||||||
|
float cl = MathF.Max(rootL, nl);
|
||||||
|
float ct = MathF.Max(rootT, nt);
|
||||||
|
float cr = MathF.Min(rootR, nr);
|
||||||
|
float cb = MathF.Min(rootB, nb);
|
||||||
|
|
||||||
|
cl = MathF.Max(0f, cl);
|
||||||
|
ct = MathF.Max(0f, ct);
|
||||||
|
cr = MathF.Min(screen.X, cr);
|
||||||
|
cb = MathF.Min(screen.Y, cb);
|
||||||
|
|
||||||
|
if (cr <= cl || cb <= ct)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
any = true;
|
||||||
|
if (cl < l) l = cl;
|
||||||
|
if (ct < t) t = ct;
|
||||||
|
if (cr > r) r = cr;
|
||||||
|
if (cb > b) b = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing usable, fallback to root rect (still a sane occluder)
|
||||||
|
if (!any)
|
||||||
|
{
|
||||||
|
rect = new RectF(rootL, rootT, rootR, rootB);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate final union rect
|
||||||
|
var uw = r - l;
|
||||||
|
var uh = b - t;
|
||||||
|
if (uw < 4f || uh < 4f)
|
||||||
|
{
|
||||||
|
rect = new RectF(rootL, rootT, rootR, rootB);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If union is excessively larger than root, fallback to root rect
|
||||||
|
if (uw > rootW * 1.35f || uh > rootH * 1.35f)
|
||||||
|
{
|
||||||
|
rect = new RectF(rootL, rootT, rootR, rootB);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
rect = new RectF(l, t, r, b);
|
rect = new RectF(l, t, r, b);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsProbablyDrawableNode(AtkResNode* n)
|
||||||
|
{
|
||||||
|
if (n == null || !n->IsVisible())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check alpha
|
||||||
|
if (n->Color.A == 16)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Check node type
|
||||||
|
return n->Type switch
|
||||||
|
{
|
||||||
|
NodeType.Text => true,
|
||||||
|
NodeType.Image => true,
|
||||||
|
NodeType.NineGrid => true,
|
||||||
|
NodeType.Counter => true,
|
||||||
|
NodeType.Component => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes the cached UI rects for occlusion checking.
|
/// Refreshes the cached UI rects for occlusion checking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="unitMgr">Unit Manager</param>
|
|
||||||
private void RefreshUiRects(RaptureAtkUnitManager* unitMgr)
|
private void RefreshUiRects(RaptureAtkUnitManager* unitMgr)
|
||||||
{
|
{
|
||||||
_uiRects.Clear();
|
_uiRects.Clear();
|
||||||
@@ -911,13 +1141,15 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
if (TryGetAddonRect(addon, screen, out var r))
|
if (TryGetAddonRect(addon, screen, out var r))
|
||||||
_uiRects.Add(r);
|
_uiRects.Add(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
DebugUiRectCountLastFrame = _uiRects.Count;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is the given label rect occluded by any UI rects?
|
/// Is the given label rect occluded by any UI rects?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="labelRect">UI/Label Rect</param>
|
|
||||||
/// <returns>Is occluded or not</returns>
|
|
||||||
private bool IsOccludedByAnyUi(RectF labelRect)
|
private bool IsOccludedByAnyUi(RectF labelRect)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _uiRects.Count; i++)
|
for (int i = 0; i < _uiRects.Count; i++)
|
||||||
@@ -931,8 +1163,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the world scale X of the given node.
|
/// Gets the world scale X of the given node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="n">Node</param>
|
|
||||||
/// <returns>World Scale of node</returns>
|
|
||||||
private static float GetWorldScaleX(AtkResNode* n)
|
private static float GetWorldScaleX(AtkResNode* n)
|
||||||
{
|
{
|
||||||
var t = n->Transform;
|
var t = n->Transform;
|
||||||
@@ -942,8 +1172,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the world scale Y of the given node.
|
/// Gets the world scale Y of the given node.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="n">Node</param>
|
|
||||||
/// <returns>World Scale of node</returns>
|
|
||||||
private static float GetWorldScaleY(AtkResNode* n)
|
private static float GetWorldScaleY(AtkResNode* n)
|
||||||
{
|
{
|
||||||
var t = n->Transform;
|
var t = n->Transform;
|
||||||
@@ -953,8 +1181,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalize an icon glyph input into a valid string.
|
/// Normalize an icon glyph input into a valid string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rawInput">Raw glyph input</param>
|
|
||||||
/// <returns>Normalized glyph input</returns>
|
|
||||||
internal static string NormalizeIconGlyph(string? rawInput)
|
internal static string NormalizeIconGlyph(string? rawInput)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(rawInput))
|
if (string.IsNullOrWhiteSpace(rawInput))
|
||||||
@@ -982,7 +1208,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is the nameplate addon visible?
|
/// Is the nameplate addon visible?
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Is it visible?</returns>
|
|
||||||
private bool IsNamePlateAddonVisible()
|
private bool IsNamePlateAddonVisible()
|
||||||
{
|
{
|
||||||
if (_mpNameplateAddon == null)
|
if (_mpNameplateAddon == null)
|
||||||
@@ -992,20 +1217,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
return root != null && root->IsVisible();
|
return root != null && root->IsVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts raw icon glyph input into an icon editor string.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rawInput">Raw icon glyph input</param>
|
|
||||||
/// <returns>Icon editor string</returns>
|
|
||||||
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 readonly struct NameplateLabelInfo
|
private readonly struct NameplateLabelInfo
|
||||||
{
|
{
|
||||||
public NameplateLabelInfo(
|
public NameplateLabelInfo(
|
||||||
@@ -1043,6 +1254,15 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
||||||
.Select(u => (ulong)u.PlayerCharacterId)];
|
.Select(u => (ulong)u.PlayerCharacterId)];
|
||||||
|
|
||||||
|
public int DebugLabelCountLastFrame { get; set; }
|
||||||
|
public int DebugUiRectCountLastFrame { get; set; }
|
||||||
|
public int DebugOccludedCountLastFrame { get; set; }
|
||||||
|
public uint DebugLastNameplateFrame { get; set; }
|
||||||
|
public bool DebugDrawUiRects { get; set; }
|
||||||
|
public bool DebugDrawLabelRects { get; set; } = true;
|
||||||
|
public bool DebugDisableOcclusion { get; set; }
|
||||||
|
public bool DebugEnabled { get; set; }
|
||||||
|
|
||||||
public void FlagRefresh()
|
public void FlagRefresh()
|
||||||
{
|
{
|
||||||
_needsLabelRefresh = true;
|
_needsLabelRefresh = true;
|
||||||
@@ -1066,7 +1286,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the active broadcasting CIDs.
|
/// Update the active broadcasting CIDs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cids">Inbound new CIDs</param>
|
|
||||||
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
||||||
{
|
{
|
||||||
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
|
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
|
||||||
@@ -1096,7 +1315,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
public NameplateBuffers()
|
public NameplateBuffers()
|
||||||
{
|
{
|
||||||
TextOffsets = new int[AddonNamePlate.NumNamePlateObjects];
|
TextOffsets = new int[AddonNamePlate.NumNamePlateObjects];
|
||||||
System.Array.Fill(TextOffsets, int.MinValue);
|
Array.Fill(TextOffsets, int.MinValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int[] TextWidths { get; } = new int[AddonNamePlate.NumNamePlateObjects];
|
public int[] TextWidths { get; } = new int[AddonNamePlate.NumNamePlateObjects];
|
||||||
@@ -1108,23 +1327,20 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
public NameplateLabelInfo[] LabelCopy { get; } = new NameplateLabelInfo[AddonNamePlate.NumNamePlateObjects];
|
public NameplateLabelInfo[] LabelCopy { get; } = new NameplateLabelInfo[AddonNamePlate.NumNamePlateObjects];
|
||||||
|
|
||||||
public Vector2[] SmoothedPos = new Vector2[AddonNamePlate.NumNamePlateObjects];
|
public Vector2[] SmoothedPos = new Vector2[AddonNamePlate.NumNamePlateObjects];
|
||||||
|
|
||||||
public bool[] HasSmoothed = new bool[AddonNamePlate.NumNamePlateObjects];
|
public bool[] HasSmoothed = new bool[AddonNamePlate.NumNamePlateObjects];
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
System.Array.Clear(TextWidths, 0, TextWidths.Length);
|
Array.Clear(TextWidths, 0, TextWidths.Length);
|
||||||
System.Array.Clear(TextHeights, 0, TextHeights.Length);
|
Array.Clear(TextHeights, 0, TextHeights.Length);
|
||||||
System.Array.Clear(ContainerHeights, 0, ContainerHeights.Length);
|
Array.Clear(ContainerHeights, 0, ContainerHeights.Length);
|
||||||
System.Array.Fill(TextOffsets, int.MinValue);
|
Array.Fill(TextOffsets, int.MinValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the LightFinder Plate Handler.
|
/// Starts the LightFinder Plate Handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">Cancellation Token</param>
|
|
||||||
/// <returns>Task Completed</returns>
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Init();
|
Init();
|
||||||
@@ -1134,8 +1350,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops the LightFinder Plate Handler.
|
/// Stops the LightFinder Plate Handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">Cancellation Token</param>
|
|
||||||
/// <returns>Task Completed</returns>
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Uninit();
|
Uninit();
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ public class UiFactory
|
|||||||
groupData: groupData,
|
groupData: groupData,
|
||||||
isLightfinderContext: isLightfinderContext,
|
isLightfinderContext: isLightfinderContext,
|
||||||
lightfinderCid: lightfinderCid,
|
lightfinderCid: lightfinderCid,
|
||||||
performanceCollector: _performanceCollectorService);
|
performanceCollector: _performanceCollectorService,
|
||||||
|
_apiController);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -968,20 +968,25 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> source)
|
Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> source)
|
||||||
{
|
{
|
||||||
var clone = new Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>(source.Count);
|
var clone = new Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>(source.Count);
|
||||||
|
|
||||||
foreach (var (objectKind, entries) in source)
|
foreach (var (objectKind, entries) in source)
|
||||||
{
|
{
|
||||||
var entryClone = new Dictionary<string, CharacterAnalyzer.FileDataEntry>(entries.Count, entries.Comparer);
|
var entryClone = new Dictionary<string, CharacterAnalyzer.FileDataEntry>(entries.Count, entries.Comparer);
|
||||||
|
|
||||||
foreach (var (hash, entry) in entries)
|
foreach (var (hash, entry) in entries)
|
||||||
{
|
{
|
||||||
entryClone[hash] = new CharacterAnalyzer.FileDataEntry(
|
entryClone[hash] = new CharacterAnalyzer.FileDataEntry(
|
||||||
hash,
|
hash: hash,
|
||||||
entry.FileType,
|
fileType: entry.FileType,
|
||||||
entry.GamePaths.ToList(),
|
gamePaths: entry.GamePaths?.ToList() ?? [],
|
||||||
entry.FilePaths.ToList(),
|
filePaths: entry.FilePaths?.ToList() ?? [],
|
||||||
entry.OriginalSize,
|
originalSize: entry.OriginalSize,
|
||||||
entry.CompressedSize,
|
compressedSize: entry.CompressedSize,
|
||||||
entry.Triangles);
|
triangles: entry.Triangles,
|
||||||
|
cacheEntries: entry.CacheEntries
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
clone[objectKind] = entryClone;
|
clone[objectKind] = entryClone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -364,15 +364,22 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cid = _dalamudUtilService.GetCID();
|
var cid = _dalamudUtilService.GetCID();
|
||||||
var hashedCid = cid.ToString().GetHash256();
|
var hashedCid = cid.ToString().GetHash256();
|
||||||
|
lock (_localHashedCidLock)
|
||||||
|
{
|
||||||
_localHashedCid = hashedCid;
|
_localHashedCid = hashedCid;
|
||||||
_localHashedCidFetchedAt = now;
|
_localHashedCidFetchedAt = DateTime.UtcNow;
|
||||||
return hashedCid;
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
lock (_localHashedCidLock)
|
||||||
{
|
{
|
||||||
if (now >= _localHashedCidNextErrorLog)
|
if (now >= _localHashedCidNextErrorLog)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
|||||||
private readonly LightFinderService _broadcastService;
|
private readonly LightFinderService _broadcastService;
|
||||||
private readonly LightlessConfigService _configService;
|
private readonly LightlessConfigService _configService;
|
||||||
private readonly LightlessProfileManager _lightlessProfileManager;
|
private readonly LightlessProfileManager _lightlessProfileManager;
|
||||||
|
private readonly LightFinderPlateHandler _lightFinderPlateHandler;
|
||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
|
|
||||||
@@ -100,7 +101,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
|||||||
DalamudUtilService dalamudUtilService,
|
DalamudUtilService dalamudUtilService,
|
||||||
LightlessProfileManager lightlessProfileManager,
|
LightlessProfileManager lightlessProfileManager,
|
||||||
ActorObjectService actorObjectService
|
ActorObjectService actorObjectService
|
||||||
) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
|
,
|
||||||
|
LightFinderPlateHandler lightFinderPlateHandler) : base(logger, mediator, "Lightfinder###LightlessLightfinderUI", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_broadcastService = broadcastService;
|
_broadcastService = broadcastService;
|
||||||
_uiSharedService = uiShared;
|
_uiSharedService = uiShared;
|
||||||
@@ -126,6 +128,7 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
|||||||
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshNearbySyncshellsAsync().ConfigureAwait(false));
|
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshNearbySyncshellsAsync().ConfigureAwait(false));
|
||||||
Mediator.Subscribe<UserLeftSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
|
Mediator.Subscribe<UserLeftSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
|
||||||
Mediator.Subscribe<UserJoinedSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
|
Mediator.Subscribe<UserJoinedSyncshell>(this, async _ => await RefreshNearbySyncshellsAsync(_.gid).ConfigureAwait(false));
|
||||||
|
_lightFinderPlateHandler = lightFinderPlateHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1379,17 +1382,53 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
#region Debug Tab
|
|
||||||
|
|
||||||
private void DrawDebugTab()
|
private void DrawDebugTab()
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (ImGui.CollapsingHeader("LightFinder Plates", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
|
{
|
||||||
|
var h = _lightFinderPlateHandler;
|
||||||
|
|
||||||
|
var enabled = h.DebugEnabled;
|
||||||
|
if (ImGui.Checkbox("Enable LightFinder debug", ref enabled))
|
||||||
|
h.DebugEnabled = enabled;
|
||||||
|
|
||||||
|
if (h.DebugEnabled)
|
||||||
|
{
|
||||||
|
ImGui.Indent();
|
||||||
|
|
||||||
|
var disableOcc = h.DebugDisableOcclusion;
|
||||||
|
if (ImGui.Checkbox("Disable occlusion (force draw)", ref disableOcc))
|
||||||
|
h.DebugDisableOcclusion = disableOcc;
|
||||||
|
|
||||||
|
var drawUiRects = h.DebugDrawUiRects;
|
||||||
|
if (ImGui.Checkbox("Draw UI rects", ref drawUiRects))
|
||||||
|
h.DebugDrawUiRects = drawUiRects;
|
||||||
|
|
||||||
|
var drawLabelRects = h.DebugDrawLabelRects;
|
||||||
|
if (ImGui.Checkbox("Draw label rects", ref drawLabelRects))
|
||||||
|
h.DebugDrawLabelRects = drawLabelRects;
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.TextUnformatted($"Labels last frame: {h.DebugLabelCountLastFrame}");
|
||||||
|
ImGui.TextUnformatted($"UI rects last frame: {h.DebugUiRectCountLastFrame}");
|
||||||
|
ImGui.TextUnformatted($"Occluded last frame: {h.DebugOccludedCountLastFrame}");
|
||||||
|
ImGui.TextUnformatted($"Last NamePlate frame: {h.DebugLastNameplateFrame}");
|
||||||
|
|
||||||
|
ImGui.Unindent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
ImGui.Text("Broadcast Cache");
|
ImGui.Text("Broadcast Cache");
|
||||||
|
|
||||||
if (ImGui.BeginTable("##BroadcastCacheTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY, new Vector2(-1, 200f)))
|
if (ImGui.BeginTable("##BroadcastCacheTable", 4,
|
||||||
|
ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY,
|
||||||
|
new Vector2(-1, 225f)))
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
|
ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableHeadersRow();
|
ImGui.TableHeadersRow();
|
||||||
@@ -1427,10 +1466,8 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#region Data Refresh
|
#region Data Refresh
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private bool _pairDiagnosticsEnabled;
|
private bool _pairDiagnosticsEnabled;
|
||||||
private string? _selectedPairDebugUid = null;
|
private string? _selectedPairDebugUid = null;
|
||||||
private string _lightfinderIconInput = string.Empty;
|
private string _lightfinderIconInput = string.Empty;
|
||||||
|
private bool _showLightfinderRendererWarning = false;
|
||||||
|
private LightfinderLabelRenderer _pendingLightfinderRenderer = LightfinderLabelRenderer.Pictomancy;
|
||||||
private bool _lightfinderIconInputInitialized = false;
|
private bool _lightfinderIconInputInitialized = false;
|
||||||
private int _lightfinderIconPresetIndex = -1;
|
private int _lightfinderIconPresetIndex = -1;
|
||||||
private static readonly LightlessConfig DefaultConfig = new();
|
private static readonly LightlessConfig DefaultConfig = new();
|
||||||
@@ -2387,7 +2389,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
var labelRenderer = _configService.Current.LightfinderLabelRenderer;
|
var labelRenderer = _configService.Current.LightfinderLabelRenderer;
|
||||||
var labelRendererLabel = labelRenderer switch
|
var labelRendererLabel = labelRenderer switch
|
||||||
{
|
{
|
||||||
LightfinderLabelRenderer.SignatureHook => "Native nameplate (sig hook)",
|
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||||
_ => "ImGui Overlay",
|
_ => "ImGui Overlay",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2397,18 +2399,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var optionLabel = option switch
|
var optionLabel = option switch
|
||||||
{
|
{
|
||||||
LightfinderLabelRenderer.SignatureHook => "Native Nameplate (sig hook)",
|
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||||
_ => "ImGui Overlay",
|
_ => "ImGui Overlay",
|
||||||
};
|
};
|
||||||
|
|
||||||
var selected = option == labelRenderer;
|
var selected = option == labelRenderer;
|
||||||
if (ImGui.Selectable(optionLabel, selected))
|
if (ImGui.Selectable(optionLabel, selected))
|
||||||
|
{
|
||||||
|
if (option == LightfinderLabelRenderer.SignatureHook)
|
||||||
|
{
|
||||||
|
_pendingLightfinderRenderer = option;
|
||||||
|
_showLightfinderRendererWarning = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
_configService.Current.LightfinderLabelRenderer = option;
|
_configService.Current.LightfinderLabelRenderer = option;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
_nameplateService.RequestRedraw();
|
_nameplateService.RequestRedraw();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (selected)
|
if (selected)
|
||||||
ImGui.SetItemDefaultFocus();
|
ImGui.SetItemDefaultFocus();
|
||||||
}
|
}
|
||||||
@@ -2416,6 +2425,34 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.EndCombo();
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_showLightfinderRendererWarning)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowSize(new Vector2(450f, 0f), ImGuiCond.Appearing);
|
||||||
|
ImGui.OpenPopup("Nameplate Warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Nameplate Warning", ref _showLightfinderRendererWarning, ImGuiWindowFlags.AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui.TextColored(UIColors.Get("DimRed"), "USE AT YOUR RISK!");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextWrapped("Writing on to the native Nameplates is known to be unstable and MAY cause crashes. DO NOT REPORT THOSE CRASHES TO DALAMUD. We will also not be supporting Nameplate crashes. You have been warned.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextWrapped("By accepting this warning, you understand that you are using this feature at risk of crashing.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
var buttonWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
if (ImGui.Button("I Understand", new Vector2(buttonWidth, 0)))
|
||||||
|
{
|
||||||
|
_configService.Current.LightfinderLabelRenderer = _pendingLightfinderRenderer;
|
||||||
|
_configService.Save();
|
||||||
|
_nameplateService.RequestRedraw();
|
||||||
|
_showLightfinderRendererWarning = false;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
_uiShared.DrawHelpText("Choose how Lightfinder labels render: the default ImGui overlay or native nameplate nodes via signature hook.");
|
_uiShared.DrawHelpText("Choose how Lightfinder labels render: the default ImGui overlay or native nameplate nodes via signature hook.");
|
||||||
|
|
||||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
@@ -2602,7 +2639,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
var selected = i == _lightfinderIconPresetIndex;
|
var selected = i == _lightfinderIconPresetIndex;
|
||||||
if (ImGui.Selectable(preview, selected))
|
if (ImGui.Selectable(preview, selected))
|
||||||
{
|
{
|
||||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(optionGlyph);
|
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(optionGlyph);
|
||||||
_lightfinderIconPresetIndex = i;
|
_lightfinderIconPresetIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4083,7 +4120,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private void RefreshLightfinderIconState()
|
private void RefreshLightfinderIconState()
|
||||||
{
|
{
|
||||||
var normalized = LightFinderPlateHandler.NormalizeIconGlyph(_configService.Current.LightfinderLabelIconGlyph);
|
var normalized = LightFinderPlateHandler.NormalizeIconGlyph(_configService.Current.LightfinderLabelIconGlyph);
|
||||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(normalized);
|
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(normalized);
|
||||||
_lightfinderIconInputInitialized = true;
|
_lightfinderIconInputInitialized = true;
|
||||||
|
|
||||||
_lightfinderIconPresetIndex = -1;
|
_lightfinderIconPresetIndex = -1;
|
||||||
@@ -4101,7 +4138,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_configService.Current.LightfinderLabelIconGlyph = normalizedGlyph;
|
_configService.Current.LightfinderLabelIconGlyph = normalizedGlyph;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(normalizedGlyph);
|
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(normalizedGlyph);
|
||||||
_lightfinderIconPresetIndex = presetIndex;
|
_lightfinderIconPresetIndex = presetIndex;
|
||||||
_lightfinderIconInputInitialized = true;
|
_lightfinderIconInputInitialized = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using LightlessSync.Services.ServerConfiguration;
|
|||||||
using LightlessSync.UI.Services;
|
using LightlessSync.UI.Services;
|
||||||
using LightlessSync.UI.Tags;
|
using LightlessSync.UI.Tags;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly ProfileTagService _profileTagService;
|
private readonly ProfileTagService _profileTagService;
|
||||||
|
private readonly ApiController _apiController;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly UserData? _userData;
|
private readonly UserData? _userData;
|
||||||
private readonly GroupData? _groupData;
|
private readonly GroupData? _groupData;
|
||||||
@@ -60,7 +62,8 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
GroupData? groupData,
|
GroupData? groupData,
|
||||||
bool isLightfinderContext,
|
bool isLightfinderContext,
|
||||||
string? lightfinderCid,
|
string? lightfinderCid,
|
||||||
PerformanceCollectorService performanceCollector)
|
PerformanceCollectorService performanceCollector,
|
||||||
|
ApiController apiController)
|
||||||
: base(logger, mediator, BuildWindowTitle(
|
: base(logger, mediator, BuildWindowTitle(
|
||||||
userData,
|
userData,
|
||||||
groupData,
|
groupData,
|
||||||
@@ -94,6 +97,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
.Apply();
|
.Apply();
|
||||||
|
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
_apiController = apiController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair? Pair { get; }
|
public Pair? Pair { get; }
|
||||||
@@ -248,19 +252,33 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
ResetBannerTexture();
|
ResetBannerTexture();
|
||||||
_lastBannerPicture = bannerBytes;
|
_lastBannerPicture = bannerBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? noteText = null;
|
string? noteText = null;
|
||||||
string statusLabel = _isLightfinderContext ? "Exploring" : "Offline";
|
|
||||||
|
var isSelfProfile = !_isLightfinderContext
|
||||||
|
&& _userData is not null
|
||||||
|
&& !string.IsNullOrEmpty(_apiController.UID)
|
||||||
|
&& string.Equals(_userData.UID, _apiController.UID, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
string statusLabel = _isLightfinderContext
|
||||||
|
? "Exploring"
|
||||||
|
: isSelfProfile ? "Online" : "Offline";
|
||||||
|
|
||||||
string? visiblePlayerName = null;
|
string? visiblePlayerName = null;
|
||||||
bool directPair = false;
|
bool directPair = false;
|
||||||
bool youPaused = false;
|
bool youPaused = false;
|
||||||
bool theyPaused = false;
|
bool theyPaused = false;
|
||||||
List<string> syncshellLines = [];
|
List<string> syncshellLines = [];
|
||||||
|
|
||||||
|
if (!_isLightfinderContext)
|
||||||
|
{
|
||||||
|
noteText = _serverManager.GetNoteForUid(_userData!.UID);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_isLightfinderContext && Pair != null)
|
if (!_isLightfinderContext && Pair != null)
|
||||||
{
|
{
|
||||||
var snapshot = _pairUiService.GetSnapshot();
|
var snapshot = _pairUiService.GetSnapshot();
|
||||||
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
|
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
|
||||||
|
|
||||||
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
|
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
|
||||||
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
|
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
|
||||||
|
|
||||||
@@ -282,11 +300,15 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
|
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
|
||||||
? groupInfo.GroupAliasOrGID
|
? groupInfo.GroupAliasOrGID
|
||||||
: gid;
|
: gid;
|
||||||
|
|
||||||
var groupNote = _serverManager.GetNoteForGid(gid);
|
var groupNote = _serverManager.GetNoteForGid(gid);
|
||||||
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
|
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSelfProfile)
|
||||||
|
statusLabel = "Online";
|
||||||
}
|
}
|
||||||
|
|
||||||
var presenceTokens = new List<PresenceToken>
|
var presenceTokens = new List<PresenceToken>
|
||||||
|
|||||||
@@ -993,19 +993,34 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
_refocusChatInput = true;
|
_refocusChatInput = true;
|
||||||
_refocusChatInputKey = channel.Key;
|
_refocusChatInputKey = channel.Key;
|
||||||
var sanitized = SanitizeOutgoingDraft(draft);
|
|
||||||
|
var draftAtSend = draft;
|
||||||
|
var sanitized = SanitizeOutgoingDraft(draftAtSend);
|
||||||
|
|
||||||
if (sanitized is not null)
|
if (sanitized is not null)
|
||||||
{
|
{
|
||||||
TrackPendingDraftClear(channel.Key, sanitized);
|
TrackPendingDraftClear(channel.Key, sanitized);
|
||||||
if (TrySendDraft(channel, sanitized))
|
draft = string.Empty;
|
||||||
{
|
_draftMessages[channel.Key] = draft;
|
||||||
_scrollToBottom = true;
|
_scrollToBottom = true;
|
||||||
}
|
|
||||||
else
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var succeeded = await _zoneChatService.SendMessageAsync(channel.Descriptor, sanitized).ConfigureAwait(false);
|
||||||
|
if (!succeeded)
|
||||||
{
|
{
|
||||||
RemovePendingDraftClear(channel.Key, sanitized);
|
RemovePendingDraftClear(channel.Key, sanitized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to send chat message");
|
||||||
|
RemovePendingDraftClear(channel.Key, sanitized);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user