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 _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 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(this, _ => UpdateSuppressionState()); _mediator.Subscribe(this, _ => UpdateSuppressionState()); _mediator.Subscribe(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(); 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)); } }