From eeda5aeb66943d3075c04882dd35ec5e4bb3992b Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Sun, 28 Dec 2025 10:54:01 +0800 Subject: [PATCH 01/10] Revert "Location Sharing" This reverts commit 70745613e13cb27dd7e90d30a811bd8b1f28d965. --- LightlessAPI | 2 +- LightlessSync/Plugin.cs | 1 - LightlessSync/Services/DalamudUtilService.cs | 13 ++-- .../Services/LocationShareService.cs | 78 ------------------- LightlessSync/Services/Mediator/Messages.cs | 2 - .../UI/Components/DrawFolderGroup.cs | 8 -- LightlessSync/UI/Components/DrawUserPair.cs | 75 ------------------ LightlessSync/UI/DrawEntityFactory.cs | 4 - LightlessSync/UI/PermissionWindowUI.cs | 20 ----- .../SignalR/ApIController.Functions.Users.cs | 12 --- .../ApiController.Functions.Callbacks.cs | 13 ---- LightlessSync/WebAPI/SignalR/ApiController.cs | 2 - 12 files changed, 6 insertions(+), 224 deletions(-) delete mode 100644 LightlessSync/Services/LocationShareService.cs diff --git a/LightlessAPI b/LightlessAPI index fdd492a..5656600 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit fdd492a8f478949d910ed0efd3e4a3ca3312ed9c +Subproject commit 56566003e0e93bba05dcef49fd3ce23c6a204d81 diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 4131e40..cd57c34 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -137,7 +137,6 @@ public sealed class Plugin : IDalamudPlugin services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(sp => new TextureMetadataHelper(sp.GetRequiredService>(), gameData)); diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 36ce98b..ef6fe7a 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -1,13 +1,11 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Game.Text; using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Control; -using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using LightlessSync.API.Dto.CharaData; @@ -27,7 +25,6 @@ using System.Runtime.CompilerServices; using System.Text; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; -using Map = Lumina.Excel.Sheets.Map; using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags; namespace LightlessSync.Services; @@ -591,7 +588,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber var location = new LocationInfo(); location.ServerId = _playerState.CurrentWorld.RowId; - location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; + //location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first location.TerritoryId = _clientState.TerritoryType; location.MapId = _clientState.MapId; if (houseMan != null) @@ -645,10 +642,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber str += $" - {MapData.Value[(ushort)location.MapId].MapName}"; } - if (location.InstanceId is not 0) - { - str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString(); - } + // if (location.InstanceId is not 0) + // { + // str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString(); + // } if (location.WardId is not 0) { diff --git a/LightlessSync/Services/LocationShareService.cs b/LightlessSync/Services/LocationShareService.cs deleted file mode 100644 index 0d3f5dc..0000000 --- a/LightlessSync/Services/LocationShareService.cs +++ /dev/null @@ -1,78 +0,0 @@ -using LightlessSync.API.Data; -using LightlessSync.API.Dto.CharaData; -using LightlessSync.API.Dto.User; -using LightlessSync.Services.Mediator; -using LightlessSync.WebAPI; -using Microsoft.Extensions.Logging; - -namespace LightlessSync.Services -{ - public class LocationShareService : DisposableMediatorSubscriberBase - { - private readonly DalamudUtilService _dalamudUtilService; - private readonly ApiController _apiController; - private Dictionary _locations = []; - - public LocationShareService(ILogger logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator) - { - _dalamudUtilService = dalamudUtilService; - _apiController = apiController; - - - Mediator.Subscribe(this, (msg) => _locations.Clear()); - Mediator.Subscribe(this, (msg) => - { - _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData())); - _ = RequestAllLocation(); - } ); - Mediator.Subscribe(this, UpdateLocationList); - Mediator.Subscribe(this, - msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData()))); - } - - private void UpdateLocationList(LocationMessage msg) - { - if (_locations.ContainsKey(msg.Uid) && msg.LocationInfo.ServerId is 0) - { - _locations.Remove(msg.Uid); - return; - } - - if ( msg.LocationInfo.ServerId is not 0 && !_locations.TryAdd(msg.Uid, msg.LocationInfo)) - { - _locations[msg.Uid] = msg.LocationInfo; - } - } - - private async Task RequestAllLocation() - { - try - { - var data = await _apiController.RequestAllLocationInfo().ConfigureAwait(false); - _locations = data.ToDictionary(x => x.user.UID, x => x.location, StringComparer.Ordinal); - } - catch (Exception e) - { - Logger.LogError(e,"RequestAllLocation error : "); - throw; - } - } - - public string GetUserLocation(string uid) - { - try - { - if (_locations.TryGetValue(uid, out var location)) - { - return _dalamudUtilService.LocationToString(location); - } - return String.Empty; - } - catch (Exception e) - { - Logger.LogError(e,"GetUserLocation error : "); - throw; - } - } - } -} \ No newline at end of file diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index be0c06b..758b9f5 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -135,7 +135,5 @@ public record ChatChannelsUpdated : MessageBase; public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase; public record GroupCollectionChangedMessage : MessageBase; public record OpenUserProfileMessage(UserData User) : MessageBase; -public record LocationMessage(string Uid, LocationInfo LocationInfo) : MessageBase; - #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/LightlessSync/UI/Components/DrawFolderGroup.cs b/LightlessSync/UI/Components/DrawFolderGroup.cs index 277115a..c39326c 100644 --- a/LightlessSync/UI/Components/DrawFolderGroup.cs +++ b/LightlessSync/UI/Components/DrawFolderGroup.cs @@ -131,7 +131,6 @@ public class DrawFolderGroup : DrawFolderBase bool disableSounds = perm.IsDisableSounds(); bool disableAnims = perm.IsDisableAnimations(); bool disableVfx = perm.IsDisableVFX(); - bool shareLocation = perm.IsSharingLocation(); if ((_groupFullInfoDto.GroupPermissions.IsPreferDisableAnimations() != disableAnims || _groupFullInfoDto.GroupPermissions.IsPreferDisableSounds() != disableSounds @@ -165,13 +164,6 @@ public class DrawFolderGroup : DrawFolderBase _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); ImGui.CloseCurrentPopup(); } - - if (_uiSharedService.IconTextButton(!shareLocation ? FontAwesomeIcon.Globe : FontAwesomeIcon.StopCircle, !shareLocation ? "Share your location to all users in Syncshell" : "STOP Share your location to all users in Syncshell", menuWidth, true)) - { - perm.SetShareLocation(!shareLocation); - _ = _apiController.GroupChangeIndividualPermissionState(new(_groupFullInfoDto.Group, new(_apiController.UID), perm)); - ImGui.CloseCurrentPopup(); - } if (IsModerator || IsOwner) { diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index 5f3d300..8e03ae4 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -37,7 +37,6 @@ public class DrawUserPair private readonly UiSharedService _uiSharedService; private readonly PlayerPerformanceConfigService _performanceConfigService; private readonly LightlessConfigService _configService; - private readonly LocationShareService _locationShareService; private readonly CharaDataManager _charaDataManager; private readonly PairLedger _pairLedger; private float _menuWidth = -1; @@ -58,7 +57,6 @@ public class DrawUserPair UiSharedService uiSharedService, PlayerPerformanceConfigService performanceConfigService, LightlessConfigService configService, - LocationShareService locationShareService, CharaDataManager charaDataManager, PairLedger pairLedger) { @@ -76,7 +74,6 @@ public class DrawUserPair _uiSharedService = uiSharedService; _performanceConfigService = performanceConfigService; _configService = configService; - _locationShareService = locationShareService; _charaDataManager = charaDataManager; _pairLedger = pairLedger; } @@ -219,17 +216,6 @@ public class DrawUserPair _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions)); } UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty)); - - var isShareingLocation = _pair.UserPair!.OwnPermissions.IsSharingLocation(); - string isShareingLocationText = isShareingLocation ? "Disable location sharing" : "Enable location sharing"; - var isShareingLocationIcon = isShareingLocation ? FontAwesomeIcon.StopCircle : FontAwesomeIcon.Globe; - if (_uiSharedService.IconTextButton(isShareingLocationIcon, isShareingLocationText, _menuWidth, true)) - { - var permissions = _pair.UserPair.OwnPermissions; - permissions.SetShareLocation(!isShareingLocation); - _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions)); - } - UiSharedService.AttachToolTip("Changes location sharing permissions with this user." + (individual ? individualText : string.Empty)); } private void DrawIndividualMenu() @@ -581,7 +567,6 @@ public class DrawUserPair : UiSharedService.TooltipSeparator + "Hold CTRL to enable preferred permissions while pausing." + Environment.NewLine + "This will leave this pair paused even if unpausing syncshells including this pair.")) : "Resume pairing with " + _pair.UserData.AliasOrUID); - //Location sharing if (_pair.IsPaired) { var individualSoundsDisabled = (_pair.UserPair?.OwnPermissions.IsDisableSounds() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableSounds() ?? false); @@ -589,66 +574,6 @@ public class DrawUserPair var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false); var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky(); var individualIcon = individualIsSticky ? FontAwesomeIcon.ArrowCircleUp : FontAwesomeIcon.InfoCircle; - - - var shareLocationIcon = FontAwesomeIcon.Globe; - var shareLocation = _pair.UserPair?.OwnPermissions.IsSharingLocation() ?? false; - var shareLocationOther = _pair.UserPair?.OtherPermissions.IsSharingLocation() ?? false; - var shareColor = shareLocation switch - { - true when shareLocationOther => UIColors.Get("LightlessGreen"), - false when shareLocationOther => UIColors.Get("LightlessBlue"), - _ => UIColors.Get("LightlessYellow"), - }; - - if (shareLocation || shareLocationOther) - { - currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX); - ImGui.SameLine(currentRightSide); - using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationOther)) - _uiSharedService.IconText(shareLocationIcon); - - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - if (shareLocationOther) - { - var location = _locationShareService.GetUserLocation(_pair.UserPair!.User.UID); - if (_pair.IsOnline) - { - if (!string.IsNullOrEmpty(location)) - { - _uiSharedService.IconText(FontAwesomeIcon.LocationArrow); - ImGui.SameLine(); - ImGui.TextUnformatted(location); - } - else - { - ImGui.TextUnformatted("Location info not updated, reconnect or waiting for zone-changing."); - } - } - else - { - ImGui.TextUnformatted("User not onlineㄟ( ▔, ▔ )ㄏ"); - } - } - else - { - ImGui.TextUnformatted("NOT Sharing location with you.(⊙x⊙;)"); - } - ImGui.Separator(); - - if (shareLocation) - { - ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o"); - } - else - { - ImGui.TextUnformatted("NOT sharing your location.(´。_。`)"); - } - ImGui.EndTooltip(); - } - } if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky) { diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index e810a29..3c71f5c 100644 --- a/LightlessSync/UI/DrawEntityFactory.cs +++ b/LightlessSync/UI/DrawEntityFactory.cs @@ -28,7 +28,6 @@ public class DrawEntityFactory private readonly ServerConfigurationManager _serverConfigurationManager; private readonly LightlessConfigService _configService; private readonly UiSharedService _uiSharedService; - private readonly LocationShareService _locationShareService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; private readonly CharaDataManager _charaDataManager; private readonly SelectTagForPairUi _selectTagForPairUi; @@ -53,7 +52,6 @@ public class DrawEntityFactory ServerConfigurationManager serverConfigurationManager, LightlessConfigService configService, UiSharedService uiSharedService, - LocationShareService locationShareService, PlayerPerformanceConfigService playerPerformanceConfigService, CharaDataManager charaDataManager, SelectTagForSyncshellUi selectTagForSyncshellUi, @@ -73,7 +71,6 @@ public class DrawEntityFactory _serverConfigurationManager = serverConfigurationManager; _configService = configService; _uiSharedService = uiSharedService; - _locationShareService = locationShareService; _playerPerformanceConfigService = playerPerformanceConfigService; _charaDataManager = charaDataManager; _selectTagForSyncshellUi = selectTagForSyncshellUi; @@ -165,7 +162,6 @@ public class DrawEntityFactory _uiSharedService, _playerPerformanceConfigService, _configService, - _locationShareService, _charaDataManager, _pairLedger); } diff --git a/LightlessSync/UI/PermissionWindowUI.cs b/LightlessSync/UI/PermissionWindowUI.cs index 9d88547..5dee098 100644 --- a/LightlessSync/UI/PermissionWindowUI.cs +++ b/LightlessSync/UI/PermissionWindowUI.cs @@ -43,7 +43,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase var disableSounds = _ownPermissions.IsDisableSounds(); var disableAnimations = _ownPermissions.IsDisableAnimations(); var disableVfx = _ownPermissions.IsDisableVFX(); - var shareLocation = _ownPermissions.IsSharingLocation(); var style = ImGui.GetStyle(); var indentSize = ImGui.GetFrameHeight() + style.ItemSpacing.X; @@ -71,7 +70,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase var otherDisableSounds = otherPerms.IsDisableSounds(); var otherDisableAnimations = otherPerms.IsDisableAnimations(); var otherDisableVFX = otherPerms.IsDisableVFX(); - var otherShareLocation = otherPerms.IsSharingLocation(); using (ImRaii.PushIndent(indentSize, false)) { @@ -126,24 +124,6 @@ public class PermissionWindowUI : WindowMediatorSubscriberBase ImGui.AlignTextToFramePadding(); ImGui.Text(Pair.UserData.AliasOrUID + " has " + (!otherDisableVFX ? "not " : string.Empty) + "disabled VFX sync with you"); } - - if (ImGui.Checkbox("Enable location Sharing", ref shareLocation)) - { - _ownPermissions.SetShareLocation(shareLocation); - } - _uiSharedService.DrawHelpText("Enable location sharing will only effect your side." + UiSharedService.TooltipSeparator - + "Note: this is NOT bidirectional, you can choose to share even others dont share with you."); - using (ImRaii.PushIndent(indentSize, false)) - { - _uiSharedService.BooleanToColoredIcon(shareLocation, false); - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.Text((!shareLocation ? "Not" : string.Empty) + "sharing location with " + Pair.UserData.AliasOrUID + " ."); - -#if DEBUG - _uiSharedService.BooleanToColoredIcon(otherShareLocation, true); -#endif - } ImGuiHelpers.ScaledDummy(0.5f); ImGui.Separator(); diff --git a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs index 6cd704f..fdc4719 100644 --- a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs +++ b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs @@ -200,17 +200,5 @@ public partial class ApiController await UserPushData(new(visibleCharacters, character, censusDto)).ConfigureAwait(false); } - - public async Task UpdateLocation(LocationDto locationDto, bool offline = false) - { - if (!IsConnected) return; - await _lightlessHub!.SendAsync(nameof(UpdateLocation), locationDto, offline).ConfigureAwait(false); - } - - public async Task> RequestAllLocationInfo() - { - if (!IsConnected) return []; - return await _lightlessHub!.InvokeAsync>(nameof(RequestAllLocationInfo)).ConfigureAwait(false); - } } #pragma warning restore MA0040 \ No newline at end of file diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs index 01abc88..490800f 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs @@ -259,13 +259,6 @@ public partial class ApiController ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData))); return Task.CompletedTask; } - - public Task Client_SendLocationToClient(LocationDto locationDto) - { - Logger.LogDebug($"{nameof(Client_SendLocationToClient)}: {locationDto.user}"); - ExecuteSafely(() => Mediator.Publish(new LocationMessage(locationDto.user.UID, locationDto.location))); - return Task.CompletedTask; - } public void OnDownloadReady(Action act) { @@ -447,12 +440,6 @@ public partial class ApiController if (_initialized) return; _lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act); } - - public void OnReciveLocation(Action act) - { - if (_initialized) return; - _lightlessHub!.On(nameof(Client_SendLocationToClient), act); - } private void ExecuteSafely(Action act) { diff --git a/LightlessSync/WebAPI/SignalR/ApiController.cs b/LightlessSync/WebAPI/SignalR/ApiController.cs index a814758..9639f6f 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.cs @@ -606,8 +606,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto)); OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data)); OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data)); - OnReciveLocation(dto => _ = Client_SendLocationToClient(dto)); - _healthCheckTokenSource?.Cancel(); _healthCheckTokenSource?.Dispose(); From a933330418c96b242c2b2d4693522c3bfd322ed0 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Sun, 28 Dec 2025 23:07:45 +0800 Subject: [PATCH 02/10] Share location --- LightlessAPI | 2 +- LightlessSync/LightlessSync.csproj | 1 + LightlessSync/Plugin.cs | 1 + LightlessSync/Services/DalamudUtilService.cs | 16 ++- .../Services/LocationShareService.cs | 131 ++++++++++++++++++ LightlessSync/Services/Mediator/Messages.cs | 1 + LightlessSync/UI/Components/DrawUserPair.cs | 102 ++++++++++++++ LightlessSync/UI/DrawEntityFactory.cs | 4 + .../SignalR/ApIController.Functions.Users.cs | 16 +++ .../ApiController.Functions.Callbacks.cs | 13 ++ LightlessSync/WebAPI/SignalR/ApiController.cs | 2 + 11 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 LightlessSync/Services/LocationShareService.cs diff --git a/LightlessAPI b/LightlessAPI index 5656600..852e2a0 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 56566003e0e93bba05dcef49fd3ce23c6a204d81 +Subproject commit 852e2a005f5bfdf3844e057c6ba71de6f5f84ed8 diff --git a/LightlessSync/LightlessSync.csproj b/LightlessSync/LightlessSync.csproj index 707d2a3..96efb14 100644 --- a/LightlessSync/LightlessSync.csproj +++ b/LightlessSync/LightlessSync.csproj @@ -37,6 +37,7 @@ + diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 2d46b43..d070831 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -140,6 +140,7 @@ public sealed class Plugin : IDalamudPlugin services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(sp => new TextureMetadataHelper(sp.GetRequiredService>(), gameData)); diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 0b93997..96c78f3 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -1,11 +1,13 @@ using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; +using Dalamud.Game.Text; using Dalamud.Plugin.Services; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Control; +using FFXIVClientStructs.FFXIV.Client.Game.UI; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using LightlessSync.API.Dto.CharaData; @@ -26,6 +28,7 @@ using System.Text; using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; +using Map = Lumina.Excel.Sheets.Map; using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags; namespace LightlessSync.Services; @@ -86,7 +89,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber WorldData = new(() => { return gameData.GetExcelSheet(clientLanguage)! - .Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0]))) + .Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0]) + || w is { RowId: > 1000, Region: 101 })) .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); }); JobData = new(() => @@ -659,7 +663,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber var location = new LocationInfo(); location.ServerId = _playerState.CurrentWorld.RowId; - //location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first + location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; location.TerritoryId = _clientState.TerritoryType; location.MapId = _clientState.MapId; if (houseMan != null) @@ -713,10 +717,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber str += $" - {MapData.Value[(ushort)location.MapId].MapName}"; } - // if (location.InstanceId is not 0) - // { - // str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString(); - // } + if (location.InstanceId is not 0) + { + str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString(); + } if (location.WardId is not 0) { diff --git a/LightlessSync/Services/LocationShareService.cs b/LightlessSync/Services/LocationShareService.cs new file mode 100644 index 0000000..71989e5 --- /dev/null +++ b/LightlessSync/Services/LocationShareService.cs @@ -0,0 +1,131 @@ +using LightlessSync.API.Data; +using LightlessSync.API.Dto.CharaData; +using LightlessSync.API.Dto.User; +using LightlessSync.Services.Mediator; +using LightlessSync.WebAPI; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; + +namespace LightlessSync.Services +{ + public class LocationShareService : DisposableMediatorSubscriberBase + { + private readonly DalamudUtilService _dalamudUtilService; + private readonly ApiController _apiController; + private IMemoryCache _locations = new MemoryCache(new MemoryCacheOptions()); + private IMemoryCache _sharingStatus = new MemoryCache(new MemoryCacheOptions()); + private CancellationTokenSource _resetToken = new CancellationTokenSource(); + + + + public LocationShareService(ILogger logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator) + { + _dalamudUtilService = dalamudUtilService; + _apiController = apiController; + + + Mediator.Subscribe(this, (msg) => + { + _resetToken.Cancel(); + _resetToken.Dispose(); + _resetToken = new CancellationTokenSource(); + }); + Mediator.Subscribe(this, (msg) => + { + _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData())); + _ = RequestAllLocation(); + } ); + Mediator.Subscribe(this, UpdateLocationList); + Mediator.Subscribe(this, + msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData()))); + } + + private void UpdateLocationList(LocationSharingMessage msg) + { + if (_locations.TryGetValue(msg.User.UID, out _) && msg.LocationInfo.ServerId is 0) + { + _locations.Remove(msg.User.UID); + return; + } + + if ( msg.LocationInfo.ServerId is not 0 && msg.ExpireAt > DateTime.UtcNow) + { + AddLocationInfo(msg.User.UID, msg.LocationInfo, msg.ExpireAt); + } + } + + private void AddLocationInfo(string uid, LocationInfo location, DateTimeOffset expireAt) + { + var options = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(expireAt) + .AddExpirationToken(new CancellationChangeToken(_resetToken.Token)); + _locations.Set(uid, location, options); + } + + private async Task RequestAllLocation() + { + try + { + var (data, status) = await _apiController.RequestAllLocationInfo().ConfigureAwait(false); + foreach (var dto in data) + { + AddLocationInfo(dto.LocationDto.User.UID, dto.LocationDto.Location, dto.ExpireAt); + } + + foreach (var dto in status) + { + AddStatus(dto.User.UID, dto.ExpireAt); + } + } + catch (Exception e) + { + Logger.LogError(e,"RequestAllLocation error : "); + throw; + } + } + + private void AddStatus(string uid, DateTimeOffset expireAt) + { + var options = new MemoryCacheEntryOptions() + .SetAbsoluteExpiration(expireAt) + .AddExpirationToken(new CancellationChangeToken(_resetToken.Token)); + _sharingStatus.Set(uid, expireAt, options); + } + + public string GetUserLocation(string uid) + { + try + { + if (_locations.TryGetValue(uid, out var location)) + { + return _dalamudUtilService.LocationToString(location); + } + return String.Empty; + } + catch (Exception e) + { + Logger.LogError(e,"GetUserLocation error : "); + throw; + } + } + + public DateTimeOffset GetSharingStatus(string uid) + { + try + { + if (_sharingStatus.TryGetValue(uid, out var expireAt)) + { + return expireAt; + } + return DateTimeOffset.MinValue; + } + catch (Exception e) + { + Logger.LogError(e,"GetSharingStatus error : "); + throw; + } + } + + } +} \ No newline at end of file diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index 758b9f5..00f8de7 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -135,5 +135,6 @@ public record ChatChannelsUpdated : MessageBase; public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase; public record GroupCollectionChangedMessage : MessageBase; public record OpenUserProfileMessage(UserData User) : MessageBase; +public record LocationSharingMessage(UserData User, LocationInfo LocationInfo, DateTimeOffset ExpireAt) : MessageBase; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index a5fa953..b8e58c4 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -37,6 +37,7 @@ public class DrawUserPair private readonly UiSharedService _uiSharedService; private readonly PlayerPerformanceConfigService _performanceConfigService; private readonly LightlessConfigService _configService; + private readonly LocationShareService _locationShareService; private readonly CharaDataManager _charaDataManager; private readonly PairLedger _pairLedger; private float _menuWidth = -1; @@ -57,6 +58,7 @@ public class DrawUserPair UiSharedService uiSharedService, PlayerPerformanceConfigService performanceConfigService, LightlessConfigService configService, + LocationShareService locationShareService, CharaDataManager charaDataManager, PairLedger pairLedger) { @@ -74,6 +76,7 @@ public class DrawUserPair _uiSharedService = uiSharedService; _performanceConfigService = performanceConfigService; _configService = configService; + _locationShareService = locationShareService; _charaDataManager = charaDataManager; _pairLedger = pairLedger; } @@ -216,6 +219,41 @@ public class DrawUserPair _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions)); } UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty)); + + if (ImGui.BeginMenu(FontAwesomeIcon.Globe.ToIconString() + " Toggle Location sharing")) + { + if (ImGui.MenuItem("Share for 30 Mins")) + { + _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddMinutes(30)); + } + + if (ImGui.MenuItem("Share for 1 Hour")) + { + _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddHours(1)); + } + + if (ImGui.MenuItem("Share for 3 Hours")) + { + _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.UtcNow.AddHours(3)); + } + + if (ImGui.MenuItem("Share until manually stop")) + { + _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.MaxValue); + } + + ImGui.Separator(); + if (ImGui.MenuItem("Stop Sharing")) + { + _ = ToggleLocationSharing([_pair.UserData.UID], DateTimeOffset.MinValue); + } + ImGui.EndMenu(); + } + } + + private Task ToggleLocationSharing(List users, DateTimeOffset expireAt) + { + return _apiController.ToggleLocationSharing(new LocationSharingToggleDto(users, expireAt)); } private void DrawIndividualMenu() @@ -574,6 +612,70 @@ public class DrawUserPair var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false); var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky(); var individualIcon = individualIsSticky ? FontAwesomeIcon.ArrowCircleUp : FontAwesomeIcon.InfoCircle; + + var shareLocationIcon = FontAwesomeIcon.Globe; + var location = _locationShareService.GetUserLocation(_pair.UserPair!.User.UID); + var shareLocation = !string.IsNullOrEmpty(location); + var expireAt = _locationShareService.GetSharingStatus(_pair.UserPair!.User.UID); + var shareLocationOther = expireAt > DateTimeOffset.UtcNow; + var shareColor = shareLocation switch + { + true when shareLocationOther => UIColors.Get("LightlessGreen"), + false when shareLocationOther => UIColors.Get("LightlessBlue"), + _ => UIColors.Get("LightlessYellow"), + }; + + if (shareLocation || shareLocationOther) + { + currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX); + ImGui.SameLine(currentRightSide); + using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationOther)) + _uiSharedService.IconText(shareLocationIcon); + + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + if (shareLocationOther) + { + if (_pair.IsOnline) + { + if (!string.IsNullOrEmpty(location)) + { + _uiSharedService.IconText(FontAwesomeIcon.LocationArrow); + ImGui.SameLine(); + ImGui.TextUnformatted(location); + } + else + { + ImGui.TextUnformatted("Location info not updated, reconnect or wait for update."); + } + } + else + { + ImGui.TextUnformatted("User not onlineㄟ( ▔, ▔ )ㄏ"); + } + } + else + { + ImGui.TextUnformatted("NOT Sharing location with you.(⊙x⊙;)"); + } + ImGui.Separator(); + + if (shareLocation) + { + ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o"); + if (expireAt != DateTimeOffset.MaxValue) + { + ImGui.TextUnformatted("Expired at " + expireAt.ToLocalTime().ToString("g")); + } + } + else + { + ImGui.TextUnformatted("NOT sharing your location.(´。_。`)"); + } + ImGui.EndTooltip(); + } + } if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky) { diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index 3c71f5c..e7bcc87 100644 --- a/LightlessSync/UI/DrawEntityFactory.cs +++ b/LightlessSync/UI/DrawEntityFactory.cs @@ -29,6 +29,7 @@ public class DrawEntityFactory private readonly LightlessConfigService _configService; private readonly UiSharedService _uiSharedService; private readonly PlayerPerformanceConfigService _playerPerformanceConfigService; + private readonly LocationShareService _locationShareService; private readonly CharaDataManager _charaDataManager; private readonly SelectTagForPairUi _selectTagForPairUi; private readonly RenamePairTagUi _renamePairTagUi; @@ -53,6 +54,7 @@ public class DrawEntityFactory LightlessConfigService configService, UiSharedService uiSharedService, PlayerPerformanceConfigService playerPerformanceConfigService, + LocationShareService locationShareService, CharaDataManager charaDataManager, SelectTagForSyncshellUi selectTagForSyncshellUi, RenameSyncshellTagUi renameSyncshellTagUi, @@ -72,6 +74,7 @@ public class DrawEntityFactory _configService = configService; _uiSharedService = uiSharedService; _playerPerformanceConfigService = playerPerformanceConfigService; + _locationShareService = locationShareService; _charaDataManager = charaDataManager; _selectTagForSyncshellUi = selectTagForSyncshellUi; _renameSyncshellTagUi = renameSyncshellTagUi; @@ -162,6 +165,7 @@ public class DrawEntityFactory _uiSharedService, _playerPerformanceConfigService, _configService, + _locationShareService, _charaDataManager, _pairLedger); } diff --git a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs index fdc4719..fca42f3 100644 --- a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs +++ b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs @@ -200,5 +200,21 @@ public partial class ApiController await UserPushData(new(visibleCharacters, character, censusDto)).ConfigureAwait(false); } + + public async Task UpdateLocation(LocationDto locationDto, bool offline = false) + { + if (!IsConnected) return; + await _lightlessHub!.SendAsync(nameof(UpdateLocation), locationDto, offline).ConfigureAwait(false); + } + public async Task<(List, List)> RequestAllLocationInfo() + { + if (!IsConnected) return ([],[]); + return await _lightlessHub!.InvokeAsync<(List, List)>(nameof(RequestAllLocationInfo)).ConfigureAwait(false); + } + public async Task ToggleLocationSharing(LocationSharingToggleDto dto) + { + if (!IsConnected) return; + await _lightlessHub!.SendAsync(nameof(ToggleLocationSharing), dto).ConfigureAwait(false); + } } #pragma warning restore MA0040 \ No newline at end of file diff --git a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs index 490800f..d0a05f7 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.Functions.Callbacks.cs @@ -259,6 +259,13 @@ public partial class ApiController ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData))); return Task.CompletedTask; } + + public Task Client_SendLocationToClient(LocationDto locationDto, DateTimeOffset expireAt) + { + Logger.LogDebug($"{nameof(Client_SendLocationToClient)}: {locationDto.User} {expireAt}"); + ExecuteSafely(() => Mediator.Publish(new LocationSharingMessage(locationDto.User, locationDto.Location, expireAt))); + return Task.CompletedTask; + } public void OnDownloadReady(Action act) { @@ -441,6 +448,12 @@ public partial class ApiController _lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act); } + public void OnReceiveLocation(Action act) + { + if (_initialized) return; + _lightlessHub!.On(nameof(Client_SendLocationToClient), act); + } + private void ExecuteSafely(Action act) { try diff --git a/LightlessSync/WebAPI/SignalR/ApiController.cs b/LightlessSync/WebAPI/SignalR/ApiController.cs index 9639f6f..45705bf 100644 --- a/LightlessSync/WebAPI/SignalR/ApiController.cs +++ b/LightlessSync/WebAPI/SignalR/ApiController.cs @@ -606,6 +606,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto)); OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data)); OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data)); + OnReceiveLocation((dto, time) => _ = Client_SendLocationToClient(dto, time)); _healthCheckTokenSource?.Cancel(); _healthCheckTokenSource?.Dispose(); @@ -774,5 +775,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL ServerState = state; } + } #pragma warning restore MA0040 From 6d20995dbf00b4485c66672eff3a67e83bc6c1ac Mon Sep 17 00:00:00 2001 From: cake Date: Mon, 29 Dec 2025 02:50:49 +0100 Subject: [PATCH 03/10] Added decompression gate to decompress files --- .../WebAPI/Files/FileDownloadManager.cs | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/LightlessSync/WebAPI/Files/FileDownloadManager.cs b/LightlessSync/WebAPI/Files/FileDownloadManager.cs index 47774f7..2731619 100644 --- a/LightlessSync/WebAPI/Files/FileDownloadManager.cs +++ b/LightlessSync/WebAPI/Files/FileDownloadManager.cs @@ -28,6 +28,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase private readonly TextureMetadataHelper _textureMetadataHelper; private readonly ConcurrentDictionary _activeDownloadStreams; + private readonly SemaphoreSlim _decompressGate = + new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2)); private volatile bool _disableDirectDownloads; private int _consecutiveDirectDownloadFailures; @@ -522,32 +524,57 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase try { + // sanity check length if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue) throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}"); + // safe cast after check + var len = checked((int)fileLengthBytes); + if (!replacementLookup.TryGetValue(fileHash, out var repl)) { Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash); - // still need to skip bytes: - var skip = checked((int)fileLengthBytes); - fileBlockStream.Position += skip; + fileBlockStream.Seek(len, SeekOrigin.Current); continue; } + // decompress var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension); + Logger.LogTrace("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath); - Logger.LogDebug("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath); - - var len = checked((int)fileLengthBytes); + // read compressed data var compressed = new byte[len]; - await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false); - MungeBuffer(compressed); - var decompressed = LZ4Wrapper.Unwrap(compressed); + if (len == 0) + { + await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty(), ct).ConfigureAwait(false); + PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + continue; + } - await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); - PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + MungeBuffer(compressed); + + // limit concurrent decompressions + await _decompressGate.WaitAsync(ct).ConfigureAwait(false); + try + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + + // decompress + var decompressed = LZ4Wrapper.Unwrap(compressed); + + Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)", + downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1); + + // write to file + await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false); + PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale); + } + finally + { + _decompressGate.Release(); + } } catch (EndOfStreamException) { @@ -605,20 +632,16 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase .. await FilesGetSizes(hashes, ct).ConfigureAwait(false), ]; - Logger.LogDebug("Files with size 0 or less: {files}", - string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); - foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) { if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) _orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); } - CurrentDownloads = downloadFileInfoFromService + CurrentDownloads = [.. downloadFileInfoFromService .Distinct() .Select(d => new DownloadFileTransfer(d)) - .Where(d => d.CanBeTransferred) - .ToList(); + .Where(d => d.CanBeTransferred)]; return CurrentDownloads; } From 18fa0a47b1fff1493976fdf3007279a7af858eff Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Mon, 29 Dec 2025 15:42:55 +0800 Subject: [PATCH 04/10] Locationshare fix --- LightlessAPI | 2 +- .../Services/LocationShareService.cs | 9 ++++++- LightlessSync/UI/Components/DrawUserPair.cs | 24 +++++++++++-------- .../SignalR/ApIController.Functions.Users.cs | 6 ++--- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/LightlessAPI b/LightlessAPI index 852e2a0..c3caa7e 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 852e2a005f5bfdf3844e057c6ba71de6f5f84ed8 +Subproject commit c3caa7e25cf17fd52c4765bf051ec37c8fd92082 diff --git a/LightlessSync/Services/LocationShareService.cs b/LightlessSync/Services/LocationShareService.cs index 71989e5..77b5c90 100644 --- a/LightlessSync/Services/LocationShareService.cs +++ b/LightlessSync/Services/LocationShareService.cs @@ -126,6 +126,13 @@ namespace LightlessSync.Services throw; } } - + + public void UpdateSharingStatus(List users, DateTimeOffset expireAt) + { + foreach (var user in users) + { + AddStatus(user, expireAt); + } + } } } \ No newline at end of file diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index b8e58c4..3bd9fc0 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -251,9 +251,13 @@ public class DrawUserPair } } - private Task ToggleLocationSharing(List users, DateTimeOffset expireAt) + private async Task ToggleLocationSharing(List users, DateTimeOffset expireAt) { - return _apiController.ToggleLocationSharing(new LocationSharingToggleDto(users, expireAt)); + var updated = await _apiController.ToggleLocationSharing(new LocationSharingToggleDto(users, expireAt)).ConfigureAwait(false); + if (updated) + { + _locationShareService.UpdateSharingStatus(users, expireAt); + } } private void DrawIndividualMenu() @@ -617,25 +621,25 @@ public class DrawUserPair var location = _locationShareService.GetUserLocation(_pair.UserPair!.User.UID); var shareLocation = !string.IsNullOrEmpty(location); var expireAt = _locationShareService.GetSharingStatus(_pair.UserPair!.User.UID); - var shareLocationOther = expireAt > DateTimeOffset.UtcNow; + var shareLocationToOther = expireAt > DateTimeOffset.UtcNow; var shareColor = shareLocation switch { - true when shareLocationOther => UIColors.Get("LightlessGreen"), - false when shareLocationOther => UIColors.Get("LightlessBlue"), + true when shareLocationToOther => UIColors.Get("LightlessGreen"), + true when !shareLocationToOther => UIColors.Get("LightlessBlue"), _ => UIColors.Get("LightlessYellow"), }; - if (shareLocation || shareLocationOther) + if (shareLocation || shareLocationToOther) { currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX); ImGui.SameLine(currentRightSide); - using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationOther)) + using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther)) _uiSharedService.IconText(shareLocationIcon); if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); - if (shareLocationOther) + if (shareLocation) { if (_pair.IsOnline) { @@ -661,12 +665,12 @@ public class DrawUserPair } ImGui.Separator(); - if (shareLocation) + if (shareLocationToOther) { ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o"); if (expireAt != DateTimeOffset.MaxValue) { - ImGui.TextUnformatted("Expired at " + expireAt.ToLocalTime().ToString("g")); + ImGui.TextUnformatted("Expires at " + expireAt.ToLocalTime().ToString("g")); } } else diff --git a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs index fca42f3..37b91f9 100644 --- a/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs +++ b/LightlessSync/WebAPI/SignalR/ApIController.Functions.Users.cs @@ -211,10 +211,10 @@ public partial class ApiController if (!IsConnected) return ([],[]); return await _lightlessHub!.InvokeAsync<(List, List)>(nameof(RequestAllLocationInfo)).ConfigureAwait(false); } - public async Task ToggleLocationSharing(LocationSharingToggleDto dto) + public async Task ToggleLocationSharing(LocationSharingToggleDto dto) { - if (!IsConnected) return; - await _lightlessHub!.SendAsync(nameof(ToggleLocationSharing), dto).ConfigureAwait(false); + if (!IsConnected) return false; + return await _lightlessHub!.InvokeAsync(nameof(ToggleLocationSharing), dto).ConfigureAwait(false); } } #pragma warning restore MA0040 \ No newline at end of file From f37fdefddd8318055b773d09333171ac5bab31a5 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Mon, 29 Dec 2025 16:43:12 +0800 Subject: [PATCH 05/10] show icon correctly --- LightlessSync/UI/Components/DrawUserPair.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index 3bd9fc0..021ad1a 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -219,8 +219,11 @@ public class DrawUserPair _ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions)); } UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty)); - - if (ImGui.BeginMenu(FontAwesomeIcon.Globe.ToIconString() + " Toggle Location sharing")) + + ImGui.SetCursorPosX(10f); + _uiSharedService.IconText(FontAwesomeIcon.Globe); + ImGui.SameLine(); + if (ImGui.BeginMenu("Toggle Location sharing")) { if (ImGui.MenuItem("Share for 30 Mins")) { From 91e60694ad4de7261dbbf645e08b09495acf4d53 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Tue, 30 Dec 2025 11:20:12 +0800 Subject: [PATCH 06/10] triggers update when map changes --- LightlessSync/Services/DalamudUtilService.cs | 15 ++++++++++++++- LightlessSync/Services/LocationShareService.cs | 5 ++--- LightlessSync/Services/Mediator/Messages.cs | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 96c78f3..03ed35d 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -60,6 +60,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private string _lastGlobalBlockReason = string.Empty; private ushort _lastZone = 0; private ushort _lastWorldId = 0; + private uint _lastMapId = 0; private bool _sentBetweenAreas = false; private Lazy _cid; @@ -689,7 +690,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber var outside = houseMan->OutdoorTerritory; var house = outside->HouseId; location.WardId = house.WardIndex + 1u; - location.HouseId = (uint)houseMan->GetCurrentPlot() + 1; + //location.HouseId = (uint)houseMan->GetCurrentPlot() + 1; location.DivisionId = houseMan->GetCurrentDivision(); } //_logger.LogWarning(LocationToString(location)); @@ -1139,6 +1140,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new ZoneSwitchEndMessage()); Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); } + + //Map + if (!_sentBetweenAreas) + { + var mapid = _clientState.MapId; + if (mapid != _lastMapId) + { + _lastMapId = mapid; + Mediator.Publish(new MapChangedMessage(mapid)); + } + } + var localPlayer = _objectTable.LocalPlayer; if (localPlayer != null) diff --git a/LightlessSync/Services/LocationShareService.cs b/LightlessSync/Services/LocationShareService.cs index 77b5c90..38b2834 100644 --- a/LightlessSync/Services/LocationShareService.cs +++ b/LightlessSync/Services/LocationShareService.cs @@ -17,8 +17,6 @@ namespace LightlessSync.Services private IMemoryCache _sharingStatus = new MemoryCache(new MemoryCacheOptions()); private CancellationTokenSource _resetToken = new CancellationTokenSource(); - - public LocationShareService(ILogger logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator) { _dalamudUtilService = dalamudUtilService; @@ -37,7 +35,7 @@ namespace LightlessSync.Services _ = RequestAllLocation(); } ); Mediator.Subscribe(this, UpdateLocationList); - Mediator.Subscribe(this, + Mediator.Subscribe(this, msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData()))); } @@ -134,5 +132,6 @@ namespace LightlessSync.Services AddStatus(user, expireAt); } } + } } \ No newline at end of file diff --git a/LightlessSync/Services/Mediator/Messages.cs b/LightlessSync/Services/Mediator/Messages.cs index 00f8de7..efe3341 100644 --- a/LightlessSync/Services/Mediator/Messages.cs +++ b/LightlessSync/Services/Mediator/Messages.cs @@ -136,5 +136,6 @@ public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Messag public record GroupCollectionChangedMessage : MessageBase; public record OpenUserProfileMessage(UserData User) : MessageBase; public record LocationSharingMessage(UserData User, LocationInfo LocationInfo, DateTimeOffset ExpireAt) : MessageBase; +public record MapChangedMessage(uint MapId) : MessageBase; #pragma warning restore S2094 #pragma warning restore MA0048 // File name must match type name \ No newline at end of file From d1c955c74f28bd47bc3c277cd2a67ef83a0eb4d7 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Tue, 30 Dec 2025 14:23:37 +0800 Subject: [PATCH 07/10] Reuse WorldData and make context menu work for non-Global uses --- LightlessSync/Services/ContextMenuService.cs | 48 ++++---------------- LightlessSync/Services/DalamudUtilService.cs | 2 +- 2 files changed, 9 insertions(+), 41 deletions(-) diff --git a/LightlessSync/Services/ContextMenuService.cs b/LightlessSync/Services/ContextMenuService.cs index 3fe893c..024e17b 100644 --- a/LightlessSync/Services/ContextMenuService.cs +++ b/LightlessSync/Services/ContextMenuService.cs @@ -10,7 +10,6 @@ using LightlessSync.UI; using LightlessSync.UI.Services; using LightlessSync.Utils; using LightlessSync.WebAPI; -using Lumina.Excel.Sheets; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -172,9 +171,8 @@ internal class ContextMenuService : IHostedService _logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId); return; } - - var world = GetWorld(target.TargetHomeWorld.RowId); - if (!IsWorldValid(world)) + + if (!IsWorldValid(target.TargetHomeWorld.RowId)) { _logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId); return; @@ -226,9 +224,8 @@ internal class ContextMenuService : IHostedService { if (args.Target is not MenuTargetDefault target) return; - - var world = GetWorld(target.TargetHomeWorld.RowId); - if (!IsWorldValid(world)) + + if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId)) return; try @@ -237,7 +234,7 @@ internal class ContextMenuService : IHostedService if (targetData == null || targetData.Address == nint.Zero) { - _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name); + _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, target.TargetHomeWorld.Value.Name); return; } @@ -252,7 +249,7 @@ internal class ContextMenuService : IHostedService } // Notify in chat when NotificationService is disabled - NotifyInChat($"Pair request sent to {target.TargetName}@{world.Name}.", NotificationType.Info); + NotifyInChat($"Pair request sent to {target.TargetName}@{target.TargetHomeWorld.Value.Name}.", NotificationType.Info); } catch (Exception ex) { @@ -312,37 +309,8 @@ internal class ContextMenuService : IHostedService p.HomeWorld.RowId == target.TargetHomeWorld.RowId); } - private World GetWorld(uint worldId) + private bool IsWorldValid(uint worldId) { - var sheet = _gameData.GetExcelSheet()!; - var luminaWorlds = sheet.Where(x => - { - var dc = x.DataCenter.ValueNullable; - var name = x.Name.ExtractText(); - var internalName = x.InternalName.ExtractText(); - - if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText())) - return false; - - if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName)) - return false; - - if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal)) - return false; - - return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name); - }); - - return luminaWorlds.FirstOrDefault(x => x.RowId == worldId); - } - - private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); - - private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF; - - public static bool IsWorldValid(World world) - { - var name = world.Name.ToString(); - return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]); + return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId); } } diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 03ed35d..b278667 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -91,7 +91,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber { return gameData.GetExcelSheet(clientLanguage)! .Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0]) - || w is { RowId: > 1000, Region: 101 })) + || w is { RowId: > 1000, Region: 101 or 201 })) .ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString()); }); JobData = new(() => From f8752fcb4d1a87a737db702e2e251a5e1aefbdb0 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Tue, 30 Dec 2025 14:37:13 +0800 Subject: [PATCH 08/10] changed kanmoji to show correctly --- LightlessSync/UI/Components/DrawUserPair.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index 021ad1a..ac5c01a 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -659,18 +659,18 @@ public class DrawUserPair } else { - ImGui.TextUnformatted("User not onlineㄟ( ▔, ▔ )ㄏ"); + ImGui.TextUnformatted("User not online. (´・ω・`)?"); } } else { - ImGui.TextUnformatted("NOT Sharing location with you.(⊙x⊙;)"); + ImGui.TextUnformatted("NOT Sharing location with you. o(TヘTo)"); } ImGui.Separator(); if (shareLocationToOther) { - ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o"); + ImGui.TextUnformatted("Sharing your location. ヾ(•ω•`)o"); if (expireAt != DateTimeOffset.MaxValue) { ImGui.TextUnformatted("Expires at " + expireAt.ToLocalTime().ToString("g")); @@ -678,7 +678,7 @@ public class DrawUserPair } else { - ImGui.TextUnformatted("NOT sharing your location.(´。_。`)"); + ImGui.TextUnformatted("NOT sharing your location.  ̄へ ̄"); } ImGui.EndTooltip(); } From ca7375b9c3f82eaea2089dcf636b5acffd01bf77 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Tue, 30 Dec 2025 14:42:02 +0800 Subject: [PATCH 09/10] dont check location when target is offline --- LightlessSync/UI/Components/DrawUserPair.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index ac5c01a..c8725e2 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -642,9 +642,10 @@ public class DrawUserPair if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); - if (shareLocation) + + if (_pair.IsOnline) { - if (_pair.IsOnline) + if (shareLocation) { if (!string.IsNullOrEmpty(location)) { @@ -659,12 +660,12 @@ public class DrawUserPair } else { - ImGui.TextUnformatted("User not online. (´・ω・`)?"); + ImGui.TextUnformatted("NOT Sharing location with you. o(TヘTo)"); } } else { - ImGui.TextUnformatted("NOT Sharing location with you. o(TヘTo)"); + ImGui.TextUnformatted("User not online. (´・ω・`)?"); } ImGui.Separator(); From e25979e08991030fbfca121a9341b8c15d84d3f9 Mon Sep 17 00:00:00 2001 From: Tsubasahane Date: Tue, 30 Dec 2025 18:04:54 +0800 Subject: [PATCH 10/10] fix Icon direction --- LightlessSync/UI/DataAnalysisUi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessSync/UI/DataAnalysisUi.cs b/LightlessSync/UI/DataAnalysisUi.cs index 32245d2..a3061a7 100644 --- a/LightlessSync/UI/DataAnalysisUi.cs +++ b/LightlessSync/UI/DataAnalysisUi.cs @@ -2183,7 +2183,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase bool toggleClicked = false; if (showToggle) { - var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft; + var icon = !isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft; Vector2 iconSize; using (_uiSharedService.IconFont.Push()) {