Compare commits
16 Commits
test-abel
...
2.0.2.72-D
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab305a249c | ||
|
|
9d104a9dd8 | ||
|
|
4eec363cd2 | ||
|
|
d00df84ed6 | ||
|
|
bcd3bd5ca2 | ||
|
|
9048b3bd87 | ||
|
|
c1829a9837 | ||
|
|
a2ed9f8d2b | ||
| 8e08da7471 | |||
|
|
cca23f6e05 | ||
|
|
3205e6e0c3 | ||
|
|
d16e46200d | ||
|
|
5fc13647ae | ||
|
|
39d5d9d7c1 | ||
|
|
c19db58ead | ||
| 30717ba200 |
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>2.0.3</Version>
|
||||
<Version>2.0.2.72</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||
|
||||
@@ -123,16 +123,22 @@ public class PlayerDataFactory
|
||||
{
|
||||
if (playerPointer == IntPtr.Zero)
|
||||
return true;
|
||||
try
|
||||
{
|
||||
var character = (Character*)playerPointer;
|
||||
if (character == null)
|
||||
return true;
|
||||
|
||||
var character = (Character*)playerPointer;
|
||||
if (character == null)
|
||||
var gameObject = &character->GameObject;
|
||||
if (gameObject == null)
|
||||
return true;
|
||||
|
||||
return gameObject->DrawObject == null;
|
||||
}
|
||||
catch (AccessViolationException)
|
||||
{
|
||||
return true;
|
||||
|
||||
var gameObject = &character->GameObject;
|
||||
if (gameObject == null)
|
||||
return true;
|
||||
|
||||
return gameObject->DrawObject == null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsCacheFresh(CacheEntry entry)
|
||||
@@ -169,11 +175,6 @@ public class PlayerDataFactory
|
||||
using var cts = new CancellationTokenSource(_hardBuildTimeout);
|
||||
var fragment = await CreateCharacterDataInternal(obj, cts.Token).ConfigureAwait(false);
|
||||
|
||||
fragment.FileReplacements =
|
||||
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
||||
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||
var allowedExtensions = CacheMonitor.AllowedFileExtensions;
|
||||
fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !allowedExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||
_characterBuildCache[key] = new CacheEntry(fragment, DateTime.UtcNow);
|
||||
PruneCharacterCacheIfNeeded();
|
||||
|
||||
@@ -219,10 +220,6 @@ public class PlayerDataFactory
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// get all remaining paths and resolve them
|
||||
var transientPaths = ManageSemiTransientData(objectKind);
|
||||
var resolvedTransientPaths = transientPaths.Count == 0
|
||||
? new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly()
|
||||
: await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
if (await CheckForNullDrawObject(playerRelatedObject.Address).ConfigureAwait(false))
|
||||
@@ -672,7 +669,7 @@ public class PlayerDataFactory
|
||||
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||
else
|
||||
{
|
||||
resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()];
|
||||
resolvedPaths[filePath] = [forwardPathsLower[i]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
if (msg.Address == Address)
|
||||
{
|
||||
_haltProcessing = false;
|
||||
Refresh();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -176,30 +177,36 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
Mediator.Publish(new GameObjectHandlerDestroyedMessage(this, _isOwnedObject));
|
||||
}
|
||||
|
||||
private unsafe void CheckAndUpdateObject()
|
||||
private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true);
|
||||
|
||||
private unsafe void CheckAndUpdateObject(bool allowPublish = true)
|
||||
{
|
||||
var prevAddr = Address;
|
||||
var prevDrawObj = DrawObjectAddress;
|
||||
|
||||
Address = _getAddress();
|
||||
|
||||
if (Address != IntPtr.Zero)
|
||||
{
|
||||
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
|
||||
var drawObjAddr = (IntPtr)gameObject->DrawObject;
|
||||
DrawObjectAddress = drawObjAddr;
|
||||
DrawObjectAddress = (IntPtr)gameObject->DrawObject;
|
||||
EntityId = gameObject->EntityId;
|
||||
CurrentDrawCondition = DrawCondition.None;
|
||||
|
||||
var chara = (Character*)Address;
|
||||
var newName = chara->GameObject.NameString;
|
||||
|
||||
if (!string.IsNullOrEmpty(newName) && !string.Equals(newName, Name, StringComparison.Ordinal))
|
||||
Name = newName;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawObjectAddress = IntPtr.Zero;
|
||||
EntityId = uint.MaxValue;
|
||||
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||
}
|
||||
|
||||
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
||||
|
||||
if (_haltProcessing) return;
|
||||
if (_haltProcessing || !allowPublish) return;
|
||||
|
||||
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
||||
bool addrDiff = Address != prevAddr;
|
||||
@@ -356,12 +363,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
private void FrameworkUpdate()
|
||||
{
|
||||
if (!_delayedZoningTask?.IsCompleted ?? false) return;
|
||||
|
||||
try
|
||||
{
|
||||
_performanceCollector.LogPerformance(this, $"CheckAndUpdateObject>{(_isOwnedObject ? "Self" : "Other")}+{ObjectKind}/{(string.IsNullOrEmpty(Name) ? "Unk" : Name)}"
|
||||
+ $"+{Address.ToString("X")}", CheckAndUpdateObject);
|
||||
var zoningDelayActive = !(_delayedZoningTask?.IsCompleted ?? true);
|
||||
_performanceCollector.LogPerformance(this, $"CheckAndUpdateObject>{(_isOwnedObject ? "Self" : "Other")}+{ObjectKind}/{(string.IsNullOrEmpty(Name) ? "Unk" : Name)}", () => CheckAndUpdateObject(allowPublish: !zoningDelayActive));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -462,6 +467,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
Logger.LogDebug("[{this}] Delay after zoning complete", this);
|
||||
_zoningCts.Dispose();
|
||||
}
|
||||
});
|
||||
}, _zoningCts.Token);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ using Microsoft.Extensions.Logging;
|
||||
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||
using FileReplacementDataComparer = LightlessSync.PlayerData.Data.FileReplacementDataComparer;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
|
||||
namespace LightlessSync.PlayerData.Pairs;
|
||||
|
||||
@@ -96,6 +95,9 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
private readonly Dictionary<ObjectKind, HashSet<PlayerChanges>> _pendingOwnedChanges = new();
|
||||
private CancellationTokenSource? _ownedRetryCts;
|
||||
private Task _ownedRetryTask = Task.CompletedTask;
|
||||
|
||||
|
||||
|
||||
private static readonly TimeSpan OwnedRetryInitialDelay = TimeSpan.FromSeconds(1);
|
||||
private static readonly TimeSpan OwnedRetryMaxDelay = TimeSpan.FromSeconds(10);
|
||||
private static readonly TimeSpan OwnedRetryStaleDataGrace = TimeSpan.FromMinutes(5);
|
||||
@@ -109,6 +111,9 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
};
|
||||
|
||||
private readonly ConcurrentDictionary<string, byte> _blockedPapHashes = new(StringComparer.OrdinalIgnoreCase);
|
||||
private AnimationValidationMode _lastAnimMode = (AnimationValidationMode)(-1);
|
||||
private bool _lastAllowOneBasedShift;
|
||||
private bool _lastAllowNeighborTolerance;
|
||||
private readonly ConcurrentDictionary<string, byte> _dumpedRemoteSkeletonForHash = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private DateTime? _invisibleSinceUtc;
|
||||
@@ -2455,6 +2460,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
|
||||
try
|
||||
{
|
||||
RefreshPapBlockCacheIfAnimSettingsChanged();
|
||||
|
||||
var replacementList = charaData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList();
|
||||
Parallel.ForEach(replacementList, new ParallelOptions()
|
||||
{
|
||||
@@ -2855,6 +2862,26 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshPapBlockCacheIfAnimSettingsChanged()
|
||||
{
|
||||
var cfg = _configService.Current;
|
||||
|
||||
if (cfg.AnimationValidationMode != _lastAnimMode
|
||||
|| cfg.AnimationAllowOneBasedShift != _lastAllowOneBasedShift
|
||||
|| cfg.AnimationAllowNeighborIndexTolerance != _lastAllowNeighborTolerance)
|
||||
{
|
||||
_lastAnimMode = cfg.AnimationValidationMode;
|
||||
_lastAllowOneBasedShift = cfg.AnimationAllowOneBasedShift;
|
||||
_lastAllowNeighborTolerance = cfg.AnimationAllowNeighborIndexTolerance;
|
||||
|
||||
_blockedPapHashes.Clear();
|
||||
_dumpedRemoteSkeletonForHash.Clear();
|
||||
|
||||
Logger.LogDebug("{handler}: Cleared blocked PAP cache due to animation setting change (mode={mode}, shift={shift}, neigh={neigh})",
|
||||
GetLogIdentifier(), _lastAnimMode, _lastAllowOneBasedShift, _lastAllowNeighborTolerance);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SplitPapMappings(
|
||||
Dictionary<(string GamePath, string? Hash), string> moddedPaths,
|
||||
out Dictionary<(string GamePath, string? Hash), string> withoutPap,
|
||||
@@ -2879,6 +2906,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
Dictionary<(string GamePath, string? Hash), string> papOnly,
|
||||
CancellationToken token)
|
||||
{
|
||||
RefreshPapBlockCacheIfAnimSettingsChanged();
|
||||
|
||||
var mode = _configService.Current.AnimationValidationMode;
|
||||
var allowBasedShift = _configService.Current.AnimationAllowOneBasedShift;
|
||||
var allownNightIndex = _configService.Current.AnimationAllowNeighborIndexTolerance;
|
||||
|
||||
@@ -123,7 +123,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
services.AddSingleton<HubFactory>();
|
||||
services.AddSingleton<FileUploadManager>();
|
||||
services.AddSingleton<FileTransferOrchestrator>();
|
||||
services.AddSingleton<FileDownloadDeduplicator>();
|
||||
services.AddSingleton<LightlessPlugin>();
|
||||
services.AddSingleton<LightlessProfileManager>();
|
||||
services.AddSingleton<TextureCompressionService>();
|
||||
|
||||
@@ -955,56 +955,103 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
|
||||
private unsafe void CheckCharacterForDrawing(nint address, string characterName)
|
||||
{
|
||||
var gameObj = (GameObject*)address;
|
||||
var drawObj = gameObj->DrawObject;
|
||||
bool isDrawing = false;
|
||||
bool isDrawingChanged = false;
|
||||
if ((nint)drawObj != IntPtr.Zero)
|
||||
if (address == nint.Zero)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000;
|
||||
if (!isDrawing)
|
||||
var gameObj = (GameObject*)address;
|
||||
|
||||
if (gameObj == null)
|
||||
return;
|
||||
|
||||
if (!_objectTable.Any(o => o?.Address == address))
|
||||
{
|
||||
isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0;
|
||||
_logger.LogDebug("Character {name} at {addr} no longer in object table", characterName, address.ToString("X"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameObj->ObjectKind == 0)
|
||||
return;
|
||||
|
||||
var drawObj = gameObj->DrawObject;
|
||||
bool isDrawing = false;
|
||||
bool isDrawingChanged = false;
|
||||
|
||||
if ((nint)drawObj != IntPtr.Zero)
|
||||
{
|
||||
try
|
||||
{
|
||||
isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000;
|
||||
}
|
||||
catch (AccessViolationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDrawing)
|
||||
{
|
||||
isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0;
|
||||
if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
||||
&& !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal))
|
||||
try
|
||||
{
|
||||
_lastGlobalBlockPlayer = characterName;
|
||||
_lastGlobalBlockReason = "HasModelFilesInSlotLoaded";
|
||||
isDrawingChanged = true;
|
||||
var charBase = (CharacterBase*)drawObj;
|
||||
if (charBase != null)
|
||||
{
|
||||
isDrawing = charBase->HasModelInSlotLoaded != 0;
|
||||
if (!isDrawing)
|
||||
{
|
||||
isDrawing = charBase->HasModelFilesInSlotLoaded != 0;
|
||||
if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
||||
&& !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal))
|
||||
{
|
||||
_lastGlobalBlockPlayer = characterName;
|
||||
_lastGlobalBlockReason = "HasModelFilesInSlotLoaded";
|
||||
isDrawingChanged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
||||
&& !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal))
|
||||
{
|
||||
_lastGlobalBlockPlayer = characterName;
|
||||
_lastGlobalBlockReason = "HasModelInSlotLoaded";
|
||||
isDrawingChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (AccessViolationException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
||||
&& !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal))
|
||||
&& !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal))
|
||||
{
|
||||
_lastGlobalBlockPlayer = characterName;
|
||||
_lastGlobalBlockReason = "HasModelInSlotLoaded";
|
||||
_lastGlobalBlockReason = "RenderFlags";
|
||||
isDrawingChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if (isDrawingChanged)
|
||||
{
|
||||
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
|
||||
&& !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal))
|
||||
{
|
||||
_lastGlobalBlockPlayer = characterName;
|
||||
_lastGlobalBlockReason = "RenderFlags";
|
||||
isDrawingChanged = true;
|
||||
}
|
||||
_logger.LogTrace("Global draw block: START => {name} ({reason})", characterName, _lastGlobalBlockReason);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDrawingChanged)
|
||||
IsAnythingDrawing |= isDrawing;
|
||||
}
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
_logger.LogTrace("Global draw block: START => {name} ({reason})", characterName, _lastGlobalBlockReason);
|
||||
_logger.LogDebug(ex, "Memory access violation checking character {name} at {addr}", characterName, address.ToString("X"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unexpected error checking character {name} at {addr}", characterName, address.ToString("X"));
|
||||
}
|
||||
|
||||
IsAnythingDrawing |= isDrawing;
|
||||
}
|
||||
|
||||
private void FrameworkOnUpdate(IFramework framework)
|
||||
@@ -1033,38 +1080,74 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
}
|
||||
|
||||
var playerDescriptors = _actorObjectService.PlayerDescriptors;
|
||||
for (var i = 0; i < playerDescriptors.Count; i++)
|
||||
var descriptorCount = playerDescriptors.Count;
|
||||
|
||||
for (var i = 0; i < descriptorCount; i++)
|
||||
{
|
||||
var actor = playerDescriptors[i];
|
||||
|
||||
var playerAddress = actor.Address;
|
||||
if (playerAddress == nint.Zero)
|
||||
continue;
|
||||
|
||||
if (actor.ObjectIndex >= 200)
|
||||
continue;
|
||||
|
||||
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime)
|
||||
try
|
||||
{
|
||||
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsAnythingDrawing)
|
||||
{
|
||||
var gameObj = (GameObject*)playerAddress;
|
||||
var currentName = gameObj != null ? gameObj->NameString ?? string.Empty : string.Empty;
|
||||
var charaName = string.IsNullOrEmpty(currentName) ? actor.Name : currentName;
|
||||
CheckCharacterForDrawing(playerAddress, charaName);
|
||||
if (IsAnythingDrawing)
|
||||
if (i >= playerDescriptors.Count)
|
||||
break;
|
||||
|
||||
var actor = playerDescriptors[i];
|
||||
|
||||
var playerAddress = actor.Address;
|
||||
if (playerAddress == nint.Zero)
|
||||
continue;
|
||||
|
||||
if (actor.ObjectIndex >= 200)
|
||||
continue;
|
||||
|
||||
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime)
|
||||
{
|
||||
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsAnythingDrawing)
|
||||
{
|
||||
try
|
||||
{
|
||||
var gameObj = (GameObject*)playerAddress;
|
||||
|
||||
if (gameObj == null || gameObj->ObjectKind == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string currentName;
|
||||
try
|
||||
{
|
||||
currentName = gameObj->NameString ?? string.Empty;
|
||||
}
|
||||
catch (AccessViolationException)
|
||||
{
|
||||
currentName = string.Empty;
|
||||
}
|
||||
|
||||
var charaName = string.IsNullOrEmpty(currentName) ? actor.Name : currentName;
|
||||
|
||||
CheckCharacterForDrawing(playerAddress, charaName);
|
||||
|
||||
if (IsAnythingDrawing)
|
||||
break;
|
||||
}
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Access violation on GameObject pointer for actor {index} at {addr}", i, playerAddress.ToString("X"));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (AccessViolationException ex)
|
||||
{
|
||||
break;
|
||||
_logger.LogDebug(ex, "Access violation processing actor {index} - object likely destroyed", i);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unexpected error processing actor {index}", i);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer))
|
||||
{
|
||||
|
||||
@@ -52,12 +52,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private readonly LightlessConfigService _configService;
|
||||
private readonly LightlessMediator _lightlessMediator;
|
||||
private readonly PairLedger _pairLedger;
|
||||
private readonly ConcurrentDictionary<GameObjectHandler, IReadOnlyDictionary<string, FileDownloadStatus>> _currentDownloads = new();
|
||||
private readonly DrawEntityFactory _drawEntityFactory;
|
||||
private readonly FileUploadManager _fileTransferManager;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
|
||||
private readonly PairUiService _pairUiService;
|
||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
|
||||
private readonly ServerConfigurationManager _serverManager;
|
||||
private readonly TagHandler _tagHandler;
|
||||
private readonly UiSharedService _uiSharedService;
|
||||
@@ -171,9 +167,12 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => UiSharedService_GposeStart());
|
||||
Mediator.Subscribe<CutsceneEndMessage>(this, (_) => UiSharedService_GposeEnd());
|
||||
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) => _currentDownloads[msg.DownloadId] = msg.DownloadStatus);
|
||||
Mediator.Subscribe<DownloadStartedMessage>(this, msg =>
|
||||
{
|
||||
_currentDownloads[msg.DownloadId] = new Dictionary<string, FileDownloadStatus>(msg.DownloadStatus, StringComparer.Ordinal);
|
||||
});
|
||||
Mediator.Subscribe<DownloadFinishedMessage>(this, (msg) => _currentDownloads.TryRemove(msg.DownloadId, out _));
|
||||
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = DrawFolders.ToList());
|
||||
Mediator.Subscribe<RefreshUiMessage>(this, (msg) => _drawFolders = [.. DrawFolders]);
|
||||
|
||||
_characterAnalyzer = characterAnalyzer;
|
||||
_playerPerformanceConfig = playerPerformanceConfig;
|
||||
|
||||
@@ -65,12 +65,14 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
|
||||
IsOpen = true;
|
||||
|
||||
Mediator.Subscribe<DownloadStartedMessage>(this, (msg) =>
|
||||
Mediator.Subscribe<DownloadStartedMessage>(this, msg =>
|
||||
{
|
||||
_currentDownloads[msg.DownloadId] = msg.DownloadStatus;
|
||||
// Capture initial totals when download starts
|
||||
var totalFiles = msg.DownloadStatus.Values.Sum(s => s.TotalFiles);
|
||||
var totalBytes = msg.DownloadStatus.Values.Sum(s => s.TotalBytes);
|
||||
|
||||
var snap = msg.DownloadStatus.ToArray();
|
||||
var totalFiles = snap.Sum(kv => kv.Value?.TotalFiles ?? 0);
|
||||
var totalBytes = snap.Sum(kv => kv.Value?.TotalBytes ?? 0);
|
||||
|
||||
_downloadInitialTotals[msg.DownloadId] = (totalFiles, totalBytes);
|
||||
_notificationDismissed = false;
|
||||
});
|
||||
@@ -79,7 +81,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
_currentDownloads.TryRemove(msg.DownloadId, out _);
|
||||
|
||||
// Dismiss notification if all downloads are complete
|
||||
if (!_currentDownloads.Any() && !_notificationDismissed)
|
||||
if (_currentDownloads.IsEmpty && !_notificationDismissed)
|
||||
{
|
||||
Mediator.Publish(new LightlessNotificationDismissMessage("pair_download_progress"));
|
||||
_notificationDismissed = true;
|
||||
@@ -474,7 +476,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
totalBytes += playerTotalBytes;
|
||||
transferredBytes += playerTransferredBytes;
|
||||
|
||||
// per-player W/Q/P/D
|
||||
// per-player W/Q/P/D/C
|
||||
var playerDlSlot = 0;
|
||||
var playerDlQueue = 0;
|
||||
var playerDlProg = 0;
|
||||
@@ -487,6 +489,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
switch (fileStatus.DownloadStatus)
|
||||
{
|
||||
case DownloadStatus.Initializing:
|
||||
case DownloadStatus.WaitingForQueue:
|
||||
playerDlQueue++;
|
||||
totalDlQueue++;
|
||||
break;
|
||||
@@ -494,10 +497,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
playerDlSlot++;
|
||||
totalDlSlot++;
|
||||
break;
|
||||
case DownloadStatus.WaitingForQueue:
|
||||
playerDlQueue++;
|
||||
totalDlQueue++;
|
||||
break;
|
||||
case DownloadStatus.Downloading:
|
||||
playerDlProg++;
|
||||
totalDlProg++;
|
||||
@@ -550,11 +549,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
|
||||
if (totalFiles == 0 || totalBytes == 0)
|
||||
return;
|
||||
|
||||
// max speed for per-player bar scale (clamped)
|
||||
double maxSpeed = perPlayer.Count > 0 ? perPlayer.Max(p => p.SpeedBytesPerSecond) : 0;
|
||||
if (maxSpeed <= 0)
|
||||
maxSpeed = 1;
|
||||
|
||||
var drawList = ImGui.GetBackgroundDrawList();
|
||||
var windowPos = ImGui.GetWindowPos();
|
||||
|
||||
|
||||
@@ -504,7 +504,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
&& decompressed.LongLength != expectedRawSize)
|
||||
{
|
||||
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale, skipDecimation);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
|
||||
// write to file without compacting during download
|
||||
await File.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale, skipDecimation);
|
||||
}, ct).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
|
||||
Reference in New Issue
Block a user