From a70968d30c97285dc52a90fc43d519d0af18e6a5 Mon Sep 17 00:00:00 2001 From: SirConstance Date: Thu, 11 Sep 2025 11:30:09 +0200 Subject: [PATCH] =?UTF-8?q?Nearby=20(AutoDetect)=20Phase=201=20=E2=80=94?= =?UTF-8?q?=20settings,=20window,=20compact=20section;=20UmbraSync=20theme?= =?UTF-8?q?;=20minor=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../Configurations/MareConfig.cs | 4 + MareSynchronos/Plugin.cs | 1 + MareSynchronos/UI/AutoDetectUi.cs | 106 ++++++++++++++++++ MareSynchronos/UI/CompactUI.cs | 44 +++++++- MareSynchronos/UI/DownloadUi.cs | 4 +- MareSynchronos/UI/SettingsUi.cs | 34 ++++++ .../WebAPI/Files/FileDownloadManager.cs | 4 +- .../WebAPI/SignalR/ApiController.cs | 6 +- 9 files changed, 193 insertions(+), 13 deletions(-) create mode 100644 MareSynchronos/UI/AutoDetectUi.cs diff --git a/.gitignore b/.gitignore index 740eafe..0afd316 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ *.userosscache *.sln.docstates .DS_Store +MareSynchronos/.DS_Store +*.zip +UmbraServer_extracted/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs index 6047545..9c0d2fd 100644 --- a/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/MareConfig.cs @@ -59,6 +59,10 @@ public class MareConfig : IMareConfiguration public bool ShowUploading { get; set; } = true; public bool ShowUploadingBigText { get; set; } = true; public bool ShowVisibleUsersSeparately { get; set; } = true; + public bool EnableAutoDetectDiscovery { get; set; } = false; + public bool AllowAutoDetectPairRequests { get; set; } = false; + public int AutoDetectMaxDistanceMeters { get; set; } = 40; + public int AutoDetectMuteMinutes { get; set; } = 5; public int TimeSpanBetweenScansInSeconds { get; set; } = 30; public int TransferBarsHeight { get; set; } = 12; public bool TransferBarsShowText { get; set; } = true; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 799c9fd..6b893cf 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -175,6 +175,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); + collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); diff --git a/MareSynchronos/UI/AutoDetectUi.cs b/MareSynchronos/UI/AutoDetectUi.cs new file mode 100644 index 0000000..055e021 --- /dev/null +++ b/MareSynchronos/UI/AutoDetectUi.cs @@ -0,0 +1,106 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Colors; +using Dalamud.Game.ClientState.Objects.SubKinds; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using Dalamud.Plugin.Services; +using MareSynchronos.MareConfiguration; +using MareSynchronos.Services; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using System.Numerics; + +namespace MareSynchronos.UI; + +public class AutoDetectUi : WindowMediatorSubscriberBase +{ + private readonly MareConfigService _configService; + private readonly DalamudUtilService _dalamud; + private readonly IObjectTable _objectTable; + + public AutoDetectUi(ILogger logger, MareMediator mediator, + MareConfigService configService, DalamudUtilService dalamudUtilService, IObjectTable objectTable, + PerformanceCollectorService performanceCollectorService) + : base(logger, mediator, "Umbra Nearby", performanceCollectorService) + { + _configService = configService; + _dalamud = dalamudUtilService; + _objectTable = objectTable; + + Flags |= ImGuiWindowFlags.NoScrollbar; + SizeConstraints = new WindowSizeConstraints() + { + MinimumSize = new Vector2(350, 220), + MaximumSize = new Vector2(600, 600), + }; + } + + public override bool DrawConditions() + { + // Visible when explicitly opened; allow drawing even if discovery is disabled to show hint + return true; + } + + protected override void DrawInternal() + { + using var _ = ImRaii.PushId("autosync-ui"); + + if (!_configService.Current.EnableAutoDetectDiscovery) + { + UiSharedService.ColorTextWrapped("Nearby detection is disabled. Enable it in Settings to start detecting nearby Umbra users.", ImGuiColors.DalamudYellow); + ImGuiHelpers.ScaledDummy(6); + } + + int maxDist = Math.Clamp(_configService.Current.AutoDetectMaxDistanceMeters, 5, 100); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Max distance (m)"); + ImGui.SameLine(); + ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale); + if (ImGui.SliderInt("##autodetect-dist", ref maxDist, 5, 100)) + { + _configService.Current.AutoDetectMaxDistanceMeters = maxDist; + _configService.Save(); + } + + ImGuiHelpers.ScaledDummy(6); + + // Table header + if (ImGui.BeginTable("autosync-nearby", 3, ImGuiTableFlags.SizingStretchProp)) + { + ImGui.TableSetupColumn("Name"); + ImGui.TableSetupColumn("World"); + ImGui.TableSetupColumn("Distance"); + ImGui.TableHeadersRow(); + + var local = _dalamud.GetPlayerCharacter(); + var localPos = local?.Position ?? Vector3.Zero; + + for (int i = 0; i < 200; i += 2) + { + var obj = _objectTable[i]; + if (obj == null || obj.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; + if (local != null && obj.Address == local.Address) continue; + + float dist = local == null ? float.NaN : Vector3.Distance(localPos, obj.Position); + if (!float.IsNaN(dist) && dist > maxDist) continue; + + string name = obj.Name.ToString(); + ushort worldId = 0; + if (obj is IPlayerCharacter pc) + { + worldId = (ushort)pc.HomeWorld.RowId; + } + string world = worldId == 0 ? "-" : (_dalamud.WorldData.Value.TryGetValue(worldId, out var w) ? w : worldId.ToString()); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted(name); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(world); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(float.IsNaN(dist) ? "-" : $"{dist:0.0} m"); + } + + ImGui.EndTable(); + } + } +} \ No newline at end of file diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 69fa74f..6a6d190 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -56,11 +56,12 @@ public class CompactUi : WindowMediatorSubscriberBase private bool _showModalForUserAddition; private bool _showSyncShells; private bool _wasOpen; + private bool _nearbyOpen = true; public CompactUi(ILogger logger, UiSharedService uiShared, MareConfigService configService, ApiController apiController, PairManager pairManager, ChatService chatService, ServerConfigurationManager serverManager, MareMediator mediator, FileUploadManager fileTransferManager, UidDisplayHandler uidDisplayHandler, CharaDataManager charaDataManager, PerformanceCollectorService performanceCollectorService) - : base(logger, mediator, "###UmbraSyncSyncMainUI", performanceCollectorService) + : base(logger, mediator, "###UmbraSyncMainUI", performanceCollectorService) { _uiSharedService = uiShared; _configService = configService; @@ -80,11 +81,11 @@ public class CompactUi : WindowMediatorSubscriberBase #if DEBUG string dev = "Dev Build"; var ver = Assembly.GetExecutingAssembly().GetName().Version!; - WindowName = $"UmbraSync {dev} ({ver.Major}.{ver.Minor}.{ver.Build})###UmbraSyncSyncMainUIDev"; + WindowName = $"UmbraSync {dev} ({ver.Major}.{ver.Minor}.{ver.Build})###UmbraSyncMainUIDev"; Toggle(); #else var ver = Assembly.GetExecutingAssembly().GetName().Version!; - WindowName = "UmbraSync " + ver.Major + "." + ver.Minor + "." + ver.Build + "###UmbraSyncSyncMainUI"; + WindowName = "UmbraSync " + ver.Major + "." + ver.Minor + "." + ver.Build + "###UmbracSyncMainUI"; #endif Mediator.Subscribe(this, (_) => IsOpen = true); Mediator.Subscribe(this, (_) => IsOpen = false); @@ -104,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase protected override void DrawInternal() { - UiSharedService.AccentColor = new Vector4(0.2f, 0.6f, 1f, 1f); // custom blue + UiSharedService.AccentColor = new Vector4(0.63f, 0.25f, 1f, 1f); ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y); WindowContentWidth = UiSharedService.GetWindowContentRegionWidth(); if (!_apiController.IsCurrentVersion) @@ -176,6 +177,14 @@ public class CompactUi : WindowMediatorSubscriberBase } ImGui.Separator(); using (ImRaii.PushId("transfers")) DrawTransfers(); + using (ImRaii.PushId("autosync")) + { + ImGui.SameLine(); + if (_uiSharedService.IconTextButton(FontAwesomeIcon.UserPlus, "Nearby", (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2)) + { + Mediator.Publish(new UiToggleMessage(typeof(AutoDetectUi))); + } + } TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight; using (ImRaii.PushId("group-user-popup")) _selectPairsForGroupUi.Draw(_pairManager.DirectPairs); using (ImRaii.PushId("grouping-popup")) _selectGroupForPairUi.Draw(); @@ -367,6 +376,29 @@ public class CompactUi : WindowMediatorSubscriberBase _pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers); + // Always show a Nearby group when detection is enabled, even if empty + if (_configService.Current.EnableAutoDetectDiscovery) + { + ImGui.Separator(); + using (ImRaii.PushId("group-Nearby")) + { + var icon = _nearbyOpen ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight; + _uiSharedService.IconText(icon); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; + ImGui.SameLine(); + ImGui.TextUnformatted("Nearby (0 Players)"); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) _nearbyOpen = !_nearbyOpen; + + if (_nearbyOpen) + { + ImGui.Indent(); + UiSharedService.ColorTextWrapped("No nearby players detected.", ImGuiColors.DalamudGrey3); + ImGui.Unindent(); + ImGui.Separator(); + } + } + } + ImGui.EndChild(); } @@ -384,7 +416,7 @@ public class CompactUi : WindowMediatorSubscriberBase { ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiSharedService.GetWindowContentRegionWidth()) / 2 - (userSize.X + textSize.X) / 2 - ImGui.GetStyle().ItemSpacing.X / 2); if (!printShard) ImGui.AlignTextToFramePadding(); - ImGui.TextColored(ImGuiColors.ParsedBlue, userCount); + ImGui.TextColored(UiSharedService.AccentColor, userCount); ImGui.SameLine(); if (!printShard) ImGui.AlignTextToFramePadding(); ImGui.TextUnformatted("Users Online"); @@ -592,7 +624,7 @@ public class CompactUi : WindowMediatorSubscriberBase { ServerState.Connecting => ImGuiColors.DalamudYellow, ServerState.Reconnecting => ImGuiColors.DalamudRed, - ServerState.Connected => new Vector4(0.2f, 0.6f, 1f, 1f), // custom blue + ServerState.Connected => new Vector4(0.63f, 0.25f, 1f, 1f), // custom violet ServerState.Disconnected => ImGuiColors.DalamudYellow, ServerState.Disconnecting => ImGuiColors.DalamudYellow, ServerState.Unauthorized => ImGuiColors.DalamudRed, diff --git a/MareSynchronos/UI/DownloadUi.cs b/MareSynchronos/UI/DownloadUi.cs index fe60cf2..c4d6574 100644 --- a/MareSynchronos/UI/DownloadUi.cs +++ b/MareSynchronos/UI/DownloadUi.cs @@ -163,13 +163,13 @@ public class DownloadUi : WindowMediatorSubscriberBase UiSharedService.Color(0, 0, 0, transparency), 1); drawList.AddRectFilled(dlBarStart with { X = dlBarStart.X - dlBarBorder, Y = dlBarStart.Y - dlBarBorder }, dlBarEnd with { X = dlBarEnd.X + dlBarBorder, Y = dlBarEnd.Y + dlBarBorder }, - UiSharedService.Color(220, 220, 255, transparency), 1); + UiSharedService.Color(230, 200, 255, transparency), 1); drawList.AddRectFilled(dlBarStart, dlBarEnd, UiSharedService.Color(0, 0, 0, transparency), 1); var dlProgressPercent = transferredBytes / (double)totalBytes; drawList.AddRectFilled(dlBarStart, dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) }, - UiSharedService.Color(100, 100, 255, transparency), 1); + UiSharedService.Color(160, 64, 255, transparency), 1); if (_configService.Current.TransferBarsShowText) { diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 3ffc48b..36fdf14 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -211,6 +211,40 @@ public class SettingsUi : WindowMediatorSubscriberBase _configService.Save(); } + ImGui.Separator(); + _uiShared.BigText("Nearby"); + bool enableDiscovery = _configService.Current.EnableAutoDetectDiscovery; + if (ImGui.Checkbox("Enable Nearby detection (beta)", ref enableDiscovery)) + { + _configService.Current.EnableAutoDetectDiscovery = enableDiscovery; + _configService.Save(); + } + bool allowRequests = _configService.Current.AllowAutoDetectPairRequests; + if (ImGui.Checkbox("Allow pair requests", ref allowRequests)) + { + _configService.Current.AllowAutoDetectPairRequests = allowRequests; + _configService.Save(); + } + if (enableDiscovery) + { + ImGui.Indent(); + int maxMeters = _configService.Current.AutoDetectMaxDistanceMeters; + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + if (ImGui.SliderInt("Max distance (meters)", ref maxMeters, 5, 100)) + { + _configService.Current.AutoDetectMaxDistanceMeters = maxMeters; + _configService.Save(); + } + int muteMin = _configService.Current.AutoDetectMuteMinutes; + ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); + if (ImGui.SliderInt("Default mute duration (minutes)", ref muteMin, 1, 120)) + { + _configService.Current.AutoDetectMuteMinutes = muteMin; + _configService.Save(); + } + ImGui.Unindent(); + } + ImGui.Separator(); _uiShared.BigText("Transfer UI"); diff --git a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs index 69439fa..3020064 100644 --- a/MareSynchronos/WebAPI/Files/FileDownloadManager.cs +++ b/MareSynchronos/WebAPI/Files/FileDownloadManager.cs @@ -49,7 +49,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase public List ForbiddenTransfers => _orchestrator.ForbiddenTransfers; - public bool IsDownloading => !CurrentDownloads.Any(); + public bool IsDownloading => CurrentDownloads.Any(); public void ClearDownload() { @@ -507,4 +507,4 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase _orchestrator.ClearDownloadRequest(requestId); } } -} \ No newline at end of file +} diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.cs b/MareSynchronos/WebAPI/SignalR/ApiController.cs index 6c8dc88..15f478d 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.cs @@ -222,7 +222,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM #endif } - await LoadIninitialPairs().ConfigureAwait(false); + await LoadInitialPairs().ConfigureAwait(false); await LoadOnlinePairs().ConfigureAwait(false); } catch (OperationCanceledException) @@ -375,7 +375,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM _initialized = true; } - private async Task LoadIninitialPairs() + private async Task LoadInitialPairs() { foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false)) { @@ -435,7 +435,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM return; } ServerState = ServerState.Connected; - await LoadIninitialPairs().ConfigureAwait(false); + await LoadInitialPairs().ConfigureAwait(false); await LoadOnlinePairs().ConfigureAwait(false); Mediator.Publish(new ConnectedMessage(_connectionDto)); }