Share location
This commit is contained in:
Submodule LightlessAPI updated: 56566003e0...852e2a005f
@@ -37,6 +37,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
|
||||
<PackageReference Include="Glamourer.Api" Version="2.8.0" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.3.1" />
|
||||
|
||||
@@ -140,6 +140,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
services.AddSingleton<IdDisplayHandler>();
|
||||
services.AddSingleton<PlayerPerformanceService>();
|
||||
services.AddSingleton<PenumbraTempCollectionJanitor>();
|
||||
services.AddSingleton<LocationShareService>();
|
||||
|
||||
services.AddSingleton<TextureMetadataHelper>(sp =>
|
||||
new TextureMetadataHelper(sp.GetRequiredService<ILogger<TextureMetadataHelper>>(), gameData));
|
||||
|
||||
@@ -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<Lumina.Excel.Sheets.World>(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)
|
||||
{
|
||||
|
||||
131
LightlessSync/Services/LocationShareService.cs
Normal file
131
LightlessSync/Services/LocationShareService.cs
Normal file
@@ -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<LocationShareService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator)
|
||||
{
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
_apiController = apiController;
|
||||
|
||||
|
||||
Mediator.Subscribe<DisconnectedMessage>(this, (msg) =>
|
||||
{
|
||||
_resetToken.Cancel();
|
||||
_resetToken.Dispose();
|
||||
_resetToken = new CancellationTokenSource();
|
||||
});
|
||||
Mediator.Subscribe<ConnectedMessage>(this, (msg) =>
|
||||
{
|
||||
_ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, apiController.DisplayName), _dalamudUtilService.GetMapData()));
|
||||
_ = RequestAllLocation();
|
||||
} );
|
||||
Mediator.Subscribe<LocationSharingMessage>(this, UpdateLocationList);
|
||||
Mediator.Subscribe<ZoneSwitchEndMessage>(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<LocationInfo>(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<DateTimeOffset>(uid, out var expireAt))
|
||||
{
|
||||
return expireAt;
|
||||
}
|
||||
return DateTimeOffset.MinValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e,"GetSharingStatus error : ");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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<string> 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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<LocationWithTimeDto>, List<SharingStatusDto>)> RequestAllLocationInfo()
|
||||
{
|
||||
if (!IsConnected) return ([],[]);
|
||||
return await _lightlessHub!.InvokeAsync<(List<LocationWithTimeDto>, List<SharingStatusDto>)>(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
|
||||
@@ -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<Guid> act)
|
||||
{
|
||||
@@ -441,6 +448,12 @@ public partial class ApiController
|
||||
_lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act);
|
||||
}
|
||||
|
||||
public void OnReceiveLocation(Action<LocationDto, DateTimeOffset> act)
|
||||
{
|
||||
if (_initialized) return;
|
||||
_lightlessHub!.On(nameof(Client_SendLocationToClient), act);
|
||||
}
|
||||
|
||||
private void ExecuteSafely(Action act)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user