Fix bubble party + Download queue + Allow pause user in syncshell + add visual feature + clean log info
This commit is contained in:
@@ -12,6 +12,7 @@ public class CharaDataConfig : IMareConfiguration
|
|||||||
public bool NearbyOwnServerOnly { get; set; } = false;
|
public bool NearbyOwnServerOnly { get; set; } = false;
|
||||||
public bool NearbyIgnoreHousingLimitations { get; set; } = false;
|
public bool NearbyIgnoreHousingLimitations { get; set; } = false;
|
||||||
public bool NearbyDrawWisps { get; set; } = true;
|
public bool NearbyDrawWisps { get; set; } = true;
|
||||||
|
public int NearbyMaxWisps { get; set; } = 20;
|
||||||
public int NearbyDistanceFilter { get; set; } = 100;
|
public int NearbyDistanceFilter { get; set; } = 100;
|
||||||
public bool NearbyShowOwnData { get; set; } = false;
|
public bool NearbyShowOwnData { get; set; } = false;
|
||||||
public bool ShowHelpTexts { get; set; } = true;
|
public bool ShowHelpTexts { get; set; } = true;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class MareConfig : IMareConfiguration
|
|||||||
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
||||||
public bool OpenPopupOnAdd { get; set; } = true;
|
public bool OpenPopupOnAdd { get; set; } = true;
|
||||||
public int ParallelDownloads { get; set; } = 10;
|
public int ParallelDownloads { get; set; } = 10;
|
||||||
|
public bool EnableDownloadQueue { get; set; } = false;
|
||||||
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
public int DownloadSpeedLimitInBytes { get; set; } = 0;
|
||||||
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
public DownloadSpeeds DownloadSpeedType { get; set; } = DownloadSpeeds.MBps;
|
||||||
[Obsolete] public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
[Obsolete] public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AssemblyName>UmbraSync</AssemblyName>
|
<AssemblyName>UmbraSync</AssemblyName>
|
||||||
<RootNamespace>UmbraSync</RootNamespace>
|
<RootNamespace>UmbraSync</RootNamespace>
|
||||||
<Version>0.1.9.4</Version>
|
<Version>0.1.9.5</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.WebAPI.Files;
|
using MareSynchronos.WebAPI.Files;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -10,21 +11,23 @@ public class FileDownloadManagerFactory
|
|||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
private readonly FileCompactor _fileCompactor;
|
private readonly FileCompactor _fileCompactor;
|
||||||
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
|
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
|
||||||
|
private readonly MareConfigService _mareConfigService;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private readonly MareMediator _mareMediator;
|
private readonly MareMediator _mareMediator;
|
||||||
|
|
||||||
public FileDownloadManagerFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, FileTransferOrchestrator fileTransferOrchestrator,
|
public FileDownloadManagerFactory(ILoggerFactory loggerFactory, MareMediator mareMediator, FileTransferOrchestrator fileTransferOrchestrator,
|
||||||
FileCacheManager fileCacheManager, FileCompactor fileCompactor)
|
FileCacheManager fileCacheManager, FileCompactor fileCompactor, MareConfigService mareConfigService)
|
||||||
{
|
{
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_mareMediator = mareMediator;
|
_mareMediator = mareMediator;
|
||||||
_fileTransferOrchestrator = fileTransferOrchestrator;
|
_fileTransferOrchestrator = fileTransferOrchestrator;
|
||||||
_fileCacheManager = fileCacheManager;
|
_fileCacheManager = fileCacheManager;
|
||||||
_fileCompactor = fileCompactor;
|
_fileCompactor = fileCompactor;
|
||||||
|
_mareConfigService = mareConfigService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileDownloadManager Create()
|
public FileDownloadManager Create()
|
||||||
{
|
{
|
||||||
return new FileDownloadManager(_loggerFactory.CreateLogger<FileDownloadManager>(), _mareMediator, _fileTransferOrchestrator, _fileCacheManager, _fileCompactor);
|
return new FileDownloadManager(_loggerFactory.CreateLogger<FileDownloadManager>(), _mareMediator, _fileTransferOrchestrator, _fileCacheManager, _fileCompactor, _mareConfigService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,9 @@ using MareSynchronos.MareConfiguration;
|
|||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
|
using NotificationType = MareSynchronos.MareConfiguration.Models.NotificationType;
|
||||||
|
using MareSynchronos.WebAPI;
|
||||||
|
using MareSynchronos.API.Dto.User;
|
||||||
|
using MareSynchronos.API.Data;
|
||||||
|
|
||||||
namespace MareSynchronos.Services.AutoDetect;
|
namespace MareSynchronos.Services.AutoDetect;
|
||||||
|
|
||||||
@@ -26,8 +29,9 @@ public class AutoDetectRequestService
|
|||||||
private readonly ConcurrentDictionary<string, PendingRequestInfo> _pendingRequests = 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);
|
||||||
|
private readonly ApiController _apiController;
|
||||||
|
|
||||||
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService)
|
public AutoDetectRequestService(ILogger<AutoDetectRequestService> logger, DiscoveryConfigProvider configProvider, DiscoveryApiClient client, MareConfigService configService, MareMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configProvider = configProvider;
|
_configProvider = configProvider;
|
||||||
@@ -35,9 +39,10 @@ public class AutoDetectRequestService
|
|||||||
_configService = configService;
|
_configService = configService;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
_dalamud = dalamudUtilService;
|
_dalamud = dalamudUtilService;
|
||||||
|
_apiController = apiController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SendRequestAsync(string token, string? uid = null, string? targetDisplayName = null, CancellationToken ct = default)
|
public async Task<bool> SendRequestAsync(string? token, string? uid = null, string? targetDisplayName = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (!_configService.Current.AllowAutoDetectPairRequests)
|
if (!_configService.Current.AllowAutoDetectPairRequests)
|
||||||
{
|
{
|
||||||
@@ -45,6 +50,13 @@ public class AutoDetectRequestService
|
|||||||
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token) && string.IsNullOrEmpty(uid))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Nearby request blocked: no token or UID provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var targetKey = BuildTargetKey(uid, token, targetDisplayName);
|
var targetKey = BuildTargetKey(uid, token, targetDisplayName);
|
||||||
if (!string.IsNullOrEmpty(targetKey))
|
if (!string.IsNullOrEmpty(targetKey))
|
||||||
{
|
{
|
||||||
@@ -101,8 +113,11 @@ public class AutoDetectRequestService
|
|||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
|
var requestToken = string.IsNullOrEmpty(token) ? null : token;
|
||||||
|
var requestUid = requestToken == null ? uid : null;
|
||||||
|
|
||||||
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
|
_logger.LogInformation("Nearby: sending pair request via {endpoint}", endpoint);
|
||||||
var ok = await _client.SendRequestAsync(endpoint!, token, displayName, ct).ConfigureAwait(false);
|
var ok = await _client.SendRequestAsync(endpoint!, requestToken, requestUid, displayName, ct).ConfigureAwait(false);
|
||||||
if (ok)
|
if (ok)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(targetKey))
|
if (!string.IsNullOrEmpty(targetKey))
|
||||||
@@ -180,6 +195,126 @@ public class AutoDetectRequestService
|
|||||||
return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false);
|
return await _client.SendAcceptAsync(endpoint!, targetUid, displayName, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendDirectUidRequestAsync(string uid, string? targetDisplayName = null, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (!_configService.Current.AllowAutoDetectPairRequests)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Nearby request blocked: AllowAutoDetectPairRequests is disabled");
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request blocked", "Enable 'Allow pair requests' in Settings to send requests.", NotificationType.Info));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(uid))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Direct pair request aborted: UID is empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetKey = BuildTargetKey(uid, null, targetDisplayName);
|
||||||
|
if (!string.IsNullOrEmpty(targetKey))
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
if (_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||||
|
{
|
||||||
|
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value > now)
|
||||||
|
{
|
||||||
|
PublishLockNotification(tracker.LockUntil.Value - now);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value <= now)
|
||||||
|
{
|
||||||
|
tracker.LockUntil = null;
|
||||||
|
tracker.Count = 0;
|
||||||
|
if (tracker.Count == 0 && tracker.LockUntil == null)
|
||||||
|
{
|
||||||
|
_refusalTrackers.Remove(targetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeCooldowns.TryGetValue(targetKey, out var lastSent))
|
||||||
|
{
|
||||||
|
var elapsed = now - lastSent;
|
||||||
|
if (elapsed < RequestCooldown)
|
||||||
|
{
|
||||||
|
PublishCooldownNotification(RequestCooldown - elapsed);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elapsed >= RequestCooldown)
|
||||||
|
{
|
||||||
|
_activeCooldowns.Remove(targetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _apiController.UserAddPair(new UserDto(new UserData(uid))).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(targetKey))
|
||||||
|
{
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_activeCooldowns[targetKey] = DateTime.UtcNow;
|
||||||
|
if (_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||||
|
{
|
||||||
|
tracker.Count = 0;
|
||||||
|
tracker.LockUntil = null;
|
||||||
|
if (tracker.Count == 0 && tracker.LockUntil == null)
|
||||||
|
{
|
||||||
|
_refusalTrackers.Remove(targetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_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! : uid;
|
||||||
|
_pendingRequests[pendingKey] = new PendingRequestInfo(pendingKey, uid, null, label, DateTime.UtcNow);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Direct pair request failed for {uid}", uid);
|
||||||
|
if (!string.IsNullOrEmpty(targetKey))
|
||||||
|
{
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
lock (_syncRoot)
|
||||||
|
{
|
||||||
|
_activeCooldowns.Remove(targetKey);
|
||||||
|
if (!_refusalTrackers.TryGetValue(targetKey, out var tracker))
|
||||||
|
{
|
||||||
|
tracker = new RefusalTracker();
|
||||||
|
_refusalTrackers[targetKey] = tracker;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracker.LockUntil.HasValue && tracker.LockUntil.Value <= now)
|
||||||
|
{
|
||||||
|
tracker.LockUntil = null;
|
||||||
|
tracker.Count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracker.Count++;
|
||||||
|
if (tracker.Count >= 3)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string? BuildTargetKey(string? uid, string? token, string? displayName)
|
private static string? BuildTargetKey(string? uid, string? token, string? displayName)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(uid)) return "uid:" + uid;
|
if (!string.IsNullOrEmpty(uid)) return "uid:" + uid;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -23,6 +25,7 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
|||||||
_api = api;
|
_api = api;
|
||||||
_requestService = requestService;
|
_requestService = requestService;
|
||||||
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
|
_mediator.Subscribe<NotificationMessage>(this, OnNotification);
|
||||||
|
_mediator.Subscribe<ManualPairInviteMessage>(this, OnManualPairInvite);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MareMediator Mediator => _mediator;
|
public MareMediator Mediator => _mediator;
|
||||||
@@ -65,6 +68,20 @@ public sealed class NearbyPendingService : IMediatorSubscriber
|
|||||||
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
|
_logger.LogInformation("NearbyPending: received request from {uid} ({name})", uid, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnManualPairInvite(ManualPairInviteMessage msg)
|
||||||
|
{
|
||||||
|
if (!string.Equals(msg.TargetUid, _api.UID, StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var display = !string.IsNullOrWhiteSpace(msg.DisplayName)
|
||||||
|
? msg.DisplayName!
|
||||||
|
: (!string.IsNullOrWhiteSpace(msg.SourceAlias) ? msg.SourceAlias : msg.SourceUid);
|
||||||
|
|
||||||
|
_pending[msg.SourceUid] = display;
|
||||||
|
_logger.LogInformation("NearbyPending: received manual invite from {uid} ({name})", msg.SourceUid, display);
|
||||||
|
_mediator.Publish(new NotificationMessage("Nearby request", $"{display} vous a envoyé une invitation de pair.", NotificationType.Info, TimeSpan.FromSeconds(5)));
|
||||||
|
}
|
||||||
|
|
||||||
public void Remove(string uid)
|
public void Remove(string uid)
|
||||||
{
|
{
|
||||||
_pending.TryRemove(uid, out _);
|
_pending.TryRemove(uid, out _);
|
||||||
|
|||||||
@@ -266,26 +266,47 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void ManageWispsNearby(List<PoseEntryExtended> previousPoses)
|
private void ManageWispsNearby(List<PoseEntryExtended> previousPoses)
|
||||||
{
|
{
|
||||||
foreach (var data in _nearbyData.Keys)
|
int maxWisps = _charaDataConfigService.Current.NearbyMaxWisps;
|
||||||
|
if (maxWisps <= 0)
|
||||||
{
|
{
|
||||||
if (_poseVfx.TryGetValue(data, out var _)) continue;
|
ClearAllVfx();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Guid? vfxGuid;
|
const int hardLimit = 200;
|
||||||
if (data.MetaInfo.IsOwnData)
|
if (maxWisps > hardLimit) maxWisps = hardLimit;
|
||||||
|
|
||||||
|
var orderedAllowedPoses = _nearbyData
|
||||||
|
.OrderBy(kvp => kvp.Value.Distance)
|
||||||
|
.Take(maxWisps)
|
||||||
|
.Select(kvp => kvp.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var allowedPoseSet = orderedAllowedPoses.ToHashSet();
|
||||||
|
|
||||||
|
foreach (var data in orderedAllowedPoses)
|
||||||
{
|
{
|
||||||
vfxGuid = _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2, 0.8f, 0.5f, 0.0f, 0.7f);
|
if (_poseVfx.TryGetValue(data, out _)) continue;
|
||||||
}
|
|
||||||
else
|
Guid? vfxGuid = data.MetaInfo.IsOwnData
|
||||||
{
|
? _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2, 0.8f, 0.5f, 0.0f, 0.7f)
|
||||||
vfxGuid = _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2);
|
: _vfxSpawnManager.SpawnObject(data.Position, data.Rotation, Vector3.One * 2);
|
||||||
}
|
|
||||||
if (vfxGuid != null)
|
if (vfxGuid != null)
|
||||||
{
|
{
|
||||||
_poseVfx[data] = vfxGuid.Value;
|
_poseVfx[data] = vfxGuid.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var data in previousPoses.Except(_nearbyData.Keys))
|
foreach (var data in previousPoses.Except(allowedPoseSet))
|
||||||
|
{
|
||||||
|
if (_poseVfx.Remove(data, out var guid))
|
||||||
|
{
|
||||||
|
_vfxSpawnManager.DespawnObject(guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var data in _poseVfx.Keys.Except(allowedPoseSet).ToList())
|
||||||
{
|
{
|
||||||
if (_poseVfx.Remove(data, out var guid))
|
if (_poseVfx.Remove(data, out var guid))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Dalamud.Game.Text;
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
@@ -19,6 +21,7 @@ namespace MareSynchronos.Services;
|
|||||||
public class ChatService : DisposableMediatorSubscriberBase
|
public class ChatService : DisposableMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
public const int DefaultColor = 710;
|
public const int DefaultColor = 710;
|
||||||
|
private const string ManualPairInvitePrefix = "[UmbraPairInvite|";
|
||||||
public const int CommandMaxNumber = 50;
|
public const int CommandMaxNumber = 50;
|
||||||
|
|
||||||
private readonly ILogger<ChatService> _logger;
|
private readonly ILogger<ChatService> _logger;
|
||||||
@@ -191,6 +194,10 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
var extraChatTags = _mareConfig.Current.ExtraChatTags;
|
var extraChatTags = _mareConfig.Current.ExtraChatTags;
|
||||||
var logKind = ResolveShellLogKind(shellConfig.LogKind);
|
var logKind = ResolveShellLogKind(shellConfig.LogKind);
|
||||||
|
|
||||||
|
var payload = SeString.Parse(message.ChatMsg.PayloadContent);
|
||||||
|
if (TryHandleManualPairInvite(message, payload))
|
||||||
|
return;
|
||||||
|
|
||||||
var msg = new SeStringBuilder();
|
var msg = new SeStringBuilder();
|
||||||
if (extraChatTags)
|
if (extraChatTags)
|
||||||
{
|
{
|
||||||
@@ -209,7 +216,7 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
msg.Add(new PlayerPayload(chatMsg.SenderName, chatMsg.SenderHomeWorldId));
|
msg.Add(new PlayerPayload(chatMsg.SenderName, chatMsg.SenderHomeWorldId));
|
||||||
}
|
}
|
||||||
msg.AddText("> ");
|
msg.AddText("> ");
|
||||||
msg.Append(SeString.Parse(message.ChatMsg.PayloadContent));
|
msg.Append(payload);
|
||||||
if (color != 0)
|
if (color != 0)
|
||||||
msg.AddUiForegroundOff();
|
msg.AddUiForegroundOff();
|
||||||
|
|
||||||
@@ -219,6 +226,52 @@ public class ChatService : DisposableMediatorSubscriberBase
|
|||||||
Type = logKind
|
Type = logKind
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryHandleManualPairInvite(GroupChatMsgMessage message, SeString payload)
|
||||||
|
{
|
||||||
|
var textValue = payload.TextValue;
|
||||||
|
if (string.IsNullOrEmpty(textValue) || !textValue.StartsWith(ManualPairInvitePrefix, StringComparison.Ordinal))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var content = textValue[ManualPairInvitePrefix.Length..];
|
||||||
|
if (content.EndsWith("]", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
content = content[..^1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = content.Split('|');
|
||||||
|
if (parts.Length < 4)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var sourceUid = parts[0];
|
||||||
|
var sourceAlias = DecodeInviteField(parts[1]);
|
||||||
|
var targetUid = parts[2];
|
||||||
|
var displayName = DecodeInviteField(parts[3]);
|
||||||
|
var inviteId = parts.Length > 4 ? parts[4] : Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
|
if (!string.Equals(targetUid, _apiController.UID, StringComparison.Ordinal))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Mediator.Publish(new ManualPairInviteMessage(sourceUid, sourceAlias, targetUid, string.IsNullOrEmpty(displayName) ? null : displayName, inviteId));
|
||||||
|
_logger.LogDebug("Received manual pair invite from {source} via syncshell", sourceUid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DecodeInviteField(string encoded)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(encoded)) return string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = Convert.FromBase64String(encoded);
|
||||||
|
return Encoding.UTF8.GetString(bytes);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
public void PrintChannelExample(string message, string gid = "")
|
public void PrintChannelExample(string message, string gid = "")
|
||||||
{
|
{
|
||||||
int chatType = _mareConfig.Current.ChatLogKind;
|
int chatType = _mareConfig.Current.ChatLogKind;
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ public record NearbyEntry(string Name, ushort WorldId, float Distance, bool IsMa
|
|||||||
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
public record DiscoveryListUpdated(List<NearbyEntry> Entries) : MessageBase;
|
||||||
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
public record NearbyDetectionToggled(bool Enabled) : MessageBase;
|
||||||
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
public record AllowPairRequestsToggled(bool Enabled) : MessageBase;
|
||||||
|
public record ManualPairInviteMessage(string SourceUid, string SourceAlias, string TargetUid, string? DisplayName, string InviteId) : MessageBase;
|
||||||
public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase;
|
public record ApplyDefaultPairPermissionsMessage(UserPairDto Pair) : MessageBase;
|
||||||
public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase;
|
public record ApplyDefaultGroupPermissionsMessage(GroupPairFullInfoDto GroupPair) : MessageBase;
|
||||||
public record ApplyDefaultsToAllSyncsMessage(string? Context = null, bool? Disabled = null) : MessageBase;
|
public record ApplyDefaultsToAllSyncsMessage(string? Context = null, bool? Disabled = null) : MessageBase;
|
||||||
|
|||||||
@@ -76,12 +76,10 @@ public sealed class TypingIndicatorStateService : IMediatorSubscriber, IDisposab
|
|||||||
_typingUsers.AddOrUpdate(uid,
|
_typingUsers.AddOrUpdate(uid,
|
||||||
_ => new TypingEntry(msg.Typing.User, now, now),
|
_ => new TypingEntry(msg.Typing.User, now, now),
|
||||||
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now));
|
(_, existing) => new TypingEntry(msg.Typing.User, existing.FirstSeen, now));
|
||||||
_logger.LogInformation("Typing state {uid} -> true", uid);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_typingUsers.TryRemove(uid, out _);
|
_typingUsers.TryRemove(uid, out _);
|
||||||
_logger.LogInformation("Typing state {uid} -> false", uid);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,14 @@ public sealed class ChangelogUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
return new List<ChangelogEntry>
|
return new List<ChangelogEntry>
|
||||||
{
|
{
|
||||||
|
new(new Version(0, 1, 9, 5), "0.1.9.5", new List<ChangelogLine>
|
||||||
|
{
|
||||||
|
new("Fix l'affichage de la bulle dans la liste du groupe."),
|
||||||
|
new("Amélioration de l'ajout des utilisateurs via le bouton +."),
|
||||||
|
new("Possibilité de mettre en pause individuellement des utilisateurs d'une syncshell."),
|
||||||
|
new("Amélioration de la stabilité du plugin en cas de petite connexion / petite configuration."),
|
||||||
|
new("Divers fix de l'interface."),
|
||||||
|
}),
|
||||||
new(new Version(0, 1, 9, 4), "0.1.9.4", new List<ChangelogLine>
|
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("Réécriture complète de la bulle de frappe avec la possibilité de choisir la taille de la bulle."),
|
||||||
|
|||||||
@@ -57,6 +57,14 @@ internal partial class CharaDataHubUi
|
|||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
_uiSharedService.DrawHelpText("Draw floating wisps where other's poses are in the world.");
|
_uiSharedService.DrawHelpText("Draw floating wisps where other's poses are in the world.");
|
||||||
|
int maxWisps = _configService.Current.NearbyMaxWisps;
|
||||||
|
ImGui.SetNextItemWidth(140);
|
||||||
|
if (ImGui.SliderInt("Maximum wisps", ref maxWisps, 0, 200))
|
||||||
|
{
|
||||||
|
_configService.Current.NearbyMaxWisps = maxWisps;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
_uiSharedService.DrawHelpText("Limit how many wisps are active at once. Set to 0 to disable wisps even when enabled above.");
|
||||||
int poseDetectionDistance = _configService.Current.NearbyDistanceFilter;
|
int poseDetectionDistance = _configService.Current.NearbyDistanceFilter;
|
||||||
ImGui.SetNextItemWidth(100);
|
ImGui.SetNextItemWidth(100);
|
||||||
if (ImGui.SliderInt("Detection Distance", ref poseDetectionDistance, 5, 1000))
|
if (ImGui.SliderInt("Detection Distance", ref poseDetectionDistance, 5, 1000))
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
_characterAnalyzer = characterAnalyzer;
|
_characterAnalyzer = characterAnalyzer;
|
||||||
var tagHandler = new TagHandler(_serverManager);
|
var tagHandler = new TagHandler(_serverManager);
|
||||||
|
|
||||||
_groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager);
|
_groupPanel = new(this, uiShared, _pairManager, chatService, uidDisplayHandler, _configService, _serverManager, _charaDataManager, _autoDetectRequestService);
|
||||||
_selectGroupForPairUi = new(tagHandler, uidDisplayHandler, _uiSharedService);
|
_selectGroupForPairUi = new(tagHandler, uidDisplayHandler, _uiSharedService);
|
||||||
_selectPairsForGroupUi = new(tagHandler, uidDisplayHandler);
|
_selectPairsForGroupUi = new(tagHandler, uidDisplayHandler);
|
||||||
_pairGroupsUi = new(configService, tagHandler, uidDisplayHandler, apiController, _selectPairsForGroupUi, _uiSharedService);
|
_pairGroupsUi = new(configService, tagHandler, uidDisplayHandler, apiController, _selectPairsForGroupUi, _uiSharedService);
|
||||||
@@ -433,6 +433,8 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var compressedValue = UiSharedService.ByteToString(summary.TotalCompressedSize);
|
var compressedValue = UiSharedService.ByteToString(summary.TotalCompressedSize);
|
||||||
Vector4? compressedColor = null;
|
Vector4? compressedColor = null;
|
||||||
|
FontAwesomeIcon? compressedIcon = null;
|
||||||
|
Vector4? compressedIconColor = null;
|
||||||
string? compressedTooltip = null;
|
string? compressedTooltip = null;
|
||||||
if (summary.HasUncomputedEntries)
|
if (summary.HasUncomputedEntries)
|
||||||
{
|
{
|
||||||
@@ -443,19 +445,25 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
compressedColor = ImGuiColors.DalamudYellow;
|
compressedColor = ImGuiColors.DalamudYellow;
|
||||||
compressedTooltip = "Au-delà de 300 MiB, certains joueurs peuvent ne pas voir toutes vos modifications.";
|
compressedTooltip = "Au-delà de 300 MiB, certains joueurs peuvent ne pas voir toutes vos modifications.";
|
||||||
|
compressedIcon = FontAwesomeIcon.ExclamationTriangle;
|
||||||
|
compressedIconColor = ImGuiColors.DalamudYellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawSelfAnalysisStatRow("Taille compressée", compressedValue, compressedColor, compressedTooltip);
|
DrawSelfAnalysisStatRow("Taille compressée", compressedValue, compressedColor, compressedTooltip, compressedIcon, compressedIconColor);
|
||||||
DrawSelfAnalysisStatRow("Taille extraite", UiSharedService.ByteToString(summary.TotalOriginalSize));
|
DrawSelfAnalysisStatRow("Taille extraite", UiSharedService.ByteToString(summary.TotalOriginalSize));
|
||||||
|
|
||||||
Vector4? trianglesColor = null;
|
Vector4? trianglesColor = null;
|
||||||
|
FontAwesomeIcon? trianglesIcon = null;
|
||||||
|
Vector4? trianglesIconColor = null;
|
||||||
string? trianglesTooltip = null;
|
string? trianglesTooltip = null;
|
||||||
if (summary.TotalTriangles >= SelfAnalysisTriangleWarningThreshold)
|
if (summary.TotalTriangles >= SelfAnalysisTriangleWarningThreshold)
|
||||||
{
|
{
|
||||||
trianglesColor = ImGuiColors.DalamudYellow;
|
trianglesColor = ImGuiColors.DalamudYellow;
|
||||||
trianglesTooltip = "Plus de 150k triangles peuvent entraîner un auto-pause et impacter les performances.";
|
trianglesTooltip = "Plus de 150k triangles peuvent entraîner un auto-pause et impacter les performances.";
|
||||||
|
trianglesIcon = FontAwesomeIcon.ExclamationTriangle;
|
||||||
|
trianglesIconColor = ImGuiColors.DalamudYellow;
|
||||||
}
|
}
|
||||||
DrawSelfAnalysisStatRow("Triangles moddés", UiSharedService.TrisToString(summary.TotalTriangles), trianglesColor, trianglesTooltip);
|
DrawSelfAnalysisStatRow("Triangles moddés", UiSharedService.TrisToString(summary.TotalTriangles), trianglesColor, trianglesTooltip, trianglesIcon, trianglesIconColor);
|
||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
@@ -490,12 +498,29 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawSelfAnalysisStatRow(string label, string value, Vector4? valueColor = null, string? tooltip = null)
|
private static void DrawSelfAnalysisStatRow(string label, string value, Vector4? valueColor = null, string? tooltip = null, FontAwesomeIcon? icon = null, Vector4? iconColor = null)
|
||||||
{
|
{
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(label);
|
ImGui.TextUnformatted(label);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
if (icon.HasValue)
|
||||||
|
{
|
||||||
|
using (ImRaii.PushFont(UiBuilder.IconFont))
|
||||||
|
{
|
||||||
|
if (iconColor.HasValue)
|
||||||
|
{
|
||||||
|
using var iconColorPush = ImRaii.PushColor(ImGuiCol.Text, iconColor.Value);
|
||||||
|
ImGui.TextUnformatted(icon.Value.ToIconString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted(icon.Value.ToIconString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.SameLine(0f, 4f);
|
||||||
|
}
|
||||||
|
|
||||||
if (valueColor.HasValue)
|
if (valueColor.HasValue)
|
||||||
{
|
{
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, valueColor.Value);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, valueColor.Value);
|
||||||
@@ -687,9 +712,9 @@ public class CompactUi : WindowMediatorSubscriberBase
|
|||||||
ImGuiHelpers.ScaledDummy(4);
|
ImGuiHelpers.ScaledDummy(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
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, _serverManager)).ToList();
|
||||||
var visibleUsers = users.Where(u => u.IsVisible).Select(c => new DrawUserPair("Visible" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager)).ToList();
|
var visibleUsers = users.Where(u => u.IsVisible).Select(c => new DrawUserPair("Visible" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager, _serverManager)).ToList();
|
||||||
var offlineUsers = users.Where(u => !u.UserPair!.OtherPermissions.IsPaired() || (!u.IsOnline && !u.UserPair!.OwnPermissions.IsPaused())).Select(c => new DrawUserPair("Offline" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager)).ToList();
|
var offlineUsers = users.Where(u => !u.UserPair!.OtherPermissions.IsPaired() || (!u.IsOnline && !u.UserPair!.OwnPermissions.IsPaused())).Select(c => new DrawUserPair("Offline" + c.UserData.UID, c, _uidDisplayHandler, _apiController, Mediator, _selectGroupForPairUi, _uiSharedService, _charaDataManager, _serverManager)).ToList();
|
||||||
|
|
||||||
_pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers);
|
_pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers);
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
using System.Numerics;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Colors;
|
using Dalamud.Interface.Colors;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using MareSynchronos.API.Data;
|
||||||
using MareSynchronos.API.Data.Enum;
|
using MareSynchronos.API.Data.Enum;
|
||||||
using MareSynchronos.API.Data.Extensions;
|
using MareSynchronos.API.Data.Extensions;
|
||||||
using MareSynchronos.API.Dto.Group;
|
using MareSynchronos.API.Dto.Group;
|
||||||
using MareSynchronos.API.Dto.User;
|
|
||||||
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.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
|
|
||||||
@@ -21,16 +27,22 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
private readonly GroupPairFullInfoDto _fullInfoDto;
|
private readonly GroupPairFullInfoDto _fullInfoDto;
|
||||||
private readonly GroupFullInfoDto _group;
|
private readonly GroupFullInfoDto _group;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
private readonly AutoDetectRequestService _autoDetectRequestService;
|
||||||
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
|
private const string ManualPairInvitePrefix = "[UmbraPairInvite|";
|
||||||
|
|
||||||
public DrawGroupPair(string id, Pair entry, ApiController apiController,
|
public DrawGroupPair(string id, Pair entry, ApiController apiController,
|
||||||
MareMediator mareMediator, GroupFullInfoDto group, GroupPairFullInfoDto fullInfoDto,
|
MareMediator mareMediator, GroupFullInfoDto group, GroupPairFullInfoDto fullInfoDto,
|
||||||
UidDisplayHandler handler, UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
UidDisplayHandler handler, UiSharedService uiSharedService, CharaDataManager charaDataManager,
|
||||||
|
AutoDetectRequestService autoDetectRequestService, ServerConfigurationManager serverConfigurationManager)
|
||||||
: base(id, entry, apiController, handler, uiSharedService)
|
: base(id, entry, apiController, handler, uiSharedService)
|
||||||
{
|
{
|
||||||
_group = group;
|
_group = group;
|
||||||
_fullInfoDto = fullInfoDto;
|
_fullInfoDto = fullInfoDto;
|
||||||
_mediator = mareMediator;
|
_mediator = mareMediator;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
|
_autoDetectRequestService = autoDetectRequestService;
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawLeftSide(float textPosY, float originalY)
|
protected override void DrawLeftSide(float textPosY, float originalY)
|
||||||
@@ -153,8 +165,9 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
|
|
||||||
bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData);
|
bool showShared = _charaDataManager.SharedWithYouData.TryGetValue(_pair.UserData, out var sharedData);
|
||||||
bool showInfo = (individualAnimDisabled || individualSoundsDisabled || animDisabled || soundsDisabled);
|
bool showInfo = (individualAnimDisabled || individualSoundsDisabled || animDisabled || soundsDisabled);
|
||||||
bool showPlus = _pair.UserPair == null;
|
bool showPlus = _pair.UserPair == null && _pair.IsOnline;
|
||||||
bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused;
|
bool showBars = (userIsOwner || (userIsModerator && !entryIsMod && !entryIsOwner)) || !_pair.IsPaused;
|
||||||
|
bool showPause = true;
|
||||||
|
|
||||||
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
var spacing = ImGui.GetStyle().ItemSpacing.X;
|
||||||
var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle
|
var permIcon = (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled) ? FontAwesomeIcon.ExclamationTriangle
|
||||||
@@ -162,12 +175,15 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
var runningIconWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X;
|
var runningIconWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Running).X;
|
||||||
var infoIconWidth = UiSharedService.GetIconSize(permIcon).X;
|
var infoIconWidth = UiSharedService.GetIconSize(permIcon).X;
|
||||||
var plusButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X;
|
var plusButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Plus).X;
|
||||||
|
var pauseIcon = _fullInfoDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
|
||||||
|
var pauseButtonWidth = _uiSharedService.GetIconButtonSize(pauseIcon).X;
|
||||||
var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
|
var barButtonWidth = _uiSharedService.GetIconButtonSize(FontAwesomeIcon.Bars).X;
|
||||||
|
|
||||||
var pos = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() + spacing
|
var pos = ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth() + spacing
|
||||||
- (showShared ? (runningIconWidth + spacing) : 0)
|
- (showShared ? (runningIconWidth + spacing) : 0)
|
||||||
- (showInfo ? (infoIconWidth + spacing) : 0)
|
- (showInfo ? (infoIconWidth + spacing) : 0)
|
||||||
- (showPlus ? (plusButtonWidth + spacing) : 0)
|
- (showPlus ? (plusButtonWidth + spacing) : 0)
|
||||||
|
- (showPause ? (pauseButtonWidth + spacing) : 0)
|
||||||
- (showBars ? (barButtonWidth + spacing) : 0);
|
- (showBars ? (barButtonWidth + spacing) : 0);
|
||||||
|
|
||||||
ImGui.SameLine(pos);
|
ImGui.SameLine(pos);
|
||||||
@@ -280,9 +296,28 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
|
|
||||||
if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
|
if (_uiSharedService.IconButton(FontAwesomeIcon.Plus))
|
||||||
{
|
{
|
||||||
_ = _apiController.UserAddPair(new UserDto(new(_pair.UserData.UID)));
|
var targetUid = _pair.UserData.UID;
|
||||||
|
if (!string.IsNullOrEmpty(targetUid))
|
||||||
|
{
|
||||||
|
_ = SendGroupPairInviteAsync(targetUid, entryUID);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Pair with " + entryUID + " individually");
|
}
|
||||||
|
UiSharedService.AttachToolTip(AppendSeenInfo("Send pairing invite to " + entryUID));
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPause)
|
||||||
|
{
|
||||||
|
ImGui.SetCursorPosY(originalY);
|
||||||
|
|
||||||
|
if (_uiSharedService.IconButton(pauseIcon))
|
||||||
|
{
|
||||||
|
var newPermissions = _fullInfoDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
|
||||||
|
_fullInfoDto.GroupUserPermissions = newPermissions;
|
||||||
|
_ = _apiController.GroupChangeIndividualPermissionState(new GroupPairUserPermissionDto(_group.Group, _pair.UserData, newPermissions));
|
||||||
|
}
|
||||||
|
|
||||||
|
UiSharedService.AttachToolTip(AppendSeenInfo((_fullInfoDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " syncing with " + entryUID));
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,4 +418,74 @@ public class DrawGroupPair : DrawPairBase
|
|||||||
|
|
||||||
return pos - spacing;
|
return pos - spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string AppendSeenInfo(string tooltip)
|
||||||
|
{
|
||||||
|
if (_pair.IsVisible) return tooltip;
|
||||||
|
|
||||||
|
var lastSeen = _serverConfigurationManager.GetNameForUid(_pair.UserData.UID);
|
||||||
|
if (string.IsNullOrWhiteSpace(lastSeen)) return tooltip;
|
||||||
|
|
||||||
|
return tooltip + " (Vu sous : " + lastSeen + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendGroupPairInviteAsync(string targetUid, string displayName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ok = await _autoDetectRequestService.SendDirectUidRequestAsync(targetUid, displayName).ConfigureAwait(false);
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
await SendManualInviteSignalAsync(targetUid, displayName).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// errors are logged within the request service; ignore here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendManualInviteSignalAsync(string targetUid, string displayName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_apiController.UID)) return;
|
||||||
|
|
||||||
|
var senderAliasRaw = string.IsNullOrEmpty(_apiController.DisplayName) ? _apiController.UID : _apiController.DisplayName;
|
||||||
|
var senderAlias = EncodeInviteField(senderAliasRaw);
|
||||||
|
var targetDisplay = EncodeInviteField(displayName);
|
||||||
|
var inviteId = Guid.NewGuid().ToString("N");
|
||||||
|
var payloadText = new StringBuilder()
|
||||||
|
.Append(ManualPairInvitePrefix)
|
||||||
|
.Append(_apiController.UID)
|
||||||
|
.Append('|')
|
||||||
|
.Append(senderAlias)
|
||||||
|
.Append('|')
|
||||||
|
.Append(targetUid)
|
||||||
|
.Append('|')
|
||||||
|
.Append(targetDisplay)
|
||||||
|
.Append('|')
|
||||||
|
.Append(inviteId)
|
||||||
|
.Append(']')
|
||||||
|
.ToString();
|
||||||
|
|
||||||
|
var payload = new SeStringBuilder().AddText(payloadText).Build().Encode();
|
||||||
|
var chatMessage = new ChatMessage
|
||||||
|
{
|
||||||
|
SenderName = senderAlias,
|
||||||
|
PayloadContent = payload
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _apiController.GroupChatSendMsg(new GroupDto(_group.Group), chatMessage).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore - invite remains tracked locally even if group chat signal fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EncodeInviteField(string value)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using MareSynchronos.Services.Mediator;
|
|||||||
using MareSynchronos.UI.Handlers;
|
using MareSynchronos.UI.Handlers;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using MareSynchronos.Services.ServerConfiguration;
|
||||||
|
|
||||||
namespace MareSynchronos.UI.Components;
|
namespace MareSynchronos.UI.Components;
|
||||||
|
|
||||||
@@ -20,10 +21,12 @@ public class DrawUserPair : DrawPairBase
|
|||||||
protected readonly MareMediator _mediator;
|
protected readonly MareMediator _mediator;
|
||||||
private readonly SelectGroupForPairUi _selectGroupForPairUi;
|
private readonly SelectGroupForPairUi _selectGroupForPairUi;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
public DrawUserPair(string id, Pair entry, UidDisplayHandler displayHandler, ApiController apiController,
|
public DrawUserPair(string id, Pair entry, UidDisplayHandler displayHandler, ApiController apiController,
|
||||||
MareMediator mareMediator, SelectGroupForPairUi selectGroupForPairUi,
|
MareMediator mareMediator, SelectGroupForPairUi selectGroupForPairUi,
|
||||||
UiSharedService uiSharedService, CharaDataManager charaDataManager)
|
UiSharedService uiSharedService, CharaDataManager charaDataManager,
|
||||||
|
ServerConfigurationManager serverConfigurationManager)
|
||||||
: base(id, entry, apiController, displayHandler, uiSharedService)
|
: base(id, entry, apiController, displayHandler, uiSharedService)
|
||||||
{
|
{
|
||||||
if (_pair.UserPair == null) throw new ArgumentException("Pair must be UserPair", nameof(entry));
|
if (_pair.UserPair == null) throw new ArgumentException("Pair must be UserPair", nameof(entry));
|
||||||
@@ -31,6 +34,7 @@ public class DrawUserPair : DrawPairBase
|
|||||||
_selectGroupForPairUi = selectGroupForPairUi;
|
_selectGroupForPairUi = selectGroupForPairUi;
|
||||||
_mediator = mareMediator;
|
_mediator = mareMediator;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsOnline => _pair.IsOnline;
|
public bool IsOnline => _pair.IsOnline;
|
||||||
@@ -135,9 +139,9 @@ public class DrawUserPair : DrawPairBase
|
|||||||
perm.SetPaused(!perm.IsPaused());
|
perm.SetPaused(!perm.IsPaused());
|
||||||
_ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm));
|
_ = _apiController.UserSetPairPermissions(new(_pair.UserData, perm));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip(!_pair.UserPair!.OwnPermissions.IsPaused()
|
UiSharedService.AttachToolTip(AppendSeenInfo(!_pair.UserPair!.OwnPermissions.IsPaused()
|
||||||
? "Pause pairing with " + entryUID
|
? "Pause pairing with " + entryUID
|
||||||
: "Resume pairing with " + entryUID);
|
: "Resume pairing with " + entryUID));
|
||||||
|
|
||||||
|
|
||||||
var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false);
|
var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false);
|
||||||
@@ -263,7 +267,7 @@ public class DrawUserPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
_selectGroupForPairUi.Open(entry);
|
_selectGroupForPairUi.Open(entry);
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Choose pair groups for " + entryUID);
|
UiSharedService.AttachToolTip(AppendSeenInfo("Choose pair groups for " + entryUID));
|
||||||
|
|
||||||
var isDisableSounds = entry.UserPair!.OwnPermissions.IsDisableSounds();
|
var isDisableSounds = entry.UserPair!.OwnPermissions.IsDisableSounds();
|
||||||
string disableSoundsText = isDisableSounds ? "Enable sound sync" : "Disable sound sync";
|
string disableSoundsText = isDisableSounds ? "Enable sound sync" : "Disable sound sync";
|
||||||
@@ -302,6 +306,16 @@ public class DrawUserPair : DrawPairBase
|
|||||||
{
|
{
|
||||||
_ = _apiController.UserRemovePair(new(entry.UserData));
|
_ = _apiController.UserRemovePair(new(entry.UserData));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
|
UiSharedService.AttachToolTip(AppendSeenInfo("Hold CTRL and click to unpair permanently from " + entryUID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AppendSeenInfo(string tooltip)
|
||||||
|
{
|
||||||
|
if (_pair.IsVisible) return tooltip;
|
||||||
|
|
||||||
|
var lastSeen = _serverConfigurationManager.GetNameForUid(_pair.UserData.UID);
|
||||||
|
if (string.IsNullOrWhiteSpace(lastSeen)) return tooltip;
|
||||||
|
|
||||||
|
return tooltip + " (Vu sous : " + lastSeen + ")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using MareSynchronos.API.Dto.Group;
|
|||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
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.UI.Components;
|
using MareSynchronos.UI.Components;
|
||||||
@@ -32,6 +33,7 @@ internal sealed class GroupPanel
|
|||||||
private readonly MareConfigService _mareConfig;
|
private readonly MareConfigService _mareConfig;
|
||||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
|
private readonly AutoDetectRequestService _autoDetectRequestService;
|
||||||
private readonly Dictionary<string, bool> _showGidForEntry = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, bool> _showGidForEntry = new(StringComparer.Ordinal);
|
||||||
private readonly UidDisplayHandler _uidDisplayHandler;
|
private readonly UidDisplayHandler _uidDisplayHandler;
|
||||||
private readonly UiSharedService _uiShared;
|
private readonly UiSharedService _uiShared;
|
||||||
@@ -74,7 +76,7 @@ internal sealed class GroupPanel
|
|||||||
|
|
||||||
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
public GroupPanel(CompactUi mainUi, UiSharedService uiShared, PairManager pairManager, ChatService chatServivce,
|
||||||
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
|
UidDisplayHandler uidDisplayHandler, MareConfigService mareConfig, ServerConfigurationManager serverConfigurationManager,
|
||||||
CharaDataManager charaDataManager)
|
CharaDataManager charaDataManager, AutoDetectRequestService autoDetectRequestService)
|
||||||
{
|
{
|
||||||
_mainUi = mainUi;
|
_mainUi = mainUi;
|
||||||
_uiShared = uiShared;
|
_uiShared = uiShared;
|
||||||
@@ -84,6 +86,7 @@ internal sealed class GroupPanel
|
|||||||
_mareConfig = mareConfig;
|
_mareConfig = mareConfig;
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
|
_autoDetectRequestService = autoDetectRequestService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ApiController ApiController => _uiShared.ApiController;
|
private ApiController ApiController => _uiShared.ApiController;
|
||||||
@@ -566,7 +569,9 @@ internal sealed class GroupPanel
|
|||||||
).Value,
|
).Value,
|
||||||
_uidDisplayHandler,
|
_uidDisplayHandler,
|
||||||
_uiShared,
|
_uiShared,
|
||||||
_charaDataManager);
|
_charaDataManager,
|
||||||
|
_autoDetectRequestService,
|
||||||
|
_serverConfigurationManager);
|
||||||
|
|
||||||
if (pair.IsVisible)
|
if (pair.IsVisible)
|
||||||
visibleUsers.Add(drawPair);
|
visibleUsers.Add(drawPair);
|
||||||
|
|||||||
@@ -209,12 +209,24 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.AlignTextToFramePadding();
|
ImGui.AlignTextToFramePadding();
|
||||||
ImGui.TextUnformatted("0 = No limit/infinite");
|
ImGui.TextUnformatted("0 = No limit/infinite");
|
||||||
|
|
||||||
|
bool enableDownloadQueue = _configService.Current.EnableDownloadQueue;
|
||||||
|
if (ImGui.Checkbox("Activer la file de téléchargements", ref enableDownloadQueue))
|
||||||
|
{
|
||||||
|
_configService.Current.EnableDownloadQueue = enableDownloadQueue;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
UiSharedService.AttachToolTip("Lance les téléchargements de personnages de manière séquentielle plutôt que tous en même temps. "
|
||||||
|
+ "Quand l'option est activée, seul le nombre configuré de téléchargements fonctionne en parallèle.");
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(250 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.SliderInt("Maximum Parallel Downloads", ref maxParallelDownloads, 1, 10))
|
if (ImGui.SliderInt("Maximum Parallel Downloads", ref maxParallelDownloads, 1, 10))
|
||||||
{
|
{
|
||||||
_configService.Current.ParallelDownloads = maxParallelDownloads;
|
_configService.Current.ParallelDownloads = maxParallelDownloads;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
}
|
}
|
||||||
|
UiSharedService.AttachToolTip(enableDownloadQueue
|
||||||
|
? "Nombre maximal de téléchargements de personnages autorisés simultanément lorsque la file est activée."
|
||||||
|
: "Nombre maximal de flux de fichiers lancés en parallèle pour chaque téléchargement.");
|
||||||
|
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
_uiShared.BigText("AutoDetect");
|
_uiShared.BigText("AutoDetect");
|
||||||
@@ -222,7 +234,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
|
bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery;
|
||||||
using (ImRaii.Disabled(isAutoDetectSuppressed))
|
using (ImRaii.Disabled(isAutoDetectSuppressed))
|
||||||
{
|
{
|
||||||
if (ImGui.Checkbox("Enable AutoDetect", ref enableDiscovery))
|
if (ImGui.Checkbox("Activer l'AutoDetect", ref enableDiscovery))
|
||||||
{
|
{
|
||||||
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
_configService.Current.EnableAutoDetectDiscovery = enableDiscovery;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
@@ -248,7 +260,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
using (ImRaii.Disabled(isAutoDetectSuppressed || !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("Activer les invitations entrantes", ref allowRequests))
|
||||||
{
|
{
|
||||||
_configService.Current.AllowAutoDetectPairRequests = allowRequests;
|
_configService.Current.AllowAutoDetectPairRequests = allowRequests;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
@@ -275,7 +287,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.Indent();
|
ImGui.Indent();
|
||||||
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters;
|
||||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.SliderInt("Max distance (meters)", ref maxMeters, 5, 100))
|
if (ImGui.SliderInt("Distance max (en mètre)", ref maxMeters, 5, 100))
|
||||||
{
|
{
|
||||||
_configService.Current.AutoDetectMaxDistanceMeters = maxMeters;
|
_configService.Current.AutoDetectMaxDistanceMeters = maxMeters;
|
||||||
_configService.Save();
|
_configService.Save();
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Bindings.ImGui;
|
using Dalamud.Bindings.ImGui;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using MareSynchronos.API.Data;
|
using MareSynchronos.API.Data;
|
||||||
|
using MareSynchronos.API.Data.Extensions;
|
||||||
using MareSynchronos.MareConfiguration;
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.MareConfiguration.Models;
|
using MareSynchronos.MareConfiguration.Models;
|
||||||
using MareSynchronos.PlayerData.Pairs;
|
using MareSynchronos.PlayerData.Pairs;
|
||||||
@@ -115,25 +117,34 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var pair = _pairManager.GetPairByUID(uid);
|
var pair = _pairManager.GetPairByUID(uid);
|
||||||
if (pair == null)
|
var targetIndex = -1;
|
||||||
{
|
var playerName = pair?.PlayerName;
|
||||||
var alias = entry.User.AliasOrUID;
|
var objectId = pair?.PlayerCharacterId ?? uint.MaxValue;
|
||||||
if (string.IsNullOrEmpty(alias))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var aliasIndex = GetPartyIndexForName(alias);
|
if (objectId != 0 && objectId != uint.MaxValue)
|
||||||
if (aliasIndex >= 0)
|
|
||||||
{
|
{
|
||||||
DrawPartyMemberTyping(drawList, partyAddon, aliasIndex);
|
targetIndex = GetPartyIndexForObjectId(objectId);
|
||||||
|
if (targetIndex >= 0 && !string.IsNullOrEmpty(playerName))
|
||||||
|
{
|
||||||
|
var member = _partyList[targetIndex];
|
||||||
|
var memberName = member?.Name?.TextValue;
|
||||||
|
if (!string.IsNullOrEmpty(memberName) && !memberName.Equals(playerName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var nameIndex = GetPartyIndexForName(playerName);
|
||||||
|
targetIndex = nameIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = GetPartyIndexForObjectId(pair.PlayerCharacterId);
|
if (targetIndex < 0 && !string.IsNullOrEmpty(playerName))
|
||||||
if (index < 0)
|
{
|
||||||
|
targetIndex = GetPartyIndexForName(playerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetIndex < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
DrawPartyMemberTyping(drawList, partyAddon, index);
|
DrawPartyMemberTyping(drawList, partyAddon, targetIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,10 +209,10 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var pair = _pairManager.GetPairByUID(uid);
|
var pair = _pairManager.GetPairByUID(uid);
|
||||||
var objectId = pair?.PlayerCharacterId ?? 0;
|
var objectId = pair?.PlayerCharacterId ?? 0;
|
||||||
if (pair == null)
|
var pairName = pair?.PlayerName ?? entry.User.AliasOrUID ?? string.Empty;
|
||||||
{
|
var pairIdent = pair?.Ident ?? string.Empty;
|
||||||
_logger.LogInformation("TypingIndicator: no pair found for {uid}, attempting fallback", uid);
|
var isPartyMember = IsPartyMember(objectId, pairName);
|
||||||
}
|
var isRelevantMember = IsPlayerRelevant(pair, isPartyMember);
|
||||||
|
|
||||||
if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId))
|
if (objectId != uint.MaxValue && objectId != 0 && TryDrawNameplateBubble(drawList, iconWrap, objectId))
|
||||||
{
|
{
|
||||||
@@ -209,20 +220,28 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pairName = pair?.PlayerName ?? entry.User.AliasOrUID ?? string.Empty;
|
var hasWorldPosition = TryResolveWorldPosition(pair, entry.User, objectId, out var worldPos);
|
||||||
var pairIdent = pair?.Ident ?? string.Empty;
|
var isNearby = hasWorldPosition && IsWithinRelevantDistance(worldPos);
|
||||||
|
|
||||||
_logger.LogInformation("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
|
if (!isRelevantMember && !isNearby)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (pair == null)
|
||||||
|
{
|
||||||
|
_logger.LogTrace("TypingIndicator: no pair found for {uid}, attempting fallback", uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogTrace("TypingIndicator: fallback draw for {uid} (objectId={objectId}, name={name}, ident={ident})",
|
||||||
uid, objectId, pairName, pairIdent);
|
uid, objectId, pairName, pairIdent);
|
||||||
|
|
||||||
if (TryResolveWorldPosition(pair, entry.User, objectId, out var worldPos))
|
if (hasWorldPosition)
|
||||||
{
|
{
|
||||||
DrawWorldFallbackIcon(drawList, iconWrap, worldPos);
|
DrawWorldFallbackIcon(drawList, iconWrap, worldPos);
|
||||||
_logger.LogInformation("TypingIndicator: fallback world draw for {uid} at {pos}", uid, worldPos);
|
_logger.LogTrace("TypingIndicator: fallback world draw for {uid} at {pos}", uid, worldPos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("TypingIndicator: could not resolve position for {uid}", uid);
|
_logger.LogTrace("TypingIndicator: could not resolve position for {uid}", uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,6 +483,48 @@ public sealed class TypingIndicatorOverlay : WindowMediatorSubscriberBase
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsPartyMember(uint objectId, string? playerName)
|
||||||
|
{
|
||||||
|
if (objectId != 0 && objectId != uint.MaxValue && GetPartyIndexForObjectId(objectId) >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(playerName) && GetPartyIndexForName(playerName) >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPlayerRelevant(Pair? pair, bool isPartyMember)
|
||||||
|
{
|
||||||
|
if (isPartyMember)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (pair?.UserPair != null)
|
||||||
|
{
|
||||||
|
var userPair = pair.UserPair;
|
||||||
|
if (userPair.OtherPermissions.IsPaired() || userPair.OwnPermissions.IsPaired())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pair?.GroupPair != null && pair.GroupPair.Any(g =>
|
||||||
|
!g.Value.GroupUserPermissions.IsPaused() &&
|
||||||
|
!g.Key.GroupUserPermissions.IsPaused()))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsWithinRelevantDistance(Vector3 position)
|
||||||
|
{
|
||||||
|
if (_clientState.LocalPlayer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var distance = Vector3.Distance(_clientState.LocalPlayer.Position, position);
|
||||||
|
return distance <= 40f;
|
||||||
|
}
|
||||||
|
|
||||||
private static unsafe uint GetEntityId(nint address)
|
private static unsafe uint GetEntityId(nint address)
|
||||||
{
|
{
|
||||||
if (address == nint.Zero) return 0;
|
if (address == nint.Zero) return 0;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using MareSynchronos.WebAPI.SignalR;
|
using MareSynchronos.WebAPI.SignalR;
|
||||||
using MareSynchronos.Services.AutoDetect;
|
using MareSynchronos.Services.AutoDetect;
|
||||||
@@ -65,15 +66,21 @@ public class DiscoveryApiClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SendRequestAsync(string endpoint, string token, string? displayName, CancellationToken ct)
|
public async Task<bool> SendRequestAsync(string endpoint, string? token, string? targetUid, string? displayName, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(token) && string.IsNullOrEmpty(targetUid))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Discovery request aborted: no token or targetUid provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
var jwt = await _tokenProvider.GetOrUpdateToken(ct).ConfigureAwait(false);
|
||||||
if (string.IsNullOrEmpty(jwt)) return false;
|
if (string.IsNullOrEmpty(jwt)) return false;
|
||||||
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
|
||||||
var body = JsonSerializer.Serialize(new { token, displayName });
|
var body = JsonSerializer.Serialize(new RequestPayload(token, targetUid, displayName));
|
||||||
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
req.Content = new StringContent(body, Encoding.UTF8, "application/json");
|
||||||
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
var resp = await _httpClient.SendAsync(req, ct).ConfigureAwait(false);
|
||||||
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
@@ -82,7 +89,7 @@ public class DiscoveryApiClient
|
|||||||
if (string.IsNullOrEmpty(jwt2)) return false;
|
if (string.IsNullOrEmpty(jwt2)) return false;
|
||||||
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
using var req2 = new HttpRequestMessage(HttpMethod.Post, endpoint);
|
||||||
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
req2.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt2);
|
||||||
var body2 = JsonSerializer.Serialize(new { token, displayName });
|
var body2 = JsonSerializer.Serialize(new RequestPayload(token, targetUid, displayName));
|
||||||
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
req2.Content = new StringContent(body2, Encoding.UTF8, "application/json");
|
||||||
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
resp = await _httpClient.SendAsync(req2, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -102,6 +109,14 @@ public class DiscoveryApiClient
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed record RequestPayload(
|
||||||
|
[property: JsonPropertyName("token"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
string? Token,
|
||||||
|
[property: JsonPropertyName("targetUid"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
string? TargetUid,
|
||||||
|
[property: JsonPropertyName("displayName"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
string? DisplayName);
|
||||||
|
|
||||||
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct, bool allowRequests = true)
|
public async Task<bool> PublishAsync(string endpoint, IEnumerable<string> hashes, string? displayName, CancellationToken ct, bool allowRequests = true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using MareSynchronos.API.Data;
|
|||||||
using MareSynchronos.API.Dto.Files;
|
using MareSynchronos.API.Dto.Files;
|
||||||
using MareSynchronos.API.Routes;
|
using MareSynchronos.API.Routes;
|
||||||
using MareSynchronos.FileCache;
|
using MareSynchronos.FileCache;
|
||||||
|
using MareSynchronos.MareConfiguration;
|
||||||
using MareSynchronos.PlayerData.Handlers;
|
using MareSynchronos.PlayerData.Handlers;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
@@ -20,17 +21,22 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly Dictionary<string, FileDownloadStatus> _downloadStatus;
|
private readonly Dictionary<string, FileDownloadStatus> _downloadStatus;
|
||||||
private readonly FileCompactor _fileCompactor;
|
private readonly FileCompactor _fileCompactor;
|
||||||
private readonly FileCacheManager _fileDbManager;
|
private readonly FileCacheManager _fileDbManager;
|
||||||
|
private readonly MareConfigService _mareConfigService;
|
||||||
private readonly FileTransferOrchestrator _orchestrator;
|
private readonly FileTransferOrchestrator _orchestrator;
|
||||||
private readonly List<ThrottledStream> _activeDownloadStreams;
|
private readonly List<ThrottledStream> _activeDownloadStreams;
|
||||||
|
private readonly object _queueLock = new();
|
||||||
|
private SemaphoreSlim? _downloadQueueSemaphore;
|
||||||
|
private int _downloadQueueCapacity = -1;
|
||||||
|
|
||||||
public FileDownloadManager(ILogger<FileDownloadManager> logger, MareMediator mediator,
|
public FileDownloadManager(ILogger<FileDownloadManager> logger, MareMediator mediator,
|
||||||
FileTransferOrchestrator orchestrator,
|
FileTransferOrchestrator orchestrator,
|
||||||
FileCacheManager fileCacheManager, FileCompactor fileCompactor) : base(logger, mediator)
|
FileCacheManager fileCacheManager, FileCompactor fileCompactor, MareConfigService mareConfigService) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_downloadStatus = new Dictionary<string, FileDownloadStatus>(StringComparer.Ordinal);
|
_downloadStatus = new Dictionary<string, FileDownloadStatus>(StringComparer.Ordinal);
|
||||||
_orchestrator = orchestrator;
|
_orchestrator = orchestrator;
|
||||||
_fileDbManager = fileCacheManager;
|
_fileDbManager = fileCacheManager;
|
||||||
_fileCompactor = fileCompactor;
|
_fileCompactor = fileCompactor;
|
||||||
|
_mareConfigService = mareConfigService;
|
||||||
_activeDownloadStreams = [];
|
_activeDownloadStreams = [];
|
||||||
|
|
||||||
Mediator.Subscribe<DownloadLimitChangedMessage>(this, (msg) =>
|
Mediator.Subscribe<DownloadLimitChangedMessage>(this, (msg) =>
|
||||||
@@ -59,6 +65,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
public async Task DownloadFiles(GameObjectHandler gameObject, List<FileReplacementData> fileReplacementDto, CancellationToken ct)
|
public async Task DownloadFiles(GameObjectHandler gameObject, List<FileReplacementData> fileReplacementDto, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
SemaphoreSlim? queueSemaphore = null;
|
||||||
|
if (_mareConfigService.Current.EnableDownloadQueue)
|
||||||
|
{
|
||||||
|
queueSemaphore = GetQueueSemaphore();
|
||||||
|
Logger.LogTrace("Queueing download for {name}. Currently queued: {queued}", gameObject.Name, queueSemaphore.CurrentCount);
|
||||||
|
await queueSemaphore.WaitAsync(ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles)));
|
Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles)));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -70,6 +84,11 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
if (queueSemaphore != null)
|
||||||
|
{
|
||||||
|
queueSemaphore.Release();
|
||||||
|
}
|
||||||
|
|
||||||
Mediator.Publish(new DownloadFinishedMessage(gameObject));
|
Mediator.Publish(new DownloadFinishedMessage(gameObject));
|
||||||
Mediator.Publish(new ResumeScanMessage(nameof(DownloadFiles)));
|
Mediator.Publish(new ResumeScanMessage(nameof(DownloadFiles)));
|
||||||
}
|
}
|
||||||
@@ -132,6 +151,22 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
return (string.Join("", hashName), long.Parse(string.Join("", fileLength)));
|
return (string.Join("", hashName), long.Parse(string.Join("", fileLength)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SemaphoreSlim GetQueueSemaphore()
|
||||||
|
{
|
||||||
|
var desiredCapacity = Math.Clamp(_mareConfigService.Current.ParallelDownloads, 1, 10);
|
||||||
|
|
||||||
|
lock (_queueLock)
|
||||||
|
{
|
||||||
|
if (_downloadQueueSemaphore == null || _downloadQueueCapacity != desiredCapacity)
|
||||||
|
{
|
||||||
|
_downloadQueueSemaphore = new SemaphoreSlim(desiredCapacity, desiredCapacity);
|
||||||
|
_downloadQueueCapacity = desiredCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _downloadQueueSemaphore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DownloadAndMungeFileHttpClient(string downloadGroup, Guid requestId, List<DownloadFileTransfer> fileTransfer, string tempPath, IProgress<long> progress, CancellationToken ct)
|
private async Task DownloadAndMungeFileHttpClient(string downloadGroup, Guid requestId, List<DownloadFileTransfer> fileTransfer, string tempPath, IProgress<long> progress, CancellationToken ct)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("GUID {requestId} on server {uri} for files {files}", requestId, fileTransfer[0].DownloadUri, string.Join(", ", fileTransfer.Select(c => c.Hash).ToList()));
|
Logger.LogDebug("GUID {requestId} on server {uri} for files {files}", requestId, fileTransfer[0].DownloadUri, string.Join(", ", fileTransfer.Select(c => c.Hash).ToList()));
|
||||||
|
|||||||
Reference in New Issue
Block a user