Fix UI + Amélioration AutoDetect & Self Analyse + Update Penumbra API
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MareSynchronos.WebAPI.AutoDetect;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
@@ -20,6 +23,7 @@ public class AutoDetectRequestService
|
||||
private readonly object _syncRoot = new();
|
||||
private readonly Dictionary<string, DateTime> _activeCooldowns = 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 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));
|
||||
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
|
||||
{
|
||||
@@ -145,6 +154,7 @@ public class AutoDetectRequestService
|
||||
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));
|
||||
}
|
||||
@@ -207,4 +217,35 @@ public class AutoDetectRequestService
|
||||
public int Count;
|
||||
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)));
|
||||
_pending.TryRemove(uidA, out _);
|
||||
_requestService.RemovePendingRequestByUid(uidA);
|
||||
_logger.LogInformation("NearbyPending: auto-accepted pairing with {uid}", uidA);
|
||||
}
|
||||
return;
|
||||
@@ -67,6 +68,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
||||
public void Remove(string uid)
|
||||
{
|
||||
_pending.TryRemove(uid, out _);
|
||||
_requestService.RemovePendingRequestByUid(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);
|
||||
_pending.TryRemove(uid, out _);
|
||||
_requestService.RemovePendingRequestByUid(uid);
|
||||
_ = _requestService.SendAcceptNotifyAsync(uid);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Lumina.Data.Files;
|
||||
using System;
|
||||
using Lumina.Data.Files;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.UI;
|
||||
using MareSynchronos.Utils;
|
||||
@@ -16,6 +18,16 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
private CancellationTokenSource? _analysisCts;
|
||||
private CancellationTokenSource _baseAnalysisCts = new();
|
||||
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)
|
||||
: base(logger, mediator)
|
||||
@@ -33,6 +45,7 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
public int CurrentFile { get; internal set; }
|
||||
public bool IsAnalysisRunning => _analysisCts != null;
|
||||
public int TotalFiles { get; internal set; }
|
||||
public CharacterAnalysisSummary CurrentSummary { get; private set; } = CharacterAnalysisSummary.Empty;
|
||||
internal Dictionary<ObjectKind, Dictionary<string, FileDataEntry>> LastAnalysis { get; } = [];
|
||||
|
||||
public void CancelAnalyze()
|
||||
@@ -80,6 +93,8 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
RefreshSummary(false, _lastDataHash);
|
||||
|
||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||
|
||||
_analysisCts.CancelDispose();
|
||||
@@ -142,9 +157,11 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
LastAnalysis[obj.Key] = data;
|
||||
}
|
||||
|
||||
_lastDataHash = charaData.DataHash.Value;
|
||||
RefreshSummary(true, _lastDataHash);
|
||||
|
||||
Mediator.Publish(new CharacterDataAnalyzedMessage());
|
||||
|
||||
_lastDataHash = charaData.DataHash.Value;
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
|
||||
@@ -239,4 +412,4 @@ public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 System.Globalization;
|
||||
using System.Text;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public sealed class CommandManagerService : IDisposable
|
||||
{
|
||||
private const string _commandName = "/usync";
|
||||
private const string _autoDetectCommand = "/autodetect";
|
||||
private const string _ssCommandPrefix = "/ums";
|
||||
|
||||
private readonly ApiController _apiController;
|
||||
@@ -43,6 +45,11 @@ public sealed class CommandManagerService : IDisposable
|
||||
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
|
||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++i)
|
||||
{
|
||||
@@ -56,12 +63,21 @@ public sealed class CommandManagerService : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_commandManager.RemoveHandler(_commandName);
|
||||
|
||||
_commandManager.RemoveHandler(_autoDetectCommand);
|
||||
|
||||
for (int i = 1; i <= ChatService.CommandMaxNumber; ++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)
|
||||
{
|
||||
var splitArgs = args.ToLowerInvariant().Trim().Split(" ", StringSplitOptions.RemoveEmptyEntries);
|
||||
@@ -145,4 +161,4 @@ public sealed class CommandManagerService : IDisposable
|
||||
_chatService.SendChatShell(shellNumber, chatBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
||||
|
||||
bool 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;
|
||||
|
||||
switch (adjustedMsg.Type)
|
||||
@@ -155,13 +155,13 @@ public class NotificationService : DisposableMediatorSubscriberBase, IHostedServ
|
||||
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))
|
||||
return suffix.TrimStart(' ', '|');
|
||||
|
||||
if (message.Contains("/usync", StringComparison.OrdinalIgnoreCase))
|
||||
if (message.Contains("/autodetect", StringComparison.OrdinalIgnoreCase))
|
||||
return message;
|
||||
|
||||
return message.TrimEnd() + suffix;
|
||||
|
||||
Reference in New Issue
Block a user