2.0.2 merged again
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -348,3 +348,6 @@ MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# idea
|
||||
/.idea
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
tagline: "Lightless Sync v2.0.1"
|
||||
subline: "LIGHTLESS IS EVOLVING!!"
|
||||
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"
|
||||
tagline: "Some Fixes"
|
||||
date: "December 23 2025"
|
||||
# be sure to set this every new version
|
||||
isCurrent: true
|
||||
versions:
|
||||
- number: "Chat"
|
||||
icon: ""
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LightlessSync.FileCache;
|
||||
|
||||
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;
|
||||
CompressedSize = compressedSize;
|
||||
Hash = hash;
|
||||
PrefixedFilePath = path;
|
||||
PrefixedFilePath = prefixedFilePath;
|
||||
LastModifiedDateTicks = lastModifiedDateTicks;
|
||||
}
|
||||
|
||||
@@ -23,7 +31,5 @@ public class FileCacheEntity
|
||||
public long? Size { get; set; }
|
||||
|
||||
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 System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace LightlessSync.FileCache;
|
||||
@@ -31,6 +33,14 @@ public sealed class FileCacheManager : IHostedService
|
||||
private bool _csvHeaderEnsured;
|
||||
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)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -45,6 +55,18 @@ public sealed class FileCacheManager : IHostedService
|
||||
private static string NormalizeSeparators(string path) => path.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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefixedPath))
|
||||
@@ -111,6 +133,114 @@ public sealed class FileCacheManager : IHostedService
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return (fileHash, LZ4Wrapper.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false), 0,
|
||||
(int)new FileInfo(fileCache).Length));
|
||||
var raw = await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false);
|
||||
var compressed = LZ4Wrapper.WrapHC(raw, 0, raw.Length);
|
||||
UpdateSizeInfo(fileHash, original: raw.LongLength, compressed: compressed.LongLength);
|
||||
return (fileHash, compressed);
|
||||
}
|
||||
|
||||
public FileCacheEntity? GetFileCacheByHash(string hash)
|
||||
@@ -891,6 +1030,14 @@ public sealed class FileCacheManager : IHostedService
|
||||
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)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -85,7 +85,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private string PlayerPersistentDataKey => _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult() + "_" + _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
||||
private string PlayerPersistentDataKey => _dalamudUtil.GetPlayerName() + "_" + _dalamudUtil.GetHomeWorldId();
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources
|
||||
{
|
||||
get
|
||||
@@ -352,6 +352,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
private void RefreshPlayerRelatedAddressMap()
|
||||
{
|
||||
_playerRelatedByAddress.Clear();
|
||||
var updatedFrameAddresses = new ConcurrentDictionary<nint, ObjectKind>();
|
||||
lock (_playerRelatedLock)
|
||||
{
|
||||
foreach (var handler in _playerRelatedPointers)
|
||||
@@ -360,9 +361,12 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
if (address != nint.Zero)
|
||||
{
|
||||
_playerRelatedByAddress[address] = handler;
|
||||
updatedFrameAddresses[address] = handler.ObjectKind;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cachedFrameAddresses = updatedFrameAddresses;
|
||||
}
|
||||
|
||||
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
||||
@@ -498,7 +502,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
||||
var gameObjectAddress = msg.GameObject;
|
||||
if (!_cachedFrameAddresses.TryGetValue(gameObjectAddress, out var objectKind))
|
||||
{
|
||||
return;
|
||||
if (_actorObjectService.TryGetOwnedKind(gameObjectAddress, out var ownedKind))
|
||||
{
|
||||
objectKind = ownedKind;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var gamePath = NormalizeGamePath(msg.GamePath);
|
||||
|
||||
@@ -95,6 +95,12 @@ public sealed class IpcCallerPenumbra : IpcServiceBase
|
||||
public Task<(string[] forward, string[][] reverse)> ResolvePathsAsync(string[] forward, string[] 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)
|
||||
=> _redraw.RedrawAsync(logger, handler, applicationId, token);
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class PenumbraResource : PenumbraBase
|
||||
{
|
||||
private readonly ActorObjectService _actorObjectService;
|
||||
private readonly GetGameObjectResourcePaths _gameObjectResourcePaths;
|
||||
private readonly ResolveGameObjectPath _resolveGameObjectPath;
|
||||
private readonly ReverseResolveGameObjectPath _reverseResolveGameObjectPath;
|
||||
private readonly ResolvePlayerPathsAsync _resolvePlayerPaths;
|
||||
private readonly GetPlayerMetaManipulations _getPlayerMetaManipulations;
|
||||
private readonly EventSubscriber<nint, string, string> _gameObjectResourcePathResolved;
|
||||
@@ -27,6 +29,8 @@ public sealed class PenumbraResource : PenumbraBase
|
||||
{
|
||||
_actorObjectService = actorObjectService;
|
||||
_gameObjectResourcePaths = new GetGameObjectResourcePaths(pluginInterface);
|
||||
_resolveGameObjectPath = new ResolveGameObjectPath(pluginInterface);
|
||||
_reverseResolveGameObjectPath = new ReverseResolveGameObjectPath(pluginInterface);
|
||||
_resolvePlayerPaths = new ResolvePlayerPathsAsync(pluginInterface);
|
||||
_getPlayerMetaManipulations = new GetPlayerMetaManipulations(pluginInterface);
|
||||
_gameObjectResourcePathResolved = GameObjectResourcePathResolved.Subscriber(pluginInterface, HandleResourceLoaded);
|
||||
@@ -67,7 +71,13 @@ public sealed class PenumbraResource : PenumbraBase
|
||||
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)
|
||||
{
|
||||
@@ -79,12 +89,12 @@ public sealed class PenumbraResource : PenumbraBase
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Compare(resolvedPath, gamePath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
if (string.Compare(gamePath, resolvedPath, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Mediator.Publish(new PenumbraResourceLoadMessage(ptr, resolvedPath, gamePath));
|
||||
Mediator.Publish(new PenumbraResourceLoadMessage(ptr, gamePath, resolvedPath));
|
||||
}
|
||||
|
||||
protected override void HandleStateChange(IpcConnectionState previous, IpcConnectionState current)
|
||||
|
||||
@@ -194,7 +194,7 @@ public class PlayerDataFactory
|
||||
|
||||
// get all remaining paths and resolve them
|
||||
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)
|
||||
{
|
||||
@@ -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 reversePaths = reverseResolve.ToArray();
|
||||
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);
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
IFramework framework, IObjectTable objectTable, IClientState clientState, ICondition condition, IChatGui chatGui,
|
||||
IGameGui gameGui, IDtrBar dtrBar, IPluginLog pluginLog, ITargetManager targetManager, INotificationManager notificationManager,
|
||||
ITextureProvider textureProvider, IContextMenu contextMenu, IGameInteropProvider gameInteropProvider, IGameConfig gameConfig,
|
||||
ISigScanner sigScanner, INamePlateGui namePlateGui, IAddonLifecycle addonLifecycle)
|
||||
ISigScanner sigScanner, INamePlateGui namePlateGui, IAddonLifecycle addonLifecycle, IPlayerState playerState)
|
||||
{
|
||||
NativeDll.Initialize(pluginInterface.AssemblyLocation.DirectoryName);
|
||||
if (!Directory.Exists(pluginInterface.ConfigDirectory.FullName))
|
||||
@@ -219,6 +219,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
gameData,
|
||||
targetManager,
|
||||
gameConfig,
|
||||
playerState,
|
||||
sp.GetRequiredService<ActorObjectService>(),
|
||||
sp.GetRequiredService<BlockedCharacterHandler>(),
|
||||
sp.GetRequiredService<LightlessMediator>(),
|
||||
@@ -479,6 +480,16 @@ public sealed class Plugin : IDalamudPlugin
|
||||
sp.GetRequiredService<UiSharedService>(),
|
||||
sp.GetRequiredService<ApiController>(),
|
||||
sp.GetRequiredService<LightFinderScannerService>(),
|
||||
sp.GetRequiredService<LightFinderPlateHandler>()));
|
||||
|
||||
services.AddScoped<WindowMediatorSubscriberBase, SyncshellFinderUI>(sp => new SyncshellFinderUI(
|
||||
sp.GetRequiredService<ILogger<SyncshellFinderUI>>(),
|
||||
sp.GetRequiredService<LightlessMediator>(),
|
||||
sp.GetRequiredService<PerformanceCollectorService>(),
|
||||
sp.GetRequiredService<LightFinderService>(),
|
||||
sp.GetRequiredService<UiSharedService>(),
|
||||
sp.GetRequiredService<ApiController>(),
|
||||
sp.GetRequiredService<LightFinderScannerService>(),
|
||||
sp.GetRequiredService<PairUiService>(),
|
||||
sp.GetRequiredService<DalamudUtilService>(),
|
||||
sp.GetRequiredService<LightlessProfileManager>(),
|
||||
|
||||
@@ -450,7 +450,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase
|
||||
};
|
||||
}
|
||||
|
||||
var loc = await _dalamudUtil.GetMapDataAsync().ConfigureAwait(false);
|
||||
var loc = _dalamudUtil.GetMapData();
|
||||
worldData.LocationInfo = loc;
|
||||
|
||||
if (_forceResendWorldData || worldData != _lastWorldData)
|
||||
|
||||
@@ -254,7 +254,7 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
|
||||
|
||||
Logger.LogTrace("Attaching World data {data}", worldData);
|
||||
|
||||
worldData.LocationInfo = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
|
||||
worldData.LocationInfo = _dalamudUtilService.GetMapData();
|
||||
|
||||
Logger.LogTrace("World data serialized: {data}", worldData);
|
||||
|
||||
|
||||
@@ -186,8 +186,8 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
|
||||
var previousPoses = _nearbyData.Keys.ToList();
|
||||
_nearbyData.Clear();
|
||||
|
||||
var ownLocation = await _dalamudUtilService.RunOnFrameworkThread(() => _dalamudUtilService.GetMapData()).ConfigureAwait(false);
|
||||
var player = await _dalamudUtilService.RunOnFrameworkThread(() => _dalamudUtilService.GetPlayerCharacter()).ConfigureAwait(false);
|
||||
var ownLocation = _dalamudUtilService.GetMapData();
|
||||
var player = await _dalamudUtilService.GetPlayerCharacterAsync().ConfigureAwait(false);
|
||||
var currentServer = player.CurrentWorld;
|
||||
var playerPos = player.Position;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.FileCache;
|
||||
@@ -98,11 +99,13 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
_analysisCts = null;
|
||||
if (print) PrintAnalysis();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_analysisCts.CancelDispose();
|
||||
_baseAnalysisCts.Dispose();
|
||||
}
|
||||
|
||||
public async Task UpdateFileEntriesAsync(IEnumerable<string> filePaths, CancellationToken token)
|
||||
{
|
||||
var normalized = new HashSet<string>(
|
||||
@@ -125,6 +128,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
|
||||
{
|
||||
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
|
||||
@@ -136,29 +140,47 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var fileCacheEntries = (await _fileCacheManager.GetAllFileCachesByHashAsync(fileEntry.Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false)).ToList();
|
||||
if (fileCacheEntries.Count == 0) continue;
|
||||
var filePath = fileCacheEntries[0].ResolvedFilepath;
|
||||
FileInfo fi = new(filePath);
|
||||
string ext = "unk?";
|
||||
try
|
||||
{
|
||||
ext = fi.Extension[1..];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Could not identify extension for {path}", filePath);
|
||||
}
|
||||
var fileCacheEntries = (await _fileCacheManager
|
||||
.GetAllFileCachesByHashAsync(fileEntry.Hash, ignoreCacheEntries: true, validate: false, token)
|
||||
.ConfigureAwait(false))
|
||||
.ToList();
|
||||
|
||||
if (fileCacheEntries.Count == 0)
|
||||
continue;
|
||||
|
||||
var resolved = fileCacheEntries[0].ResolvedFilepath;
|
||||
|
||||
var extWithDot = Path.GetExtension(resolved);
|
||||
var ext = string.IsNullOrEmpty(extWithDot) ? "unk?" : extWithDot.TrimStart('.');
|
||||
|
||||
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,
|
||||
[.. fileEntry.GamePaths],
|
||||
[.. fileCacheEntries.Select(c => c.ResolvedFilepath).Distinct(StringComparer.Ordinal)],
|
||||
entry.Size > 0 ? entry.Size.Value : 0,
|
||||
entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0,
|
||||
tris);
|
||||
if (orig <= 0 && cached.Original > 0) orig = cached.Original;
|
||||
if (comp <= 0 && cached.Compressed > 0) comp = cached.Compressed;
|
||||
}
|
||||
|
||||
data[fileEntry.Hash] = new FileDataEntry(
|
||||
fileEntry.Hash,
|
||||
ext,
|
||||
[.. fileEntry.GamePaths],
|
||||
distinctFilePaths,
|
||||
orig,
|
||||
comp,
|
||||
tris,
|
||||
fileCacheEntries);
|
||||
}
|
||||
LastAnalysis[obj.Key] = data;
|
||||
}
|
||||
@@ -167,6 +189,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||
_lastDataHash = charaData.DataHash.Value;
|
||||
}
|
||||
|
||||
private void RecalculateSummary()
|
||||
{
|
||||
var builder = ImmutableDictionary.CreateBuilder<ObjectKind, CharacterAnalysisObjectSummary>();
|
||||
@@ -192,6 +215,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
|
||||
|
||||
_latestSummary = new CharacterAnalysisSummary(builder.ToImmutable());
|
||||
}
|
||||
|
||||
private void PrintAnalysis()
|
||||
{
|
||||
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))));
|
||||
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)
|
||||
{
|
||||
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
||||
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token)
|
||||
{
|
||||
var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false);
|
||||
var normalSize = new FileInfo(FilePaths[0]).Length;
|
||||
var entries = await fileCacheManager.GetAllFileCachesByHashAsync(Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.Size = normalSize;
|
||||
entry.CompressedSize = compressedsize.Item2.LongLength;
|
||||
}
|
||||
OriginalSize = normalSize;
|
||||
CompressedSize = compressedsize.Item2.LongLength;
|
||||
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();
|
||||
|
||||
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 FileDataEntry(
|
||||
string hash,
|
||||
string fileType,
|
||||
List<string> gamePaths,
|
||||
List<string> filePaths,
|
||||
long originalSize,
|
||||
long compressedSize,
|
||||
long triangles,
|
||||
IReadOnlyList<FileCacheEntity> cacheEntries)
|
||||
{
|
||||
Hash = hash;
|
||||
FileType = fileType;
|
||||
GamePaths = gamePaths;
|
||||
FilePaths = filePaths;
|
||||
OriginalSize = originalSize;
|
||||
CompressedSize = compressedSize;
|
||||
Triangles = triangles;
|
||||
CacheEntries = cacheEntries;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public Lazy<string> Format => _format ??= CreateFormatValue();
|
||||
private Lazy<string>? _format;
|
||||
|
||||
public void RefreshFormat()
|
||||
{
|
||||
_format = CreateFormatValue();
|
||||
}
|
||||
public void RefreshFormat() => _format = CreateFormatValue();
|
||||
|
||||
private Lazy<string> CreateFormatValue()
|
||||
=> new(() =>
|
||||
{
|
||||
if (!string.Equals(FileType, "tex", StringComparison.Ordinal))
|
||||
{
|
||||
if (!string.Equals(FileType, "tex", StringComparison.OrdinalIgnoreCase))
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -576,7 +576,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
||||
|
||||
try
|
||||
{
|
||||
var location = await _dalamudUtilService.GetMapDataAsync().ConfigureAwait(false);
|
||||
var location = _dalamudUtilService.GetMapData();
|
||||
var territoryId = (ushort)location.TerritoryId;
|
||||
var worldId = (ushort)location.ServerId;
|
||||
|
||||
@@ -702,7 +702,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
||||
{
|
||||
try
|
||||
{
|
||||
var worldId = (ushort)await _dalamudUtilService.GetWorldIdAsync().ConfigureAwait(false);
|
||||
var worldId = (ushort)_dalamudUtilService.GetWorldId();
|
||||
return definition.Descriptor with { WorldId = worldId };
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1161,7 +1161,7 @@ public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedS
|
||||
{
|
||||
try
|
||||
{
|
||||
return _dalamudUtilService.GetPlayerNameAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return _dalamudUtilService.GetPlayerName();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -241,7 +241,7 @@ internal class ContextMenuService : IHostedService
|
||||
return;
|
||||
}
|
||||
|
||||
var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||
var senderCid = _dalamudUtil.GetCID().ToString().GetHash256();
|
||||
var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
|
||||
|
||||
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);
|
||||
|
||||
@@ -37,6 +37,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
private readonly ICondition _condition;
|
||||
private readonly IDataManager _gameData;
|
||||
private readonly IGameConfig _gameConfig;
|
||||
private readonly IPlayerState _playerState;
|
||||
private readonly BlockedCharacterHandler _blockedCharacterHandler;
|
||||
private readonly IFramework _framework;
|
||||
private readonly IGameGui _gameGui;
|
||||
@@ -60,7 +61,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
private Lazy<ulong> _cid;
|
||||
|
||||
public DalamudUtilService(ILogger<DalamudUtilService> logger, IClientState clientState, IObjectTable objectTable, IFramework framework,
|
||||
IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, IGameConfig gameConfig,
|
||||
IGameGui gameGui, ICondition condition, IDataManager gameData, ITargetManager targetManager, IGameConfig gameConfig, IPlayerState playerState,
|
||||
ActorObjectService actorObjectService, BlockedCharacterHandler blockedCharacterHandler, LightlessMediator mediator, PerformanceCollectorService performanceCollector,
|
||||
LightlessConfigService configService, PlayerPerformanceConfigService playerPerformanceConfigService, Lazy<PairFactory> pairFactory)
|
||||
{
|
||||
@@ -72,6 +73,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
_condition = condition;
|
||||
_gameData = gameData;
|
||||
_gameConfig = gameConfig;
|
||||
_playerState = playerState;
|
||||
_actorObjectService = actorObjectService;
|
||||
_targetManager = targetManager;
|
||||
_blockedCharacterHandler = blockedCharacterHandler;
|
||||
@@ -80,21 +82,27 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
_configService = configService;
|
||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||
_pairFactory = pairFactory;
|
||||
var clientLanguage = _clientState.ClientLanguage;
|
||||
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])))
|
||||
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
||||
});
|
||||
JobData = new(() =>
|
||||
{
|
||||
return gameData.GetExcelSheet<ClassJob>(Dalamud.Game.ClientLanguage.English)!
|
||||
.ToDictionary(k => k.RowId, k => k.NameEnglish.ToString());
|
||||
return gameData.GetExcelSheet<ClassJob>(clientLanguage)!
|
||||
.ToDictionary(k => k.RowId, k => k.Name.ToString());
|
||||
});
|
||||
var clientLanguage = _clientState.ClientLanguage;
|
||||
TerritoryData = new(() => BuildTerritoryData(clientLanguage));
|
||||
TerritoryDataEnglish = new(() => BuildTerritoryData(Dalamud.Game.ClientLanguage.English));
|
||||
MapData = new(() => BuildMapData(clientLanguage));
|
||||
ContentFinderData = new Lazy<Dictionary<uint, string>>(() =>
|
||||
{
|
||||
return _gameData.GetExcelSheet<TerritoryType>()!
|
||||
.Where(w => w.RowId != 0 && !string.IsNullOrEmpty(w.ContentFinderCondition.ValueNullable?.Name.ToString()))
|
||||
.ToDictionary(w => w.RowId, w => w.ContentFinderCondition.Value.Name.ToString());
|
||||
});
|
||||
mediator.Subscribe<TargetPairMessage>(this, (msg) =>
|
||||
{
|
||||
if (clientState.IsPvP) return;
|
||||
@@ -280,6 +288,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
public Lazy<Dictionary<uint, string>> TerritoryData { get; private set; }
|
||||
public Lazy<Dictionary<uint, string>> TerritoryDataEnglish { get; private set; }
|
||||
public Lazy<Dictionary<uint, (Map Map, string MapName)>> MapData { get; private set; }
|
||||
public Lazy<Dictionary<uint, string>> ContentFinderData { get; private set; }
|
||||
public bool IsLodEnabled { get; private set; }
|
||||
public LightlessMediator Mediator { get; }
|
||||
|
||||
@@ -373,7 +382,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
public bool GetIsPlayerPresent()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
return _objectTable.LocalPlayer != null && _objectTable.LocalPlayer.IsValid();
|
||||
return _objectTable.LocalPlayer != null && _objectTable.LocalPlayer.IsValid() && _playerState.IsLoaded;
|
||||
}
|
||||
|
||||
public async Task<bool> GetIsPlayerPresentAsync()
|
||||
@@ -587,34 +596,17 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
|
||||
public string GetPlayerName()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
return _objectTable.LocalPlayer?.Name.ToString() ?? "--";
|
||||
}
|
||||
|
||||
public async Task<string> GetPlayerNameAsync()
|
||||
{
|
||||
return await RunOnFrameworkThread(GetPlayerName).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<ulong> GetCIDAsync()
|
||||
{
|
||||
return await RunOnFrameworkThread(GetCID).ConfigureAwait(false);
|
||||
return _playerState.CharacterName;
|
||||
}
|
||||
|
||||
public unsafe ulong GetCID()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
var playerChar = GetPlayerCharacter();
|
||||
|
||||
if (playerChar == null || playerChar.Address == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
return ((BattleChara*)playerChar.Address)->Character.ContentId;
|
||||
return _playerState.ContentId;
|
||||
}
|
||||
|
||||
public async Task<string> GetPlayerNameHashedAsync()
|
||||
public string GetPlayerNameHashed()
|
||||
{
|
||||
return await RunOnFrameworkThread(() => _cid.Value.ToString().GetHash256()).ConfigureAwait(false);
|
||||
return _cid.Value.ToString().GetHash256();
|
||||
}
|
||||
|
||||
public static unsafe bool TryGetHashedCID(IPlayerCharacter? playerCharacter, out string hashedCid)
|
||||
@@ -653,54 +645,100 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
|
||||
public uint GetHomeWorldId()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
return _objectTable.LocalPlayer?.HomeWorld.RowId ?? 0;
|
||||
return _playerState.HomeWorld.RowId;
|
||||
}
|
||||
|
||||
public uint GetWorldId()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
return _objectTable.LocalPlayer!.CurrentWorld.RowId;
|
||||
return _playerState.CurrentWorld.RowId;
|
||||
}
|
||||
|
||||
public unsafe LocationInfo GetMapData()
|
||||
{
|
||||
EnsureIsOnFramework();
|
||||
var agentMap = AgentMap.Instance();
|
||||
var houseMan = HousingManager.Instance();
|
||||
uint serverId = 0;
|
||||
if (_objectTable.LocalPlayer == null) serverId = 0;
|
||||
else serverId = _objectTable.LocalPlayer.CurrentWorld.RowId;
|
||||
uint mapId = agentMap == null ? 0 : agentMap->CurrentMapId;
|
||||
uint territoryId = agentMap == null ? 0 : agentMap->CurrentTerritoryId;
|
||||
uint divisionId = houseMan == null ? 0 : (uint)(houseMan->GetCurrentDivision());
|
||||
uint wardId = houseMan == null ? 0 : (uint)(houseMan->GetCurrentWard() + 1);
|
||||
uint houseId = 0;
|
||||
var tempHouseId = houseMan == null ? 0 : (houseMan->GetCurrentPlot());
|
||||
if (!houseMan->IsInside()) tempHouseId = 0;
|
||||
if (tempHouseId < -1)
|
||||
{
|
||||
divisionId = tempHouseId == -127 ? 2 : (uint)1;
|
||||
tempHouseId = 100;
|
||||
}
|
||||
if (tempHouseId == -1) tempHouseId = 0;
|
||||
houseId = (uint)tempHouseId;
|
||||
if (houseId != 0)
|
||||
{
|
||||
territoryId = HousingManager.GetOriginalHouseTerritoryTypeId();
|
||||
}
|
||||
uint roomId = houseMan == null ? 0 : (uint)(houseMan->GetCurrentRoom());
|
||||
|
||||
return new LocationInfo()
|
||||
var location = new LocationInfo();
|
||||
location.ServerId = _playerState.CurrentWorld.RowId;
|
||||
//location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first
|
||||
location.TerritoryId = _clientState.TerritoryType;
|
||||
location.MapId = _clientState.MapId;
|
||||
if (houseMan != null)
|
||||
{
|
||||
ServerId = serverId,
|
||||
MapId = mapId,
|
||||
TerritoryId = territoryId,
|
||||
DivisionId = divisionId,
|
||||
WardId = wardId,
|
||||
HouseId = houseId,
|
||||
RoomId = roomId
|
||||
};
|
||||
if (houseMan->IsInside())
|
||||
{
|
||||
location.TerritoryId = HousingManager.GetOriginalHouseTerritoryTypeId();
|
||||
var house = houseMan->GetCurrentIndoorHouseId();
|
||||
location.WardId = house.WardIndex + 1u;
|
||||
location.HouseId = house.IsApartment ? 100 : house.PlotIndex + 1u;
|
||||
location.RoomId = (uint)house.RoomNumber;
|
||||
location.DivisionId = house.IsApartment ? house.ApartmentDivision + 1u : houseMan->GetCurrentDivision();
|
||||
}
|
||||
else if (houseMan->IsInWorkshop())
|
||||
{
|
||||
var workShop = houseMan->WorkshopTerritory;
|
||||
var house = workShop->HouseId;
|
||||
location.WardId = house.WardIndex + 1u;
|
||||
location.HouseId = house.PlotIndex + 1u;
|
||||
}
|
||||
else if (houseMan->IsOutside())
|
||||
{
|
||||
var outside = houseMan->OutdoorTerritory;
|
||||
var house = outside->HouseId;
|
||||
location.WardId = house.WardIndex + 1u;
|
||||
location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
||||
location.DivisionId = houseMan->GetCurrentDivision();
|
||||
}
|
||||
//_logger.LogWarning(LocationToString(location));
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
public string LocationToString(LocationInfo location)
|
||||
{
|
||||
if (location.ServerId is 0 || location.TerritoryId is 0) return String.Empty;
|
||||
var str = WorldData.Value[(ushort)location.ServerId];
|
||||
|
||||
if (ContentFinderData.Value.TryGetValue(location.TerritoryId , out var dutyName))
|
||||
{
|
||||
str += $" - [In Duty]{dutyName}";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (location.HouseId is not 0 || location.MapId is 0) // Dont show mapName when in house/no map available
|
||||
{
|
||||
str += $" - {TerritoryData.Value[(ushort)location.TerritoryId]}";
|
||||
}
|
||||
else
|
||||
{
|
||||
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
||||
}
|
||||
|
||||
// if (location.InstanceId is not 0)
|
||||
// {
|
||||
// str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
||||
// }
|
||||
|
||||
if (location.WardId is not 0)
|
||||
{
|
||||
str += $" Ward #{location.WardId}";
|
||||
}
|
||||
|
||||
if (location.HouseId is not 0 and not 100)
|
||||
{
|
||||
str += $" House #{location.HouseId}";
|
||||
}
|
||||
else if (location.HouseId is 100)
|
||||
{
|
||||
str += $" {(location.DivisionId == 2 ? "[Subdivision]" : "")} Apartment";
|
||||
}
|
||||
|
||||
if (location.RoomId is not 0)
|
||||
{
|
||||
str += $" Room #{location.RoomId}";
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
public unsafe void SetMarkerAndOpenMap(Vector3 position, Map map)
|
||||
@@ -712,21 +750,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
agentMap->SetFlagMapMarker(map.TerritoryType.RowId, map.RowId, position);
|
||||
}
|
||||
|
||||
public async Task<LocationInfo> GetMapDataAsync()
|
||||
{
|
||||
return await RunOnFrameworkThread(GetMapData).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<uint> GetWorldIdAsync()
|
||||
{
|
||||
return await RunOnFrameworkThread(GetWorldId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<uint> GetHomeWorldIdAsync()
|
||||
{
|
||||
return await RunOnFrameworkThread(GetHomeWorldId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public unsafe bool IsGameObjectPresent(IntPtr key)
|
||||
{
|
||||
return _objectTable.Any(f => f.Address == key);
|
||||
|
||||
@@ -23,6 +23,7 @@ using Pictomancy;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
@@ -41,6 +42,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
private readonly LightlessConfigService _configService;
|
||||
private readonly PairUiService _pairUiService;
|
||||
private readonly LightlessMediator _mediator;
|
||||
|
||||
public LightlessMediator Mediator => _mediator;
|
||||
|
||||
private readonly IUiBuilder _uiBuilder;
|
||||
@@ -61,16 +63,22 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
|
||||
// / Overlay window flags
|
||||
private const ImGuiWindowFlags _overlayFlags =
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.NoBackground |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoSavedSettings |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoInputs;
|
||||
ImGuiWindowFlags.NoDecoration |
|
||||
ImGuiWindowFlags.NoBackground |
|
||||
ImGuiWindowFlags.NoMove |
|
||||
ImGuiWindowFlags.NoSavedSettings |
|
||||
ImGuiWindowFlags.NoNav |
|
||||
ImGuiWindowFlags.NoInputs;
|
||||
|
||||
private readonly List<RectF> _uiRects = new(128);
|
||||
private ImmutableHashSet<string> _activeBroadcastingCids = [];
|
||||
|
||||
#if DEBUG
|
||||
// Debug controls
|
||||
|
||||
// Debug counters (read-only from UI)
|
||||
#endif
|
||||
|
||||
private bool IsPictomancyRenderer => _configService.Current.LightfinderLabelRenderer == LightfinderLabelRenderer.Pictomancy;
|
||||
|
||||
public LightFinderPlateHandler(
|
||||
@@ -96,7 +104,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
_uiBuilder = pluginInterface.UiBuilder ?? throw new ArgumentNullException(nameof(pluginInterface));
|
||||
_ = pictomancyService ?? throw new ArgumentNullException(nameof(pictomancyService));
|
||||
_lastRenderer = _configService.Current.LightfinderLabelRenderer;
|
||||
|
||||
}
|
||||
|
||||
private void RefreshRendererState()
|
||||
@@ -187,8 +194,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Draw detour for nameplate addon.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="args"></param>
|
||||
private void NameplateDrawDetour(AddonEvent type, AddonArgs args)
|
||||
{
|
||||
RefreshRendererState();
|
||||
@@ -199,6 +204,16 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
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)
|
||||
{
|
||||
ClearLabelBuffer();
|
||||
@@ -218,6 +233,10 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (fw != null)
|
||||
_lastNamePlateDrawFrame = fw->FrameCounter;
|
||||
|
||||
#if DEBUG
|
||||
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||
#endif
|
||||
|
||||
var pNameplateAddon = (AddonNamePlate*)args.Addon.Address;
|
||||
|
||||
if (_mpNameplateAddon != pNameplateAddon)
|
||||
@@ -234,6 +253,13 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// </summary>
|
||||
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");
|
||||
if (currentHandle.Address == nint.Zero)
|
||||
{
|
||||
@@ -297,7 +323,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
|
||||
for (int i = 0; i < safeCount; ++i)
|
||||
{
|
||||
|
||||
var objectInfoPtr = vec[i];
|
||||
if (objectInfoPtr == null)
|
||||
continue;
|
||||
@@ -314,7 +339,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if ((ObjectKind)gameObject->ObjectKind != ObjectKind.Player)
|
||||
continue;
|
||||
|
||||
// CID gating
|
||||
// CID gating - only show for active broadcasters
|
||||
var cid = DalamudUtilService.GetHashedCIDFromPlayerPointer((nint)gameObject);
|
||||
if (cid == null || !_activeBroadcastingCids.Contains(cid))
|
||||
continue;
|
||||
@@ -350,12 +375,12 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (!currentConfig.LightfinderLabelShowHidden && !isNameplateVisible)
|
||||
continue;
|
||||
|
||||
// Prepare label content and scaling
|
||||
var scaleMultiplier = System.Math.Clamp(currentConfig.LightfinderLabelScale, 0.5f, 2.0f);
|
||||
// Prepare label content and scaling factors
|
||||
var scaleMultiplier = Math.Clamp(currentConfig.LightfinderLabelScale, 0.5f, 2.0f);
|
||||
var baseScale = currentConfig.LightfinderLabelUseIcon ? 1.0f : 0.5f;
|
||||
var effectiveScale = baseScale * scaleMultiplier;
|
||||
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
|
||||
? NormalizeIconGlyph(currentConfig.LightfinderLabelIconGlyph)
|
||||
: _defaultLabelText;
|
||||
@@ -363,8 +388,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (!currentConfig.LightfinderLabelUseIcon && (string.IsNullOrWhiteSpace(labelContent) || string.Equals(labelContent, "-", StringComparison.Ordinal)))
|
||||
labelContent = _defaultLabelText;
|
||||
|
||||
var nodeWidth = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
||||
var nodeHeight = (int)System.Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
|
||||
var nodeWidth = (int)Math.Round(AtkNodeHelpers.DefaultTextNodeWidth * effectiveScale);
|
||||
var nodeHeight = (int)Math.Round(AtkNodeHelpers.DefaultTextNodeHeight * effectiveScale);
|
||||
AlignmentType alignment;
|
||||
|
||||
var textScaleY = nameText->AtkResNode.ScaleY;
|
||||
@@ -374,7 +399,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
var blockHeight = ResolveCache(
|
||||
_buffers.TextHeights,
|
||||
nameplateIndex,
|
||||
System.Math.Abs((int)nameplateObject.TextH),
|
||||
Math.Abs((int)nameplateObject.TextH),
|
||||
() => GetScaledTextHeight(nameText),
|
||||
nodeHeight);
|
||||
|
||||
@@ -384,7 +409,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
(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;
|
||||
},
|
||||
blockHeight + 1);
|
||||
@@ -392,7 +417,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
var blockTop = containerHeight - blockHeight;
|
||||
if (blockTop < 0)
|
||||
blockTop = 0;
|
||||
var verticalPadding = (int)System.Math.Round(4 * effectiveScale);
|
||||
var verticalPadding = (int)Math.Round(4 * effectiveScale);
|
||||
|
||||
var positionY = blockTop - verticalPadding;
|
||||
|
||||
@@ -400,21 +425,14 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
var textWidth = ResolveCache(
|
||||
_buffers.TextWidths,
|
||||
nameplateIndex,
|
||||
System.Math.Abs(rawTextWidth),
|
||||
Math.Abs(rawTextWidth),
|
||||
() => GetScaledTextWidth(nameText),
|
||||
nodeWidth);
|
||||
|
||||
// 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);
|
||||
|
||||
if (nameContainer == null)
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
_logger.LogDebug("Nameplate {Index} container became unavailable during update, skipping.", nameplateIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
var res = nameContainer;
|
||||
|
||||
// X scale
|
||||
@@ -450,7 +468,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
|
||||
var offsetXScreen = currentConfig.LightfinderLabelOffsetX * worldScaleX;
|
||||
|
||||
// alignment based on config
|
||||
// alignment based on config setting
|
||||
switch (currentConfig.LabelAlignment)
|
||||
{
|
||||
case LabelAlignment.Left:
|
||||
@@ -469,7 +487,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
}
|
||||
else
|
||||
{
|
||||
// manual X positioning
|
||||
// manual X positioning with optional cached offset
|
||||
var cachedTextOffset = _buffers.TextOffsets[nameplateIndex];
|
||||
var hasCachedOffset = cachedTextOffset != int.MinValue;
|
||||
var baseOffsetXLocal = (!currentConfig.LightfinderLabelUseIcon && hasValidOffset && hasCachedOffset)
|
||||
@@ -489,16 +507,16 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
|
||||
// final position before smoothing
|
||||
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();
|
||||
float dt = fw->RealFrameDeltaTime;
|
||||
|
||||
//smoothing..
|
||||
//smoothing.. snap.. smooth.. snap
|
||||
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
||||
finalPosition = SmoothPosition(nameplateIndex, finalPosition, dt);
|
||||
finalPosition = SnapToPixels(finalPosition, dpiScale);
|
||||
|
||||
// prepare label info
|
||||
// prepare label info for rendering
|
||||
var pivot = (currentConfig.LightfinderAutoAlign || currentConfig.LightfinderLabelUseIcon)
|
||||
? AlignmentToPivot(alignment)
|
||||
: _defaultPivot;
|
||||
@@ -545,7 +563,23 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (fw == null)
|
||||
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;
|
||||
|
||||
if (_lastNamePlateDrawFrame == 0 || (frame - _lastNamePlateDrawFrame) > 1)
|
||||
@@ -553,34 +587,62 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
ClearLabelBuffer();
|
||||
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||
|
||||
#if DEBUG
|
||||
DebugLabelCountLastFrame = 0;
|
||||
DebugUiRectCountLastFrame = 0;
|
||||
DebugOccludedCountLastFrame = 0;
|
||||
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
//Gpose Check
|
||||
// Gpose Check - do not render.
|
||||
if (_clientState.IsGPosing)
|
||||
{
|
||||
ClearLabelBuffer();
|
||||
Array.Clear(_buffers.HasSmoothed, 0, _buffers.HasSmoothed.Length);
|
||||
_lastNamePlateDrawFrame = 0;
|
||||
|
||||
#if DEBUG
|
||||
DebugLabelCountLastFrame = 0;
|
||||
DebugUiRectCountLastFrame = 0;
|
||||
DebugOccludedCountLastFrame = 0;
|
||||
DebugLastNameplateFrame = 0;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// If nameplate addon is not visible, skip rendering
|
||||
// If nameplate addon is not visible, skip rendering entirely.
|
||||
if (!IsNamePlateAddonVisible())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugLabelCountLastFrame = 0;
|
||||
DebugUiRectCountLastFrame = 0;
|
||||
DebugOccludedCountLastFrame = 0;
|
||||
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
int copyCount;
|
||||
lock (_labelLock)
|
||||
{
|
||||
copyCount = _labelRenderCount;
|
||||
if (copyCount == 0)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugLabelCountLastFrame = 0;
|
||||
DebugUiRectCountLastFrame = 0;
|
||||
DebugOccludedCountLastFrame = 0;
|
||||
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
Array.Copy(_buffers.LabelRender, _buffers.LabelCopy, copyCount);
|
||||
}
|
||||
|
||||
var uiModule = fw != null ? fw->GetUIModule() : null;
|
||||
|
||||
var uiModule = fw->GetUIModule();
|
||||
if (uiModule != null)
|
||||
{
|
||||
var rapture = uiModule->GetRaptureAtkModule();
|
||||
@@ -599,7 +661,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
var vpPos = vp.Pos;
|
||||
|
||||
ImGuiHelpers.ForceNextWindowMainViewport();
|
||||
|
||||
ImGui.SetNextWindowPos(vp.Pos);
|
||||
ImGui.SetNextWindowSize(vp.Size);
|
||||
|
||||
@@ -610,54 +671,118 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
|
||||
ImGui.PopStyleVar(2);
|
||||
|
||||
using var drawList = PictoService.Draw();
|
||||
if (drawList == null)
|
||||
// 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();
|
||||
if (drawList == null)
|
||||
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)
|
||||
{
|
||||
ref var info = ref _buffers.LabelCopy[i];
|
||||
|
||||
// final draw position with viewport offset (only when viewports are enabled)
|
||||
var drawPos = info.ScreenPosition;
|
||||
if (useViewportOffset)
|
||||
drawPos += vpPos;
|
||||
|
||||
var font = default(ImFontPtr);
|
||||
if (info.UseIcon)
|
||||
{
|
||||
var ioFonts = ImGui.GetIO().Fonts;
|
||||
font = ioFonts.Fonts.Size > 1 ? new ImFontPtr(ioFonts.Fonts[1]) : ImGui.GetFont();
|
||||
}
|
||||
else
|
||||
{
|
||||
font = ImGui.GetFont();
|
||||
}
|
||||
|
||||
if (!font.IsNull)
|
||||
ImGui.PushFont(font);
|
||||
|
||||
// calculate size for occlusion checking
|
||||
var baseSize = ImGui.CalcTextSize(info.Text);
|
||||
var baseFontSize = ImGui.GetFontSize();
|
||||
|
||||
if (!font.IsNull)
|
||||
ImGui.PopFont();
|
||||
|
||||
// scale size based on font size
|
||||
var scale = baseFontSize > 0 ? (info.FontSize / baseFontSize) : 1f;
|
||||
var size = baseSize * scale;
|
||||
|
||||
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);
|
||||
|
||||
bool wouldOcclude = 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;
|
||||
|
||||
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();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < copyCount; ++i)
|
||||
{
|
||||
ref var info = ref _buffers.LabelCopy[i];
|
||||
|
||||
// final draw position with viewport offset
|
||||
var drawPos = info.ScreenPosition + vpPos;
|
||||
var font = default(ImFontPtr);
|
||||
if (info.UseIcon)
|
||||
{
|
||||
var ioFonts = ImGui.GetIO().Fonts;
|
||||
font = ioFonts.Fonts.Size > 1 ? new ImFontPtr(ioFonts.Fonts[1]) : ImGui.GetFont();
|
||||
}
|
||||
else
|
||||
{
|
||||
font = ImGui.GetFont();
|
||||
}
|
||||
|
||||
if (!font.IsNull)
|
||||
ImGui.PushFont(font);
|
||||
|
||||
// calculate size for occlusion checking
|
||||
var baseSize = ImGui.CalcTextSize(info.Text);
|
||||
var baseFontSize = ImGui.GetFontSize();
|
||||
|
||||
if (!font.IsNull)
|
||||
ImGui.PopFont();
|
||||
|
||||
// scale size based on font size
|
||||
var scale = baseFontSize > 0 ? (info.FontSize / baseFontSize) : 1f;
|
||||
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 labelRect = new RectF(topLeft.X, topLeft.Y, topLeft.X + size.X, topLeft.Y + size.Y);
|
||||
|
||||
// occlusion check
|
||||
if (IsOccludedByAnyUi(labelRect))
|
||||
continue;
|
||||
|
||||
drawList.AddScreenText(drawPos, info.Text, info.TextColor, info.FontSize, info.Pivot, info.EdgeColor, font);
|
||||
}
|
||||
#if DEBUG
|
||||
DebugLabelCountLastFrame = copyCount;
|
||||
DebugUiRectCountLastFrame = _uiRects.Count;
|
||||
DebugOccludedCountLastFrame = occludedThisFrame;
|
||||
DebugLastNameplateFrame = _lastNamePlateDrawFrame;
|
||||
#endif
|
||||
}
|
||||
|
||||
private static Vector2 AlignmentToPivot(AlignmentType alignment) => alignment switch
|
||||
@@ -705,8 +830,8 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (scale <= 0f)
|
||||
scale = 1f;
|
||||
|
||||
var computed = (int)System.Math.Round(rawHeight * scale);
|
||||
return System.Math.Max(1, computed);
|
||||
var computed = (int)Math.Round(rawHeight * scale);
|
||||
return Math.Max(1, computed);
|
||||
}
|
||||
|
||||
private static unsafe int GetScaledTextWidth(AtkTextNode* node)
|
||||
@@ -730,12 +855,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Resolves a cached value for the given index.
|
||||
/// </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(
|
||||
int[] cache,
|
||||
int index,
|
||||
@@ -775,9 +894,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Snapping a position to pixel grid based on DPI scale.
|
||||
/// </summary>
|
||||
/// <param name="p">Position</param>
|
||||
/// <param name="dpiScale">DPI Scale</param>
|
||||
/// <returns></returns>
|
||||
private static Vector2 SnapToPixels(Vector2 p, float dpiScale)
|
||||
{
|
||||
// snap to pixel grid
|
||||
@@ -786,15 +902,9 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Smooths the position using exponential smoothing.
|
||||
/// </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)
|
||||
{
|
||||
// exponential smoothing
|
||||
@@ -821,73 +931,193 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
return cur;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a valid screen rect for the given addon.
|
||||
/// </summary>
|
||||
/// <param name="addon">Addon UI</param>
|
||||
/// <param name="screen">Screen positioning/param>
|
||||
/// <param name="rect">RectF of Addon</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool IsFinite(float f) => !(float.IsNaN(f) || float.IsInfinity(f));
|
||||
|
||||
private static bool TryGetAddonRect(AtkUnitBase* addon, Vector2 screen, out RectF rect)
|
||||
{
|
||||
// Addon existence
|
||||
rect = default;
|
||||
if (addon == null)
|
||||
return false;
|
||||
|
||||
// Visibility check
|
||||
// Addon must be visible
|
||||
if (!addon->IsVisible)
|
||||
return false;
|
||||
|
||||
// Root must be visible
|
||||
var root = addon->RootNode;
|
||||
if (root == null || !root->IsVisible())
|
||||
return false;
|
||||
|
||||
// Size check
|
||||
float w = root->Width;
|
||||
float h = root->Height;
|
||||
if (w <= 0 || h <= 0)
|
||||
// Must have multiple nodes to be useful
|
||||
var nodeCount = addon->UldManager.NodeListCount;
|
||||
var nodeList = addon->UldManager.NodeList;
|
||||
if (nodeCount <= 1 || nodeList == null)
|
||||
return false;
|
||||
|
||||
// Local scale
|
||||
float sx = root->ScaleX; if (sx <= 0f) sx = 1f;
|
||||
float sy = root->ScaleY; if (sy <= 0f) sy = 1f;
|
||||
float rsx = GetWorldScaleX(root);
|
||||
float rsy = GetWorldScaleY(root);
|
||||
if (!IsFinite(rsx) || rsx <= 0f) rsx = 1f;
|
||||
if (!IsFinite(rsy) || rsy <= 0f) rsy = 1f;
|
||||
|
||||
// World/composed scale from Transform
|
||||
float wsx = GetWorldScaleX(root);
|
||||
float wsy = GetWorldScaleY(root);
|
||||
if (wsx <= 0f) wsx = 1f;
|
||||
if (wsy <= 0f) wsy = 1f;
|
||||
// clamp insane root scales (rare but prevents explosions)
|
||||
rsx = MathF.Min(rsx, 6f);
|
||||
rsy = MathF.Min(rsy, 6f);
|
||||
|
||||
// World scale may include parent scaling; use it if meaningfully different.
|
||||
float useX = MathF.Abs(wsx - sx) > 0.01f ? wsx : sx;
|
||||
float useY = MathF.Abs(wsy - sy) > 0.01f ? wsy : sy;
|
||||
|
||||
w *= useX;
|
||||
h *= useY;
|
||||
|
||||
if (w < 4f || h < 4f)
|
||||
float rw = root->Width * rsx;
|
||||
float rh = root->Height * rsy;
|
||||
if (!IsFinite(rw) || !IsFinite(rh) || rw <= 2f || rh <= 2f)
|
||||
return false;
|
||||
|
||||
// Screen coords
|
||||
float l = root->ScreenX;
|
||||
float t = root->ScreenY;
|
||||
float r = l + w;
|
||||
float b = t + h;
|
||||
|
||||
// Drop fullscreen-ish / insane rects
|
||||
if (w >= screen.X * 0.98f && h >= screen.Y * 0.98f)
|
||||
float rl = root->ScreenX;
|
||||
float rt = root->ScreenY;
|
||||
if (!IsFinite(rl) || !IsFinite(rt))
|
||||
return false;
|
||||
|
||||
// Drop offscreen rects
|
||||
if (l < -screen.X || t < -screen.Y || r > screen.X * 2f || b > screen.Y * 2f)
|
||||
float rr = rl + rw;
|
||||
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;
|
||||
|
||||
// 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);
|
||||
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>
|
||||
/// Refreshes the cached UI rects for occlusion checking.
|
||||
/// </summary>
|
||||
/// <param name="unitMgr">Unit Manager</param>
|
||||
private void RefreshUiRects(RaptureAtkUnitManager* unitMgr)
|
||||
{
|
||||
_uiRects.Clear();
|
||||
@@ -911,13 +1141,15 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
if (TryGetAddonRect(addon, screen, out var r))
|
||||
_uiRects.Add(r);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
DebugUiRectCountLastFrame = _uiRects.Count;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the given label rect occluded by any UI rects?
|
||||
/// </summary>
|
||||
/// <param name="labelRect">UI/Label Rect</param>
|
||||
/// <returns>Is occluded or not</returns>
|
||||
private bool IsOccludedByAnyUi(RectF labelRect)
|
||||
{
|
||||
for (int i = 0; i < _uiRects.Count; i++)
|
||||
@@ -931,8 +1163,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Gets the world scale X of the given node.
|
||||
/// </summary>
|
||||
/// <param name="n">Node</param>
|
||||
/// <returns>World Scale of node</returns>
|
||||
private static float GetWorldScaleX(AtkResNode* n)
|
||||
{
|
||||
var t = n->Transform;
|
||||
@@ -942,8 +1172,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Gets the world scale Y of the given node.
|
||||
/// </summary>
|
||||
/// <param name="n">Node</param>
|
||||
/// <returns>World Scale of node</returns>
|
||||
private static float GetWorldScaleY(AtkResNode* n)
|
||||
{
|
||||
var t = n->Transform;
|
||||
@@ -953,8 +1181,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Normalize an icon glyph input into a valid string.
|
||||
/// </summary>
|
||||
/// <param name="rawInput">Raw glyph input</param>
|
||||
/// <returns>Normalized glyph input</returns>
|
||||
internal static string NormalizeIconGlyph(string? rawInput)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawInput))
|
||||
@@ -982,7 +1208,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Is the nameplate addon visible?
|
||||
/// </summary>
|
||||
/// <returns>Is it visible?</returns>
|
||||
private bool IsNamePlateAddonVisible()
|
||||
{
|
||||
if (_mpNameplateAddon == null)
|
||||
@@ -992,20 +1217,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
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
|
||||
{
|
||||
public NameplateLabelInfo(
|
||||
@@ -1043,6 +1254,15 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
||||
.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()
|
||||
{
|
||||
_needsLabelRefresh = true;
|
||||
@@ -1066,7 +1286,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Update the active broadcasting CIDs.
|
||||
/// </summary>
|
||||
/// <param name="cids">Inbound new CIDs</param>
|
||||
public void UpdateBroadcastingCids(IEnumerable<string> cids)
|
||||
{
|
||||
var newSet = cids.ToImmutableHashSet(StringComparer.Ordinal);
|
||||
@@ -1096,7 +1315,7 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
public NameplateBuffers()
|
||||
{
|
||||
TextOffsets = new int[AddonNamePlate.NumNamePlateObjects];
|
||||
System.Array.Fill(TextOffsets, int.MinValue);
|
||||
Array.Fill(TextOffsets, int.MinValue);
|
||||
}
|
||||
|
||||
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 Vector2[] SmoothedPos = new Vector2[AddonNamePlate.NumNamePlateObjects];
|
||||
|
||||
public bool[] HasSmoothed = new bool[AddonNamePlate.NumNamePlateObjects];
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
System.Array.Clear(TextWidths, 0, TextWidths.Length);
|
||||
System.Array.Clear(TextHeights, 0, TextHeights.Length);
|
||||
System.Array.Clear(ContainerHeights, 0, ContainerHeights.Length);
|
||||
System.Array.Fill(TextOffsets, int.MinValue);
|
||||
Array.Clear(TextWidths, 0, TextWidths.Length);
|
||||
Array.Clear(TextHeights, 0, TextHeights.Length);
|
||||
Array.Clear(ContainerHeights, 0, ContainerHeights.Length);
|
||||
Array.Fill(TextOffsets, int.MinValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the LightFinder Plate Handler.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation Token</param>
|
||||
/// <returns>Task Completed</returns>
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Init();
|
||||
@@ -1134,8 +1350,6 @@ public unsafe class LightFinderPlateHandler : IHostedService, IMediatorSubscribe
|
||||
/// <summary>
|
||||
/// Stops the LightFinder Plate Handler.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Cancellation Token</param>
|
||||
/// <returns>Task Completed</returns>
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Uninit();
|
||||
|
||||
@@ -67,7 +67,7 @@ public class LightFinderService : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
try
|
||||
{
|
||||
var cid = await _dalamudUtil.GetCIDAsync().ConfigureAwait(false);
|
||||
var cid = _dalamudUtil.GetCID();
|
||||
return cid.ToString().GetHash256();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -101,9 +101,9 @@ public class ServerConfigurationManager
|
||||
}
|
||||
hasMulti = false;
|
||||
|
||||
var charaName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult();
|
||||
var worldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
||||
var cid = _dalamudUtil.GetCIDAsync().GetAwaiter().GetResult();
|
||||
var charaName = _dalamudUtil.GetPlayerName();
|
||||
var worldId = _dalamudUtil.GetHomeWorldId();
|
||||
var cid = _dalamudUtil.GetCID();
|
||||
|
||||
var auth = currentServer.Authentications.FindAll(f => string.Equals(f.CharacterName, charaName) && f.WorldId == worldId);
|
||||
if (auth.Count >= 2)
|
||||
@@ -148,9 +148,9 @@ public class ServerConfigurationManager
|
||||
}
|
||||
hasMulti = false;
|
||||
|
||||
var charaName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult();
|
||||
var worldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
||||
var cid = _dalamudUtil.GetCIDAsync().GetAwaiter().GetResult();
|
||||
var charaName = _dalamudUtil.GetPlayerName();
|
||||
var worldId = _dalamudUtil.GetHomeWorldId();
|
||||
var cid = _dalamudUtil.GetCID();
|
||||
if (!currentServer.Authentications.Any() && currentServer.SecretKeys.Any())
|
||||
{
|
||||
currentServer.Authentications.Add(new Authentication()
|
||||
@@ -268,16 +268,16 @@ public class ServerConfigurationManager
|
||||
{
|
||||
if (serverSelectionIndex == -1) serverSelectionIndex = CurrentServerIndex;
|
||||
var server = GetServerByIndex(serverSelectionIndex);
|
||||
if (server.Authentications.Exists(c => string.Equals(c.CharacterName, _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(), StringComparison.Ordinal)
|
||||
&& c.WorldId == _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult()))
|
||||
if (server.Authentications.Exists(c => string.Equals(c.CharacterName, _dalamudUtil.GetPlayerName(), StringComparison.Ordinal)
|
||||
&& c.WorldId == _dalamudUtil.GetHomeWorldId()))
|
||||
return;
|
||||
|
||||
server.Authentications.Add(new Authentication()
|
||||
{
|
||||
CharacterName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult(),
|
||||
WorldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult(),
|
||||
CharacterName = _dalamudUtil.GetPlayerName(),
|
||||
WorldId = _dalamudUtil.GetHomeWorldId(),
|
||||
SecretKeyIdx = !server.UseOAuth2 ? server.SecretKeys.Last().Key : -1,
|
||||
LastSeenCID = _dalamudUtil.GetCIDAsync().GetAwaiter().GetResult()
|
||||
LastSeenCID = _dalamudUtil.GetCID()
|
||||
});
|
||||
Save();
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ public class UiFactory
|
||||
groupData: groupData,
|
||||
isLightfinderContext: isLightfinderContext,
|
||||
lightfinderCid: lightfinderCid,
|
||||
performanceCollector: _performanceCollectorService);
|
||||
performanceCollector: _performanceCollectorService,
|
||||
_apiController);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -968,20 +968,25 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> source)
|
||||
{
|
||||
var clone = new Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>(source.Count);
|
||||
|
||||
foreach (var (objectKind, entries) in source)
|
||||
{
|
||||
var entryClone = new Dictionary<string, CharacterAnalyzer.FileDataEntry>(entries.Count, entries.Comparer);
|
||||
|
||||
foreach (var (hash, entry) in entries)
|
||||
{
|
||||
entryClone[hash] = new CharacterAnalyzer.FileDataEntry(
|
||||
hash,
|
||||
entry.FileType,
|
||||
entry.GamePaths.ToList(),
|
||||
entry.FilePaths.ToList(),
|
||||
entry.OriginalSize,
|
||||
entry.CompressedSize,
|
||||
entry.Triangles);
|
||||
hash: hash,
|
||||
fileType: entry.FileType,
|
||||
gamePaths: entry.GamePaths?.ToList() ?? [],
|
||||
filePaths: entry.FilePaths?.ToList() ?? [],
|
||||
originalSize: entry.OriginalSize,
|
||||
compressedSize: entry.CompressedSize,
|
||||
triangles: entry.Triangles,
|
||||
cacheEntries: entry.CacheEntries
|
||||
);
|
||||
}
|
||||
|
||||
clone[objectKind] = entryClone;
|
||||
}
|
||||
|
||||
|
||||
@@ -368,8 +368,8 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
{
|
||||
try
|
||||
{
|
||||
var cid = await _dalamudUtilService.GetCIDAsync().ConfigureAwait(false);
|
||||
var hashedCid = cid.ToString().GetHash256();
|
||||
var cid = _dalamudUtilService.GetCID();
|
||||
var hashedCid = cid.ToString().GetHash256();
|
||||
lock (_localHashedCidLock)
|
||||
{
|
||||
_localHashedCid = hashedCid;
|
||||
|
||||
@@ -1380,19 +1380,55 @@ public class LightFinderUI : WindowMediatorSubscriberBase
|
||||
#endregion
|
||||
|
||||
#if DEBUG
|
||||
#region Debug Tab
|
||||
if (ImGui.BeginTabItem("Debug"))
|
||||
{
|
||||
if (ImGui.CollapsingHeader("LightFinder Plates", ImGuiTreeNodeFlags.DefaultOpen))
|
||||
{
|
||||
var h = _lightFinderPlateHandler;
|
||||
|
||||
private void DrawDebugTab()
|
||||
{
|
||||
ImGui.Text("Broadcast Cache");
|
||||
var enabled = h.DebugEnabled;
|
||||
if (ImGui.Checkbox("Enable LightFinder debug", ref enabled))
|
||||
h.DebugEnabled = enabled;
|
||||
|
||||
if (ImGui.BeginTable("##BroadcastCacheTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY, new Vector2(-1, 200f)))
|
||||
{
|
||||
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Broadcasting", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
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");
|
||||
|
||||
if (ImGui.BeginTable("##BroadcastCacheTable", 4,
|
||||
ImGuiTableFlags.RowBg | ImGuiTableFlags.Borders | ImGuiTableFlags.ScrollY,
|
||||
new Vector2(-1, 225f)))
|
||||
{
|
||||
ImGui.TableSetupColumn("CID", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("IsBroadcasting", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Expires In", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Syncshell GID", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private bool _pairDiagnosticsEnabled;
|
||||
private string? _selectedPairDebugUid = null;
|
||||
private string _lightfinderIconInput = string.Empty;
|
||||
private bool _showLightfinderRendererWarning = false;
|
||||
private LightfinderLabelRenderer _pendingLightfinderRenderer = LightfinderLabelRenderer.Pictomancy;
|
||||
private bool _lightfinderIconInputInitialized = false;
|
||||
private int _lightfinderIconPresetIndex = -1;
|
||||
private static readonly LightlessConfig DefaultConfig = new();
|
||||
@@ -2387,7 +2389,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
var labelRenderer = _configService.Current.LightfinderLabelRenderer;
|
||||
var labelRendererLabel = labelRenderer switch
|
||||
{
|
||||
LightfinderLabelRenderer.SignatureHook => "Native nameplate (sig hook)",
|
||||
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||
_ => "ImGui Overlay",
|
||||
};
|
||||
|
||||
@@ -2397,18 +2399,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
var optionLabel = option switch
|
||||
{
|
||||
LightfinderLabelRenderer.SignatureHook => "Native Nameplate (sig hook)",
|
||||
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||
_ => "ImGui Overlay",
|
||||
};
|
||||
|
||||
var selected = option == labelRenderer;
|
||||
if (ImGui.Selectable(optionLabel, selected))
|
||||
{
|
||||
_configService.Current.LightfinderLabelRenderer = option;
|
||||
_configService.Save();
|
||||
_nameplateService.RequestRedraw();
|
||||
if (option == LightfinderLabelRenderer.SignatureHook)
|
||||
{
|
||||
_pendingLightfinderRenderer = option;
|
||||
_showLightfinderRendererWarning = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_configService.Current.LightfinderLabelRenderer = option;
|
||||
_configService.Save();
|
||||
_nameplateService.RequestRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
if (selected)
|
||||
ImGui.SetItemDefaultFocus();
|
||||
}
|
||||
@@ -2416,6 +2425,34 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
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.");
|
||||
|
||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||
@@ -2602,7 +2639,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
var selected = i == _lightfinderIconPresetIndex;
|
||||
if (ImGui.Selectable(preview, selected))
|
||||
{
|
||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(optionGlyph);
|
||||
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(optionGlyph);
|
||||
_lightfinderIconPresetIndex = i;
|
||||
}
|
||||
}
|
||||
@@ -4083,7 +4120,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private void RefreshLightfinderIconState()
|
||||
{
|
||||
var normalized = LightFinderPlateHandler.NormalizeIconGlyph(_configService.Current.LightfinderLabelIconGlyph);
|
||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(normalized);
|
||||
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(normalized);
|
||||
_lightfinderIconInputInitialized = true;
|
||||
|
||||
_lightfinderIconPresetIndex = -1;
|
||||
@@ -4101,7 +4138,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
_configService.Current.LightfinderLabelIconGlyph = normalizedGlyph;
|
||||
_configService.Save();
|
||||
_lightfinderIconInput = LightFinderPlateHandler.ToIconEditorString(normalizedGlyph);
|
||||
_lightfinderIconInput = LightFinderPlateHandler.NormalizeIconGlyph(normalizedGlyph);
|
||||
_lightfinderIconPresetIndex = presetIndex;
|
||||
_lightfinderIconInputInitialized = true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using LightlessSync.Services.ServerConfiguration;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.UI.Tags;
|
||||
using LightlessSync.Utils;
|
||||
using LightlessSync.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
private readonly PairUiService _pairUiService;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly ProfileTagService _profileTagService;
|
||||
private readonly ApiController _apiController;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
private readonly UserData? _userData;
|
||||
private readonly GroupData? _groupData;
|
||||
@@ -60,7 +62,8 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
GroupData? groupData,
|
||||
bool isLightfinderContext,
|
||||
string? lightfinderCid,
|
||||
PerformanceCollectorService performanceCollector)
|
||||
PerformanceCollectorService performanceCollector,
|
||||
ApiController apiController)
|
||||
: base(logger, mediator, BuildWindowTitle(
|
||||
userData,
|
||||
groupData,
|
||||
@@ -94,6 +97,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
.Apply();
|
||||
|
||||
IsOpen = true;
|
||||
_apiController = apiController;
|
||||
}
|
||||
|
||||
public Pair? Pair { get; }
|
||||
@@ -248,19 +252,33 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
ResetBannerTexture();
|
||||
_lastBannerPicture = bannerBytes;
|
||||
}
|
||||
|
||||
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;
|
||||
bool directPair = false;
|
||||
bool youPaused = false;
|
||||
bool theyPaused = false;
|
||||
List<string> syncshellLines = [];
|
||||
|
||||
if (!_isLightfinderContext)
|
||||
{
|
||||
noteText = _serverManager.GetNoteForUid(_userData!.UID);
|
||||
}
|
||||
|
||||
if (!_isLightfinderContext && Pair != null)
|
||||
{
|
||||
var snapshot = _pairUiService.GetSnapshot();
|
||||
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
|
||||
|
||||
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
|
||||
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
|
||||
|
||||
@@ -282,11 +300,15 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
||||
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
|
||||
? groupInfo.GroupAliasOrGID
|
||||
: gid;
|
||||
|
||||
var groupNote = _serverManager.GetNoteForGid(gid);
|
||||
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelfProfile)
|
||||
statusLabel = "Online";
|
||||
}
|
||||
|
||||
var presenceTokens = new List<PresenceToken>
|
||||
|
||||
@@ -431,7 +431,7 @@ public class TopTabMenu
|
||||
|
||||
try
|
||||
{
|
||||
var myCidHash = (await _dalamudUtilService.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
|
||||
var myCidHash = _dalamudUtilService.GetCID().ToString().GetHash256();
|
||||
await _apiController.TryPairWithContentId(request.HashedCid).ConfigureAwait(false);
|
||||
_pairRequestService.RemoveRequest(request.HashedCid);
|
||||
|
||||
|
||||
@@ -993,18 +993,33 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
_refocusChatInput = true;
|
||||
_refocusChatInputKey = channel.Key;
|
||||
var sanitized = SanitizeOutgoingDraft(draft);
|
||||
|
||||
var draftAtSend = draft;
|
||||
var sanitized = SanitizeOutgoingDraft(draftAtSend);
|
||||
|
||||
if (sanitized is not null)
|
||||
{
|
||||
TrackPendingDraftClear(channel.Key, sanitized);
|
||||
if (TrySendDraft(channel, sanitized))
|
||||
draft = string.Empty;
|
||||
_draftMessages[channel.Key] = draft;
|
||||
_scrollToBottom = true;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
_scrollToBottom = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
RemovePendingDraftClear(channel.Key, sanitized);
|
||||
}
|
||||
try
|
||||
{
|
||||
var succeeded = await _zoneChatService.SendMessageAsync(channel.Descriptor, sanitized).ConfigureAwait(false);
|
||||
if (!succeeded)
|
||||
{
|
||||
RemovePendingDraftClear(channel.Key, sanitized);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to send chat message");
|
||||
RemovePendingDraftClear(channel.Key, sanitized);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public partial class ApiController
|
||||
CensusDataDto? censusDto = null;
|
||||
if (_serverManager.SendCensusData && _lastCensus != null)
|
||||
{
|
||||
var world = await _dalamudUtil.GetWorldIdAsync().ConfigureAwait(false);
|
||||
var world = _dalamudUtil.GetWorldId();
|
||||
censusDto = new((ushort)world, _lastCensus.RaceId, _lastCensus.TribeId, _lastCensus.Gender);
|
||||
Logger.LogDebug("Attaching Census Data: {data}", censusDto);
|
||||
}
|
||||
|
||||
@@ -544,8 +544,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
var charaName = _dalamudUtil.GetPlayerNameAsync().GetAwaiter().GetResult();
|
||||
var worldId = _dalamudUtil.GetHomeWorldIdAsync().GetAwaiter().GetResult();
|
||||
var charaName = _dalamudUtil.GetPlayerName();
|
||||
var worldId = _dalamudUtil.GetHomeWorldId();
|
||||
var auth = _serverManager.CurrentServer.Authentications.Find(f => string.Equals(f.CharacterName, charaName, StringComparison.Ordinal) && f.WorldId == worldId);
|
||||
if (auth?.AutoLogin ?? false)
|
||||
{
|
||||
@@ -653,7 +653,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
||||
CensusDataDto? dto = null;
|
||||
if (_serverManager.SendCensusData && _lastCensus != null)
|
||||
{
|
||||
var world = await _dalamudUtil.GetWorldIdAsync().ConfigureAwait(false);
|
||||
var world = _dalamudUtil.GetWorldId();
|
||||
dto = new((ushort)world, _lastCensus.RaceId, _lastCensus.TribeId, _lastCensus.Gender);
|
||||
Logger.LogDebug("Attaching Census Data: {data}", dto);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
||||
result = await _httpClient.PostAsync(tokenUri, new FormUrlEncodedContent(
|
||||
[
|
||||
new KeyValuePair<string, string>("auth", auth),
|
||||
new KeyValuePair<string, string>("charaIdent", await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false)),
|
||||
new KeyValuePair<string, string>("charaIdent", _dalamudUtil.GetPlayerNameHashed()),
|
||||
]), ct).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
@@ -152,7 +152,7 @@ public sealed class TokenProvider : IDisposable, IMediatorSubscriber
|
||||
JwtIdentifier jwtIdentifier;
|
||||
try
|
||||
{
|
||||
var playerIdentifier = await _dalamudUtil.GetPlayerNameHashedAsync().ConfigureAwait(false);
|
||||
var playerIdentifier = _dalamudUtil.GetPlayerNameHashed();
|
||||
|
||||
if (string.IsNullOrEmpty(playerIdentifier))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user