Fix UI + Amélioration AutoDetect & Self Analyse + Update Penumbra API
This commit is contained in:
@@ -66,8 +66,8 @@ public class MareConfig : IMareConfiguration
|
|||||||
public bool DefaultDisableVfx { get; set; } = false;
|
public bool DefaultDisableVfx { get; set; } = false;
|
||||||
public Dictionary<string, SyncOverrideEntry> PairSyncOverrides { get; set; } = new(StringComparer.Ordinal);
|
public Dictionary<string, SyncOverrideEntry> PairSyncOverrides { get; set; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<string, SyncOverrideEntry> GroupSyncOverrides { get; set; } = new(StringComparer.Ordinal);
|
public Dictionary<string, SyncOverrideEntry> GroupSyncOverrides { get; set; } = new(StringComparer.Ordinal);
|
||||||
public bool EnableAutoDetectDiscovery { get; set; } = false;
|
public bool EnableAutoDetectDiscovery { get; set; } = true;
|
||||||
public bool AllowAutoDetectPairRequests { get; set; } = false;
|
public bool AllowAutoDetectPairRequests { get; set; } = true;
|
||||||
public int AutoDetectMaxDistanceMeters { get; set; } = 40;
|
public int AutoDetectMaxDistanceMeters { get; set; } = 40;
|
||||||
public int AutoDetectMuteMinutes { get; set; } = 5;
|
public int AutoDetectMuteMinutes { get; set; } = 5;
|
||||||
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectRequestService>();
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectRequestService>();
|
||||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>();
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>();
|
||||||
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.NearbyPendingService>();
|
||||||
|
collection.AddSingleton<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>();
|
||||||
collection.AddSingleton<MarePlugin>();
|
collection.AddSingleton<MarePlugin>();
|
||||||
collection.AddSingleton<MareProfileManager>();
|
collection.AddSingleton<MareProfileManager>();
|
||||||
collection.AddSingleton<GameObjectHandlerFactory>();
|
collection.AddSingleton<GameObjectHandlerFactory>();
|
||||||
@@ -149,6 +150,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<TemporarySyncshellNotificationService>();
|
collection.AddSingleton<TemporarySyncshellNotificationService>();
|
||||||
collection.AddSingleton<PartyListTypingService>();
|
collection.AddSingleton<PartyListTypingService>();
|
||||||
collection.AddSingleton<TypingIndicatorStateService>();
|
collection.AddSingleton<TypingIndicatorStateService>();
|
||||||
|
collection.AddSingleton<ChatTwoCompatibilityService>();
|
||||||
|
|
||||||
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||||
@@ -220,6 +222,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||||
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>());
|
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.NearbyDiscoveryService>());
|
||||||
|
collection.AddHostedService(p => p.GetRequiredService<ChatTwoCompatibilityService>());
|
||||||
|
collection.AddHostedService(p => p.GetRequiredService<MareSynchronos.Services.AutoDetect.AutoDetectSuppressionService>());
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MareSynchronos.WebAPI.AutoDetect;
|
using MareSynchronos.WebAPI.AutoDetect;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
@@ -20,6 +23,7 @@ public class AutoDetectRequestService
|
|||||||
private readonly object _syncRoot = new();
|
private readonly object _syncRoot = new();
|
||||||
private readonly Dictionary<string, DateTime> _activeCooldowns = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, DateTime> _activeCooldowns = new(StringComparer.Ordinal);
|
||||||
private readonly Dictionary<string, RefusalTracker> _refusalTrackers = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, RefusalTracker> _refusalTrackers = new(StringComparer.Ordinal);
|
||||||
|
private readonly ConcurrentDictionary<string, PendingRequestInfo> _pendingRequests = new(StringComparer.Ordinal);
|
||||||
private static readonly TimeSpan RequestCooldown = TimeSpan.FromMinutes(5);
|
private static readonly TimeSpan RequestCooldown = TimeSpan.FromMinutes(5);
|
||||||
private static readonly TimeSpan RefusalLockDuration = TimeSpan.FromMinutes(15);
|
private static readonly TimeSpan RefusalLockDuration = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
@@ -118,6 +122,11 @@ public class AutoDetectRequestService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
|
_mediator.Publish(new NotificationMessage("Nearby request sent", "The other user will receive a request notification.", NotificationType.Info));
|
||||||
|
var pendingKey = EnsureTargetKey(targetKey);
|
||||||
|
var label = !string.IsNullOrWhiteSpace(targetDisplayName)
|
||||||
|
? targetDisplayName!
|
||||||
|
: (!string.IsNullOrEmpty(uid) ? uid : (!string.IsNullOrEmpty(token) ? token : pendingKey));
|
||||||
|
_pendingRequests[pendingKey] = new PendingRequestInfo(pendingKey, uid, token, label, DateTime.UtcNow);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -145,6 +154,7 @@ public class AutoDetectRequestService
|
|||||||
tracker.LockUntil = now.Add(RefusalLockDuration);
|
tracker.LockUntil = now.Add(RefusalLockDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_pendingRequests.TryRemove(targetKey, out _);
|
||||||
}
|
}
|
||||||
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
|
_mediator.Publish(new NotificationMessage("Nearby request failed", "The server rejected the request. Try again soon.", NotificationType.Warning));
|
||||||
}
|
}
|
||||||
@@ -207,4 +217,35 @@ public class AutoDetectRequestService
|
|||||||
public int Count;
|
public int Count;
|
||||||
public DateTime? LockUntil;
|
public DateTime? LockUntil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<PendingRequestInfo> GetPendingRequestsSnapshot()
|
||||||
|
{
|
||||||
|
return _pendingRequests.Values.OrderByDescending(v => v.SentAt).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePendingRequestByUid(string uid)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(uid)) return;
|
||||||
|
foreach (var kvp in _pendingRequests)
|
||||||
|
{
|
||||||
|
if (string.Equals(kvp.Value.Uid, uid, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_pendingRequests.TryRemove(kvp.Key, out _);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePendingRequestByKey(string key)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(key)) return;
|
||||||
|
_pendingRequests.TryRemove(key, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EnsureTargetKey(string? targetKey)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(targetKey) ? targetKey! : "target:" + Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record PendingRequestInfo(string Key, string? Uid, string? Token, string TargetDisplayName, DateTime SentAt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
|
using MareSynchronos.Services;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
|
public sealed class AutoDetectSuppressionService : IHostedService, IMediatorSubscriber
|
||||||
|
{
|
||||||
|
private static readonly string[] ContentTypeKeywords =
|
||||||
|
[
|
||||||
|
"dungeon",
|
||||||
|
"donjon",
|
||||||
|
"raid",
|
||||||
|
"trial",
|
||||||
|
"défi",
|
||||||
|
"front",
|
||||||
|
"frontline",
|
||||||
|
"pvp",
|
||||||
|
"jcj",
|
||||||
|
"conflict",
|
||||||
|
"conflit"
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly ILogger<AutoDetectSuppressionService> _logger;
|
||||||
|
private readonly MareConfigService _configService;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
|
private readonly MareMediator _mediator;
|
||||||
|
private readonly DalamudUtilService _dalamudUtilService;
|
||||||
|
|
||||||
|
private bool _isSuppressed;
|
||||||
|
private bool _hasSavedState;
|
||||||
|
private bool _savedDiscoveryEnabled;
|
||||||
|
private bool _savedAllowRequests;
|
||||||
|
private bool _suppressionWarningShown;
|
||||||
|
public bool IsSuppressed => _isSuppressed;
|
||||||
|
|
||||||
|
public AutoDetectSuppressionService(ILogger<AutoDetectSuppressionService> logger,
|
||||||
|
MareConfigService configService, IClientState clientState,
|
||||||
|
IDataManager dataManager, DalamudUtilService dalamudUtilService, MareMediator mediator)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configService = configService;
|
||||||
|
_clientState = clientState;
|
||||||
|
_dataManager = dataManager;
|
||||||
|
_dalamudUtilService = dalamudUtilService;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MareMediator Mediator => _mediator;
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_mediator.Subscribe<ZoneSwitchEndMessage>(this, _ => UpdateSuppressionState());
|
||||||
|
_mediator.Subscribe<DalamudLoginMessage>(this, _ => UpdateSuppressionState());
|
||||||
|
_mediator.Subscribe<DalamudLogoutMessage>(this, _ => ClearSuppression());
|
||||||
|
UpdateSuppressionState();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_mediator.UnsubscribeAll(this);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSuppressionState()
|
||||||
|
{
|
||||||
|
_ = _dalamudUtilService.RunOnFrameworkThread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_clientState.IsLoggedIn || _clientState.LocalPlayer == null)
|
||||||
|
{
|
||||||
|
ClearSuppression();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint territoryId = _clientState.TerritoryType;
|
||||||
|
bool shouldSuppress = ShouldSuppressForTerritory(territoryId);
|
||||||
|
ApplySuppression(shouldSuppress, territoryId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to update AutoDetect suppression state");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplySuppression(bool shouldSuppress, uint territoryId)
|
||||||
|
{
|
||||||
|
if (shouldSuppress)
|
||||||
|
{
|
||||||
|
if (!_isSuppressed)
|
||||||
|
{
|
||||||
|
_savedDiscoveryEnabled = _configService.Current.EnableAutoDetectDiscovery;
|
||||||
|
_savedAllowRequests = _configService.Current.AllowAutoDetectPairRequests;
|
||||||
|
_hasSavedState = true;
|
||||||
|
_isSuppressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
if (_configService.Current.EnableAutoDetectDiscovery)
|
||||||
|
{
|
||||||
|
_configService.Current.EnableAutoDetectDiscovery = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (_configService.Current.AllowAutoDetectPairRequests)
|
||||||
|
{
|
||||||
|
_configService.Current.AllowAutoDetectPairRequests = false;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("AutoDetect temporarily disabled in instanced content (territory {territoryId}).", territoryId);
|
||||||
|
if (!_suppressionWarningShown)
|
||||||
|
{
|
||||||
|
_suppressionWarningShown = true;
|
||||||
|
const string warningText = "Zone instanciée détectée : les fonctions AutoDetect/Nearby sont coupées pour économiser de la bande passante.";
|
||||||
|
_mediator.Publish(new DualNotificationMessage("AutoDetect désactivé",
|
||||||
|
warningText,
|
||||||
|
NotificationType.Warning, TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!_isSuppressed) return;
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
|
bool wasSuppressed = _suppressionWarningShown;
|
||||||
|
if (_hasSavedState)
|
||||||
|
{
|
||||||
|
if (_configService.Current.EnableAutoDetectDiscovery != _savedDiscoveryEnabled)
|
||||||
|
{
|
||||||
|
_configService.Current.EnableAutoDetectDiscovery = _savedDiscoveryEnabled;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (_configService.Current.AllowAutoDetectPairRequests != _savedAllowRequests)
|
||||||
|
{
|
||||||
|
_configService.Current.AllowAutoDetectPairRequests = _savedAllowRequests;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_isSuppressed = false;
|
||||||
|
_hasSavedState = false;
|
||||||
|
_suppressionWarningShown = false;
|
||||||
|
|
||||||
|
if (changed || wasSuppressed)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("AutoDetect restored after leaving instanced content (territory {territoryId}).", territoryId);
|
||||||
|
const string restoredText = "Vous avez quitté la zone instanciée : AutoDetect/Nearby fonctionnent de nouveau.";
|
||||||
|
_mediator.Publish(new DualNotificationMessage("AutoDetect réactivé",
|
||||||
|
restoredText,
|
||||||
|
NotificationType.Info, TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearSuppression()
|
||||||
|
{
|
||||||
|
if (!_isSuppressed) return;
|
||||||
|
_isSuppressed = false;
|
||||||
|
if (_hasSavedState)
|
||||||
|
{
|
||||||
|
_configService.Current.EnableAutoDetectDiscovery = _savedDiscoveryEnabled;
|
||||||
|
_configService.Current.AllowAutoDetectPairRequests = _savedAllowRequests;
|
||||||
|
}
|
||||||
|
_hasSavedState = false;
|
||||||
|
_suppressionWarningShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldSuppressForTerritory(uint territoryId)
|
||||||
|
{
|
||||||
|
if (territoryId == 0) return false;
|
||||||
|
|
||||||
|
var cfcSheet = _dataManager.GetExcelSheet<ContentFinderCondition>();
|
||||||
|
if (cfcSheet == null) return false;
|
||||||
|
|
||||||
|
var cfc = cfcSheet.FirstOrDefault(c => c.TerritoryType.RowId == territoryId);
|
||||||
|
if (cfc.RowId == 0) return false;
|
||||||
|
|
||||||
|
if (MatchesSuppressionKeyword(cfc.Name.ToString())) return true;
|
||||||
|
|
||||||
|
var contentType = cfc.ContentType.Value;
|
||||||
|
if (contentType.RowId == 0) return false;
|
||||||
|
|
||||||
|
return MatchesSuppressionKeyword(contentType.Name.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesSuppressionKeyword(string? text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(text)) return false;
|
||||||
|
return ContentTypeKeywords.Any(keyword => text.Contains(keyword, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
_ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
|
_ = _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uidA)));
|
||||||
_pending.TryRemove(uidA, out _);
|
_pending.TryRemove(uidA, out _);
|
||||||
|
_requestService.RemovePendingRequestByUid(uidA);
|
||||||
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
|
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -67,6 +68,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
|||||||
public void Remove(string uid)
|
public void Remove(string uid)
|
||||||
{
|
{
|
||||||
_pending.TryRemove(uid, out _);
|
_pending.TryRemove(uid, out _);
|
||||||
|
_requestService.RemovePendingRequestByUid(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> AcceptAsync(string uid)
|
public async Task<bool> AcceptAsync(string uid)
|
||||||
@@ -75,6 +77,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
|
await _api.UserAddPair(new MareSynchronos.API.Dto.User.UserDto(new MareSynchronos.API.Data.UserData(uid))).ConfigureAwait(false);
|
||||||
_pending.TryRemove(uid, out _);
|
_pending.TryRemove(uid, out _);
|
||||||
|
_requestService.RemovePendingRequestByUid(uid);
|
||||||
_ = _requestService.SendAcceptNotifyAsync(uid);
|
_ = _requestService.SendAcceptNotifyAsync(uid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using Lumina.Data.Files;
|
using System;
|
||||||
|
using Lumina.Data.Files;
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.UI;
|
using MareSynchronos.UI;
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
@@ -16,6 +18,16 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
|||||||
private CancellationTokenSource? _analysisCts;
|
private CancellationTokenSource? _analysisCts;
|
||||||
private CancellationTokenSource _baseAnalysisCts = new();
|
private CancellationTokenSource _baseAnalysisCts = new();
|
||||||
private string _lastDataHash = string.Empty;
|
private string _lastDataHash = string.Empty;
|
||||||
|
private CharacterAnalysisSummary _previousSummary = CharacterAnalysisSummary.Empty;
|
||||||
|
private DateTime _lastAutoAnalysis = DateTime.MinValue;
|
||||||
|
private string _lastAutoAnalysisHash = string.Empty;
|
||||||
|
private const int AutoAnalysisFileDeltaThreshold = 25;
|
||||||
|
private const long AutoAnalysisSizeDeltaThreshold = 50L * 1024 * 1024;
|
||||||
|
private static readonly TimeSpan AutoAnalysisCooldown = TimeSpan.FromMinutes(2);
|
||||||
|
private const long NotificationSizeThreshold = 300L * 1024 * 1024;
|
||||||
|
private const long NotificationTriangleThreshold = 150_000;
|
||||||
|
private bool _sizeWarningShown;
|
||||||
|
private bool _triangleWarningShown;
|
||||||
|
|
||||||
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
|
public CharacterAnalyzer(ILogger<CharacterAnalyzer> logger, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
|
||||||
: base(logger, mediator)
|
: base(logger, mediator)
|
||||||
@@ -33,6 +45,7 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
|||||||
public int CurrentFile { get; internal set; }
|
public int CurrentFile { get; internal set; }
|
||||||
public bool IsAnalysisRunning => _analysisCts != null;
|
public bool IsAnalysisRunning => _analysisCts != null;
|
||||||
public int TotalFiles { get; internal set; }
|
public int TotalFiles { get; internal set; }
|
||||||
|
public CharacterAnalysisSummary CurrentSummary { get; private set; } = CharacterAnalysisSummary.Empty;
|
||||||
internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = [];
|
internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = [];
|
||||||
|
|
||||||
public void CancelAnalyze()
|
public void CancelAnalyze()
|
||||||
@@ -80,6 +93,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefreshSummary(false, _lastDataHash);
|
||||||
|
|
||||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||||
|
|
||||||
_analysisCts.CancelDispose();
|
_analysisCts.CancelDispose();
|
||||||
@@ -142,9 +157,11 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
|||||||
LastAnalysis[obj.Key] = data;
|
LastAnalysis[obj.Key] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lastDataHash = charaData.DataHash.Value;
|
||||||
|
RefreshSummary(true, _lastDataHash);
|
||||||
|
|
||||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||||
|
|
||||||
_lastDataHash = charaData.DataHash.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintAnalysis()
|
private void PrintAnalysis()
|
||||||
@@ -193,6 +210,162 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
|||||||
Logger.LogInformation("IMPORTANT NOTES:\n\r- For uploads and downloads only the compressed size is relevant.\n\r- An unusually high total files count beyond 200 and up will also increase your download time to others significantly.");
|
Logger.LogInformation("IMPORTANT NOTES:\n\r- For uploads 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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshSummary(bool evaluateAutoAnalysis, string dataHash)
|
||||||
|
{
|
||||||
|
var summary = CalculateSummary();
|
||||||
|
CurrentSummary = summary;
|
||||||
|
|
||||||
|
if (evaluateAutoAnalysis)
|
||||||
|
{
|
||||||
|
EvaluateAutoAnalysis(summary, dataHash);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_previousSummary = summary;
|
||||||
|
|
||||||
|
if (!summary.HasUncomputedEntries && string.Equals(_lastAutoAnalysisHash, dataHash, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_lastAutoAnalysisHash = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EvaluateThresholdNotifications(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharacterAnalysisSummary CalculateSummary()
|
||||||
|
{
|
||||||
|
if (LastAnalysis.Count == 0)
|
||||||
|
{
|
||||||
|
return CharacterAnalysisSummary.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
long original = 0;
|
||||||
|
long compressed = 0;
|
||||||
|
long triangles = 0;
|
||||||
|
int files = 0;
|
||||||
|
bool hasUncomputed = false;
|
||||||
|
|
||||||
|
foreach (var obj in LastAnalysis.Values)
|
||||||
|
{
|
||||||
|
foreach (var entry in obj.Values)
|
||||||
|
{
|
||||||
|
files++;
|
||||||
|
original += entry.OriginalSize;
|
||||||
|
compressed += entry.CompressedSize;
|
||||||
|
triangles += entry.Triangles;
|
||||||
|
hasUncomputed |= !entry.IsComputed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CharacterAnalysisSummary(files, original, compressed, triangles, hasUncomputed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EvaluateAutoAnalysis(CharacterAnalysisSummary newSummary, string dataHash)
|
||||||
|
{
|
||||||
|
var previous = _previousSummary;
|
||||||
|
_previousSummary = newSummary;
|
||||||
|
|
||||||
|
if (newSummary.TotalFiles == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(_lastAutoAnalysisHash, dataHash, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAnalysisRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
if (now - _lastAutoAnalysis < AutoAnalysisCooldown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool firstSummary = previous.TotalFiles == 0;
|
||||||
|
bool filesIncreased = newSummary.TotalFiles - previous.TotalFiles >= AutoAnalysisFileDeltaThreshold;
|
||||||
|
bool sizeIncreased = newSummary.TotalCompressedSize - previous.TotalCompressedSize >= AutoAnalysisSizeDeltaThreshold;
|
||||||
|
bool needsCompute = newSummary.HasUncomputedEntries;
|
||||||
|
|
||||||
|
if (!firstSummary && !filesIncreased && !sizeIncreased && !needsCompute)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastAutoAnalysis = now;
|
||||||
|
_lastAutoAnalysisHash = dataHash;
|
||||||
|
_ = ComputeAnalysis(print: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EvaluateThresholdNotifications(CharacterAnalysisSummary summary)
|
||||||
|
{
|
||||||
|
if (summary.IsEmpty || summary.HasUncomputedEntries)
|
||||||
|
{
|
||||||
|
ResetThresholdFlagsIfNeeded(summary);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sizeExceeded = summary.TotalCompressedSize >= NotificationSizeThreshold;
|
||||||
|
bool trianglesExceeded = summary.TotalTriangles >= NotificationTriangleThreshold;
|
||||||
|
List<string> exceededReasons = new();
|
||||||
|
|
||||||
|
if (sizeExceeded && !_sizeWarningShown)
|
||||||
|
{
|
||||||
|
exceededReasons.Add($"un poids partagé de {UiSharedService.ByteToString(summary.TotalCompressedSize)} (≥ 300 MiB)");
|
||||||
|
_sizeWarningShown = true;
|
||||||
|
}
|
||||||
|
else if (!sizeExceeded && _sizeWarningShown)
|
||||||
|
{
|
||||||
|
_sizeWarningShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trianglesExceeded && !_triangleWarningShown)
|
||||||
|
{
|
||||||
|
exceededReasons.Add($"un total de {UiSharedService.TrisToString(summary.TotalTriangles)} triangles (≥ 150k)");
|
||||||
|
_triangleWarningShown = true;
|
||||||
|
}
|
||||||
|
else if (!trianglesExceeded && _triangleWarningShown)
|
||||||
|
{
|
||||||
|
_triangleWarningShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exceededReasons.Count == 0) return;
|
||||||
|
|
||||||
|
string combined = string.Join(" et ", exceededReasons);
|
||||||
|
string message = $"Attention : votre self-analysis indique {combined}. Des joueurs risquent de ne pas vous voir et UmbraSync peut activer un auto-pause. Pensez à réduire textures ou modèles lourds.";
|
||||||
|
Mediator.Publish(new DualNotificationMessage("Self Analysis", message, NotificationType.Warning));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetThresholdFlagsIfNeeded(CharacterAnalysisSummary summary)
|
||||||
|
{
|
||||||
|
if (summary.IsEmpty)
|
||||||
|
{
|
||||||
|
_sizeWarningShown = false;
|
||||||
|
_triangleWarningShown = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.TotalCompressedSize < NotificationSizeThreshold)
|
||||||
|
{
|
||||||
|
_sizeWarningShown = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.TotalTriangles < NotificationTriangleThreshold)
|
||||||
|
{
|
||||||
|
_triangleWarningShown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct CharacterAnalysisSummary(int TotalFiles, long TotalOriginalSize, long TotalCompressedSize, long TotalTriangles, bool HasUncomputedEntries)
|
||||||
|
{
|
||||||
|
public static CharacterAnalysisSummary Empty => new();
|
||||||
|
public bool IsEmpty => TotalFiles == 0 && TotalOriginalSize == 0 && TotalCompressedSize == 0 && TotalTriangles == 0;
|
||||||
|
}
|
||||||
|
|
||||||
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize, long Triangles)
|
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 bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
||||||
|
|||||||
68
MareSynchronos/Services/ChatTwoCompatibilityService.cs
Normal file
68
MareSynchronos/Services/ChatTwoCompatibilityService.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
|
using MareSynchronos.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace MareSynchronos.Services;
|
||||||
|
|
||||||
|
public sealed class ChatTwoCompatibilityService : MediatorSubscriberBase, IHostedService
|
||||||
|
{
|
||||||
|
private const string ChatTwoInternalName = "ChatTwo";
|
||||||
|
private readonly IDalamudPluginInterface _pluginInterface;
|
||||||
|
private bool _warningShown;
|
||||||
|
|
||||||
|
public ChatTwoCompatibilityService(ILogger<ChatTwoCompatibilityService> logger, IDalamudPluginInterface pluginInterface, MareMediator mediator)
|
||||||
|
: base(logger, mediator)
|
||||||
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
|
||||||
|
Mediator.SubscribeKeyed<PluginChangeMessage>(this, ChatTwoInternalName, OnChatTwoStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var initialState = PluginWatcherService.GetInitialPluginState(_pluginInterface, ChatTwoInternalName);
|
||||||
|
if (initialState?.IsLoaded == true)
|
||||||
|
{
|
||||||
|
ShowWarning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogDebug(ex, "Failed to inspect ChatTwo initial state");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Mediator.UnsubscribeAll(this);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnChatTwoStateChanged(PluginChangeMessage message)
|
||||||
|
{
|
||||||
|
if (message.IsLoaded)
|
||||||
|
{
|
||||||
|
ShowWarning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowWarning()
|
||||||
|
{
|
||||||
|
if (_warningShown) return;
|
||||||
|
_warningShown = true;
|
||||||
|
|
||||||
|
const string warningTitle = "ChatTwo détecté";
|
||||||
|
const string warningBody = "Actuellement, le plugin ChatTwo n'est pas compatible avec la bulle d'écriture d'UmbraSync. Désactivez ChatTwo si vous souhaitez conserver l'indicateur de saisie.";
|
||||||
|
|
||||||
|
Mediator.Publish(new NotificationMessage(warningTitle, warningBody, NotificationType.Warning, TimeSpan.FromSeconds(10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,14 @@ using MareSynchronos.UI;
|
|||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
namespace MareSynchronos.Services;
|
namespace MareSynchronos.Services;
|
||||||
|
|
||||||
public sealed class CommandManagerService : IDisposable
|
public sealed class CommandManagerService : IDisposable
|
||||||
{
|
{
|
||||||
private const string _commandName = "/usync";
|
private const string _commandName = "/usync";
|
||||||
|
private const string _autoDetectCommand = "/autodetect";
|
||||||
private const string _ssCommandPrefix = "/ums";
|
private const string _ssCommandPrefix = "/ums";
|
||||||
|
|
||||||
private readonly ApiController _apiController;
|
private readonly ApiController _apiController;
|
||||||
@@ -43,6 +45,11 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
HelpMessage = "Opens the UmbraSync UI"
|
HelpMessage = "Opens the UmbraSync UI"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_commandManager.AddHandler(_autoDetectCommand, new CommandInfo(OnAutoDetectCommand)
|
||||||
|
{
|
||||||
|
HelpMessage = "Opens the AutoDetect window"
|
||||||
|
});
|
||||||
|
|
||||||
// Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway
|
// Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway
|
||||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
||||||
{
|
{
|
||||||
@@ -56,12 +63,21 @@ public sealed class CommandManagerService : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_commandManager.RemoveHandler(_commandName);
|
_commandManager.RemoveHandler(_commandName);
|
||||||
|
_commandManager.RemoveHandler(_autoDetectCommand);
|
||||||
|
|
||||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
||||||
_commandManager.RemoveHandler($"{_ssCommandPrefix}{i}");
|
_commandManager.RemoveHandler($"{_ssCommandPrefix}{i}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAutoDetectCommand(string command, string args)
|
||||||
|
{
|
||||||
|
UiSharedService.AccentColor = new Vector4(0x8D / 255f, 0x37 / 255f, 0xC0 / 255f, 1f);
|
||||||
|
UiSharedService.AccentHoverColor = new Vector4(0x3A / 255f, 0x15 / 255f, 0x50 / 255f, 1f);
|
||||||
|
UiSharedService.AccentActiveColor = UiSharedService.AccentHoverColor;
|
||||||
|
_mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void OnCommand(string command, string args)
|
private void OnCommand(string command, string args)
|
||||||
{
|
{
|
||||||
var splitArgs = args.ToLowerInvariant().Trim().Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
var splitArgs = args.ToLowerInvariant().Trim().Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
|
|
||||||
bool appendInstruction;
|
bool appendInstruction;
|
||||||
bool forceChat = ShouldForceChat(msg, out appendInstruction);
|
bool forceChat = ShouldForceChat(msg, out appendInstruction);
|
||||||
var effectiveMessage = forceChat && appendInstruction ? AppendUsyncInstruction(msg.Message) : msg.Message;
|
var effectiveMessage = forceChat && appendInstruction ? AppendAutoDetectInstruction(msg.Message) : msg.Message;
|
||||||
var adjustedMsg = forceChat && appendInstruction ? msg with { Message = effectiveMessage } : msg;
|
var adjustedMsg = forceChat && appendInstruction ? msg with { Message = effectiveMessage } : msg;
|
||||||
|
|
||||||
switch (adjustedMsg.Type)
|
switch (adjustedMsg.Type)
|
||||||
@@ -155,13 +155,13 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string AppendUsyncInstruction(string? message)
|
private static string AppendAutoDetectInstruction(string? message)
|
||||||
{
|
{
|
||||||
const string suffix = " | Ouvrez /usync pour voir l'invitation.";
|
const string suffix = " | Ouvrez /autodetect pour gérer l'invitation.";
|
||||||
if (string.IsNullOrWhiteSpace(message))
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
return suffix.TrimStart(' ', '|');
|
return suffix.TrimStart(' ', '|');
|
||||||
|
|
||||||
if (message.Contains("/usync", StringComparison.OrdinalIgnoreCase))
|
if (message.Contains("/autodetect", StringComparison.OrdinalIgnoreCase))
|
||||||
return message;
|
return message;
|
||||||
|
|
||||||
return message.TrimEnd() + suffix;
|
return message.TrimEnd() + suffix;
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
@@ -5,13 +9,15 @@ using Dalamud.Interface.Utility;
|
|||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.Services;
|
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using MareSynchronos.Services;
|
||||||
|
using MareSynchronos.Services.AutoDetect;
|
||||||
|
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
|
||||||
|
|
||||||
namespace MareSynchronos.UI;
|
namespace MareSynchronos.UI;
|
||||||
|
|
||||||
@@ -20,13 +26,15 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
|||||||
private readonly MareConfigService _configService;
|
private readonly MareConfigService _configService;
|
||||||
private readonly DalamudUtilService _dalamud;
|
private readonly DalamudUtilService _dalamud;
|
||||||
private readonly IObjectTable _objectTable;
|
private readonly IObjectTable _objectTable;
|
||||||
private readonly Services.AutoDetect.AutoDetectRequestService _requestService;
|
private readonly AutoDetectRequestService _requestService;
|
||||||
|
private readonly NearbyPendingService _pendingService;
|
||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private List<Services.Mediator.NearbyEntry> _entries = new();
|
private List<Services.Mediator.NearbyEntry> _entries = new();
|
||||||
|
private readonly HashSet<string> _acceptInFlight = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
|
public AutoDetectUi(ILogger<AutoDetectUi> logger, MareMediator mediator,
|
||||||
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
|
MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable,
|
||||||
Services.AutoDetect.AutoDetectRequestService requestService, PairManager pairManager,
|
AutoDetectRequestService requestService, NearbyPendingService pendingService, PairManager pairManager,
|
||||||
PerformanceCollectorService performanceCollectorService)
|
PerformanceCollectorService performanceCollectorService)
|
||||||
: base(logger, mediator, "AutoDetect", performanceCollectorService)
|
: base(logger, mediator, "AutoDetect", performanceCollectorService)
|
||||||
{
|
{
|
||||||
@@ -34,6 +42,7 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
|||||||
_dalamud = dalamudUtilService;
|
_dalamud = dalamudUtilService;
|
||||||
_objectTable = objectTable;
|
_objectTable = objectTable;
|
||||||
_requestService = requestService;
|
_requestService = requestService;
|
||||||
|
_pendingService = pendingService;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
|
|
||||||
Flags |= ImGuiWindowFlags.NoScrollbar;
|
Flags |= ImGuiWindowFlags.NoScrollbar;
|
||||||
@@ -53,9 +62,141 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
using var idScope = ImRaii.PushId("autodetect-ui");
|
using var idScope = ImRaii.PushId("autodetect-ui");
|
||||||
|
|
||||||
|
var incomingInvites = _pendingService.Pending.ToList();
|
||||||
|
var outgoingInvites = _requestService.GetPendingRequestsSnapshot();
|
||||||
|
|
||||||
|
Vector4 accent = UiSharedService.AccentColor;
|
||||||
|
if (accent.W <= 0f) accent = ImGuiColors.ParsedPurple;
|
||||||
|
Vector4 inactiveTab = new(accent.X * 0.45f, accent.Y * 0.45f, accent.Z * 0.45f, Math.Clamp(accent.W + 0.15f, 0f, 1f));
|
||||||
|
Vector4 hoverTab = UiSharedService.AccentHoverColor;
|
||||||
|
|
||||||
|
using var tabs = ImRaii.TabBar("AutoDetectTabs");
|
||||||
|
if (!tabs.Success) return;
|
||||||
|
|
||||||
|
var incomingCount = incomingInvites.Count;
|
||||||
|
DrawStyledTab($"Invitations ({incomingCount})", accent, inactiveTab, hoverTab, () =>
|
||||||
|
{
|
||||||
|
DrawInvitationsTab(incomingInvites, outgoingInvites);
|
||||||
|
});
|
||||||
|
|
||||||
|
DrawStyledTab("Proximité", accent, inactiveTab, hoverTab, DrawNearbyTab);
|
||||||
|
|
||||||
|
using (ImRaii.Disabled(true))
|
||||||
|
{
|
||||||
|
DrawStyledTab("Syncshell", accent, inactiveTab, hoverTab, () =>
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("Disponible prochainement.", ImGuiColors.DalamudGrey3);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawStyledTab(string label, Vector4 accent, Vector4 inactive, Vector4 hover, Action draw, bool disabled = false)
|
||||||
|
{
|
||||||
|
var tabColor = disabled ? ImGuiColors.DalamudGrey3 : inactive;
|
||||||
|
var tabHover = disabled ? ImGuiColors.DalamudGrey3 : hover;
|
||||||
|
var tabActive = disabled ? ImGuiColors.DalamudGrey2 : accent;
|
||||||
|
using var baseColor = ImRaii.PushColor(ImGuiCol.Tab, tabColor);
|
||||||
|
using var hoverColor = ImRaii.PushColor(ImGuiCol.TabHovered, tabHover);
|
||||||
|
using var activeColor = ImRaii.PushColor(ImGuiCol.TabActive, tabActive);
|
||||||
|
using var activeText = ImRaii.PushColor(ImGuiCol.Text, disabled ? ImGuiColors.DalamudGrey2 : Vector4.One, false);
|
||||||
|
using var tab = ImRaii.TabItem(label);
|
||||||
|
if (tab.Success)
|
||||||
|
{
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawInvitationsTab(List<KeyValuePair<string, string>> incomingInvites, IReadOnlyCollection<AutoDetectRequestService.PendingRequestInfo> outgoingInvites)
|
||||||
|
{
|
||||||
|
if (incomingInvites.Count == 0 && outgoingInvites.Count == 0)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("Aucune invitation en attente. Cette page regroupera les demandes reçues et celles que vous avez envoyées.", ImGuiColors.DalamudGrey3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingInvites.Count == 0)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("Vous n'avez aucune invitation de pair en attente pour le moment.", ImGuiColors.DalamudGrey3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiHelpers.ScaledDummy(4);
|
||||||
|
float leftWidth = Math.Max(220f * ImGuiHelpers.GlobalScale, ImGui.CalcTextSize("Invitations reçues (00)").X + ImGui.GetStyle().FramePadding.X * 4f);
|
||||||
|
var avail = ImGui.GetContentRegionAvail();
|
||||||
|
|
||||||
|
ImGui.BeginChild("incoming-requests", new Vector2(leftWidth, avail.Y), true);
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudOrange, $"Invitations reçues ({incomingInvites.Count})");
|
||||||
|
ImGui.Separator();
|
||||||
|
if (incomingInvites.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled("Aucune invitation reçue.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var (uid, name) in incomingInvites.OrderBy(k => k.Value, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(uid);
|
||||||
|
bool processing = _acceptInFlight.Contains(uid);
|
||||||
|
ImGui.TextUnformatted(name);
|
||||||
|
ImGui.TextDisabled(uid);
|
||||||
|
if (processing)
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled("Traitement en cours...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Accepter"))
|
||||||
|
{
|
||||||
|
TriggerAccept(uid);
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Refuser"))
|
||||||
|
{
|
||||||
|
_pendingService.Remove(uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.EndChild();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
ImGui.BeginChild("outgoing-requests", new Vector2(0, avail.Y), true);
|
||||||
|
ImGui.TextColored(ImGuiColors.DalamudOrange, $"Invitations envoyées ({outgoingInvites.Count})");
|
||||||
|
ImGui.Separator();
|
||||||
|
if (outgoingInvites.Count == 0)
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled("Aucune invitation envoyée en attente.");
|
||||||
|
ImGui.EndChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var info in outgoingInvites.OrderByDescending(i => i.SentAt))
|
||||||
|
{
|
||||||
|
using var id = ImRaii.PushId(info.Key);
|
||||||
|
ImGui.TextUnformatted(info.TargetDisplayName);
|
||||||
|
if (!string.IsNullOrEmpty(info.Uid))
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled(info.Uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TextDisabled($"Envoyée il y a {FormatDuration(DateTime.UtcNow - info.SentAt)}");
|
||||||
|
if (ImGui.Button("Retirer"))
|
||||||
|
{
|
||||||
|
_requestService.RemovePendingRequestByKey(info.Key);
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Retire uniquement cette entrée locale de suivi.");
|
||||||
|
ImGui.Separator();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNearbyTab()
|
||||||
|
{
|
||||||
if (!_configService.Current.EnableAutoDetectDiscovery)
|
if (!_configService.Current.EnableAutoDetectDiscovery)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Nearby detection is disabled. Enable it in Settings to start detecting nearby Umbra users.", ImGuiColors.DalamudYellow);
|
UiSharedService.ColorTextWrapped("AutoDetect est désactivé. Activez-le dans les paramètres pour détecter les utilisateurs Umbra à proximité.", ImGuiColors.DalamudYellow);
|
||||||
ImGuiHelpers.ScaledDummy(6);
|
ImGuiHelpers.ScaledDummy(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,26 +275,6 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
|||||||
_entries = msg.Entries;
|
_entries = msg.Entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Services.Mediator.NearbyEntry> BuildLocalSnapshot(int maxDist)
|
|
||||||
{
|
|
||||||
var list = new List<Services.Mediator.NearbyEntry>();
|
|
||||||
var local = _dalamud.GetPlayerCharacter();
|
|
||||||
var localPos = local?.Position ?? Vector3.Zero;
|
|
||||||
for (int i = 0; i < 200; i += 2)
|
|
||||||
{
|
|
||||||
var obj = _objectTable[i];
|
|
||||||
if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
|
|
||||||
if (local != null && obj.Address == local.Address) continue;
|
|
||||||
float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position);
|
|
||||||
if (!float.IsNaN(dist) && dist > maxDist) continue;
|
|
||||||
string name = obj.Name.ToString();
|
|
||||||
ushort worldId = 0;
|
|
||||||
if (obj is IPlayerCharacter pc) worldId = (ushort)pc.HomeWorld.RowId;
|
|
||||||
list.Add(new Services.Mediator.NearbyEntry(name, worldId, dist, false, null, null, null));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
|
private bool IsAlreadyPairedByUidOrAlias(Services.Mediator.NearbyEntry e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -194,4 +315,37 @@ public class AutoDetectUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TriggerAccept(string uid)
|
||||||
|
{
|
||||||
|
if (!_acceptInFlight.Add(uid)) return;
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool ok = await _pendingService.AcceptAsync(uid).ConfigureAwait(false);
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
Mediator.Publish(new NotificationMessage("AutoDetect", $"Impossible d'accepter l'invitation {uid}.", NotificationType.Warning, TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_acceptInFlight.Remove(uid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatDuration(TimeSpan span)
|
||||||
|
{
|
||||||
|
if (span.TotalMinutes >= 1)
|
||||||
|
{
|
||||||
|
var minutes = Math.Max(1, (int)Math.Round(span.TotalMinutes));
|
||||||
|
return minutes == 1 ? "1 minute" : $"{minutes} minutes";
|
||||||
|
}
|
||||||
|
|
||||||
|
var seconds = Math.Max(1, (int)Math.Round(span.TotalSeconds));
|
||||||
|
return seconds == 1 ? "1 seconde" : $"{seconds} secondes";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,16 @@ public sealed class ChangelogUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return new List<ChangelogEntry>
|
return new List<ChangelogEntry>
|
||||||
{
|
{
|
||||||
|
new(new Version(0, 1, 9, 4), "0.1.9.4", new List<ChangelogLine>
|
||||||
|
{
|
||||||
|
new("Réécriture complète de la bulle de frappe avec la possibilité de choisir la taille de la bulle."),
|
||||||
|
new("Désactivation de l'AutoDetect en zone instanciée."),
|
||||||
|
new("Réécriture interface AutoDetect pour acceuillir les invitations en attente et préparer les synchsells publiques."),
|
||||||
|
new("Amélioration de la compréhension des activations / désactivations des préférences de synchronisation par défaut."),
|
||||||
|
new("Mise en avant du Self Analyse avec une alerte lorsqu'un seuil de donnée a été atteint."),
|
||||||
|
new("Ajout de l'alerte de la non-compatibilité du plugin Chat2."),
|
||||||
|
new("Divers fix de l'interface."),
|
||||||
|
}),
|
||||||
new(new Version(0, 1, 9, 3), "0.1.9.3", new List<ChangelogLine>
|
new(new Version(0, 1, 9, 3), "0.1.9.3", new List<ChangelogLine>
|
||||||
{
|
{
|
||||||
new("Correctif de l'affichage de la bulle de frappe quand l'interface est à + de 100%."),
|
new("Correctif de l'affichage de la bulle de frappe quand l'interface est à + de 100%."),
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
private bool _showSyncShells;
|
private bool _showSyncShells;
|
||||||
private bool _wasOpen;
|
private bool _wasOpen;
|
||||||
private bool _nearbyOpen = true;
|
private bool _nearbyOpen = true;
|
||||||
private bool _pendingOpen = true;
|
|
||||||
private List<Services.Mediator.NearbyEntry> _nearbyEntries = new();
|
private List<Services.Mediator.NearbyEntry> _nearbyEntries = new();
|
||||||
|
|
||||||
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
|
public CompactUi(ILogger<CompactUi> logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService,
|
||||||
@@ -297,6 +296,10 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
bool animsDisabled = _configService.Current.DefaultDisableAnimations;
|
bool animsDisabled = _configService.Current.DefaultDisableAnimations;
|
||||||
bool vfxDisabled = _configService.Current.DefaultDisableVfx;
|
bool vfxDisabled = _configService.Current.DefaultDisableVfx;
|
||||||
bool showNearby = _configService.Current.EnableAutoDetectDiscovery;
|
bool showNearby = _configService.Current.EnableAutoDetectDiscovery;
|
||||||
|
int pendingInvites = _nearbyPending.Pending.Count;
|
||||||
|
|
||||||
|
const string nearbyLabel = "AutoDetect";
|
||||||
|
|
||||||
|
|
||||||
var soundIcon = soundsDisabled ? FontAwesomeIcon.VolumeMute : FontAwesomeIcon.VolumeUp;
|
var soundIcon = soundsDisabled ? FontAwesomeIcon.VolumeMute : FontAwesomeIcon.VolumeUp;
|
||||||
var animIcon = animsDisabled ? FontAwesomeIcon.WindowClose : FontAwesomeIcon.Running;
|
var animIcon = animsDisabled ? FontAwesomeIcon.WindowClose : FontAwesomeIcon.Running;
|
||||||
@@ -306,7 +309,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
float audioWidth = _uiSharedService.GetIconTextButtonSize(soundIcon, soundLabel);
|
float audioWidth = _uiSharedService.GetIconTextButtonSize(soundIcon, soundLabel);
|
||||||
float animWidth = _uiSharedService.GetIconTextButtonSize(animIcon, animLabel);
|
float animWidth = _uiSharedService.GetIconTextButtonSize(animIcon, animLabel);
|
||||||
float vfxWidth = _uiSharedService.GetIconTextButtonSize(vfxIcon, vfxLabel);
|
float vfxWidth = _uiSharedService.GetIconTextButtonSize(vfxIcon, vfxLabel);
|
||||||
float nearbyWidth = showNearby ? _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, "Nearby") : 0f;
|
float nearbyWidth = showNearby ? _uiSharedService.GetIconTextButtonSize(FontAwesomeIcon.UserPlus, pendingInvites > 0 ? $"{nearbyLabel} ({pendingInvites})" : nearbyLabel) : 0f;
|
||||||
int buttonCount = 3 + (showNearby ? 1 : 0);
|
int buttonCount = 3 + (showNearby ? 1 : 0);
|
||||||
float totalWidth = audioWidth + animWidth + vfxWidth + nearbyWidth + spacing * (buttonCount - 1);
|
float totalWidth = audioWidth + animWidth + vfxWidth + nearbyWidth + spacing * (buttonCount - 1);
|
||||||
float available = ImGui.GetContentRegionAvail().X;
|
float available = ImGui.GetContentRegionAvail().X;
|
||||||
@@ -346,11 +349,15 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
if (showNearby)
|
if (showNearby)
|
||||||
{
|
{
|
||||||
ImGui.SameLine(0, spacing);
|
ImGui.SameLine(0, spacing);
|
||||||
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", nearbyWidth))
|
var autodetectLabel = pendingInvites > 0 ? $"{nearbyLabel} ({pendingInvites})" : nearbyLabel;
|
||||||
|
if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, autodetectLabel, nearbyWidth))
|
||||||
{
|
{
|
||||||
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
|
Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi)));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Ouvrir la détection de proximité");
|
string tooltip = pendingInvites > 0
|
||||||
|
? string.Format("Vous avez {0} invitation{1} reçue. Ouvrez l\'interface AutoDetect pour y répondre.", pendingInvites, pendingInvites > 1 ? "s" : string.Empty)
|
||||||
|
: "Ouvrir les outils AutoDetect (invitations et proximité).\n\nLes demandes reçues sont listées dans l\'onglet 'Invitations'.";
|
||||||
|
UiSharedService.AttachToolTip(tooltip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
@@ -524,48 +531,11 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false);
|
ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// intentionally left blank; pending requests handled in collapsible section below
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
var pendingCount = _nearbyPending?.Pending.Count ?? 0;
|
var pendingCount = _nearbyPending?.Pending.Count ?? 0;
|
||||||
if (pendingCount > 0)
|
if (pendingCount > 0)
|
||||||
{
|
{
|
||||||
using (ImRaii.PushId("group-Pending"))
|
UiSharedService.ColorTextWrapped("Invitation AutoDetect en attente. Ouvrez l\'interface AutoDetect pour gérer vos demandes.", ImGuiColors.DalamudYellow);
|
||||||
{
|
ImGuiHelpers.ScaledDummy(4);
|
||||||
var icon = _pendingOpen ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
|
|
||||||
_uiSharedService.IconText(icon);
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _pendingOpen = !_pendingOpen;
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextUnformatted($"En attente ({pendingCount})");
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _pendingOpen = !_pendingOpen;
|
|
||||||
|
|
||||||
if (_pendingOpen)
|
|
||||||
{
|
|
||||||
ImGui.Indent();
|
|
||||||
foreach (var kv in _nearbyPending!.Pending)
|
|
||||||
{
|
|
||||||
ImGui.AlignTextToFramePadding();
|
|
||||||
ImGui.TextUnformatted($"{kv.Value} [{kv.Key}]");
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Check))
|
|
||||||
{
|
|
||||||
_ = _nearbyPending.AcceptAsync(kv.Key);
|
|
||||||
}
|
|
||||||
UiSharedService.AttachToolTip("Accept and add as pair");
|
|
||||||
ImGui.SameLine();
|
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Times))
|
|
||||||
{
|
|
||||||
_nearbyPending.Remove(kv.Key);
|
|
||||||
}
|
|
||||||
UiSharedService.AttachToolTip("Dismiss request");
|
|
||||||
}
|
|
||||||
ImGui.Unindent();
|
|
||||||
ImGui.Separator();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var onlineUsers = users.Where(u => u.UserPair!.OtherPermissions.IsPaired() && (u.IsOnline || u.UserPair!.OwnPermissions.IsPaused())).Select(c => new DrawUserPair("Online" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager)).ToList();
|
var onlineUsers = users.Where(u => u.UserPair!.OtherPermissions.IsPaired() && (u.IsOnline || u.UserPair!.OwnPermissions.IsPaused())).Select(c => new DrawUserPair("Online" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager)).ToList();
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using MareSynchronos.MareConfiguration.Models;
|
|||||||
using MareSynchronos.PlayerData.Handlers;
|
using MareSynchronos.PlayerData.Handlers;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
|
using MareSynchronos.Services.AutoDetect;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Services.ServerConfiguration;
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
@@ -44,6 +45,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private readonly PairManager _pairManager;
|
private readonly PairManager _pairManager;
|
||||||
private readonly ChatService _chatService;
|
private readonly ChatService _chatService;
|
||||||
private readonly GuiHookService _guiHookService;
|
private readonly GuiHookService _guiHookService;
|
||||||
|
private readonly AutoDetectSuppressionService _autoDetectSuppressionService;
|
||||||
private readonly PerformanceCollectorService _performanceCollector;
|
private readonly PerformanceCollectorService _performanceCollector;
|
||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||||
@@ -77,7 +79,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
FileCacheManager fileCacheManager,
|
FileCacheManager fileCacheManager,
|
||||||
FileCompactor fileCompactor, ApiController apiController,
|
FileCompactor fileCompactor, ApiController apiController,
|
||||||
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
|
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
|
||||||
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mediator, "Umbra Settings", performanceCollector)
|
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService,
|
||||||
|
AutoDetectSuppressionService autoDetectSuppressionService) : base(logger, mediator, "Umbra Settings", performanceCollector)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_pairManager = pairManager;
|
_pairManager = pairManager;
|
||||||
@@ -96,6 +99,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_cacheMonitor = cacheMonitor;
|
_cacheMonitor = cacheMonitor;
|
||||||
_dalamudUtilService = dalamudUtilService;
|
_dalamudUtilService = dalamudUtilService;
|
||||||
_registerService = registerService;
|
_registerService = registerService;
|
||||||
|
_autoDetectSuppressionService = autoDetectSuppressionService;
|
||||||
_fileCompactor = fileCompactor;
|
_fileCompactor = fileCompactor;
|
||||||
_uiShared = uiShared;
|
_uiShared = uiShared;
|
||||||
AllowClickthrough = false;
|
AllowClickthrough = false;
|
||||||
@@ -214,8 +218,11 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uiShared.BigText("AutoDetect");
|
_uiShared.BigText("AutoDetect");
|
||||||
|
bool isAutoDetectSuppressed = _autoDetectSuppressionService?.IsSuppressed ?? false;
|
||||||
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
|
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
|
||||||
if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery))
|
using (ImRaii.Disabled(isAutoDetectSuppressed))
|
||||||
|
{
|
||||||
|
if (ImGui.Checkbox("Enable AutoDetect", ref enableDiscovery))
|
||||||
{
|
{
|
||||||
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
@@ -231,9 +238,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
Mediator.Publish(new AllowPairRequestsToggled(false));
|
Mediator.Publish(new AllowPairRequestsToggled(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isAutoDetectSuppressed && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
{
|
||||||
|
UiSharedService.AttachToolTip("AutoDetect est temporairement désactivé dans cette zone instanciée.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allow Pair Requests is disabled when Nearby is OFF
|
// Allow Pair Requests is disabled when Nearby is OFF
|
||||||
using (ImRaii.Disabled(!enableDiscovery))
|
using (ImRaii.Disabled(isAutoDetectSuppressed || !enableDiscovery))
|
||||||
{
|
{
|
||||||
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
|
bool allowRequests = _configService.Current.AllowAutoDetectPairRequests;
|
||||||
if (ImGui.Checkbox("Allow pair requests", ref allowRequests))
|
if (ImGui.Checkbox("Allow pair requests", ref allowRequests))
|
||||||
@@ -246,15 +258,19 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
// user-facing info toast
|
// user-facing info toast
|
||||||
Mediator.Publish(new NotificationMessage(
|
Mediator.Publish(new NotificationMessage(
|
||||||
"Nearby Detection",
|
"AutoDetect",
|
||||||
allowRequests ? "Pair requests enabled: others can invite you." : "Pair requests disabled: others cannot invite you.",
|
allowRequests ? "Invitations entrantes autorisées : les autres peuvent vous inviter." : "Invitations entrantes désactivées : les autres ne peuvent pas vous inviter.",
|
||||||
NotificationType.Info,
|
NotificationType.Info,
|
||||||
default));
|
default));
|
||||||
}
|
}
|
||||||
|
if (isAutoDetectSuppressed && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||||
|
{
|
||||||
|
UiSharedService.AttachToolTip("AutoDetect est temporairement désactivé dans cette zone instanciée.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Radius only available when both Nearby and Allow Pair Requests are ON
|
// Radius only available when both Nearby and Allow Pair Requests are ON
|
||||||
if (enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
|
if (!isAutoDetectSuppressed && enableDiscovery && _configService.Current.AllowAutoDetectPairRequests)
|
||||||
{
|
{
|
||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
||||||
@@ -266,6 +282,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
ImGui.Unindent();
|
ImGui.Unindent();
|
||||||
}
|
}
|
||||||
|
else if (isAutoDetectSuppressed)
|
||||||
|
{
|
||||||
|
UiSharedService.ColorTextWrapped("AutoDetect est verrouillé tant que vous restez dans une zone instanciée.", ImGuiColors.DalamudYellow);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uiShared.BigText("Transfer UI");
|
_uiShared.BigText("Transfer UI");
|
||||||
|
|||||||
Submodule Penumbra.Api updated: 648b6fc2ce...97fe622e4e
Reference in New Issue
Block a user