Compare commits
18 Commits
nameplate-
...
lifestream
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de9c9955ef | ||
|
|
df33a0f0a2 | ||
| c439d1c822 | |||
| 7e61954541 | |||
|
|
fb58d8657d | ||
| bbb3375661 | |||
| ed7932ab83 | |||
| 4eaaaf694c | |||
|
|
c32c89d1a8 | ||
| a8b58d05d6 | |||
| 9ea0571e82 | |||
|
|
308c220735 | ||
|
|
27d4da4615 | ||
|
|
6b49c92ef9 | ||
|
|
6d20995dbf | ||
|
|
cf495dc826 | ||
|
|
08050614da | ||
| 94f520d0e7 |
Submodule LightlessAPI updated: 56566003e0...4ecd5375e6
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Lifestream.Enums;
|
||||||
|
|
||||||
|
public enum ResidentialAetheryteKind
|
||||||
|
{
|
||||||
|
None = -1,
|
||||||
|
Uldah = 9,
|
||||||
|
Gridania = 2,
|
||||||
|
Limsa = 8,
|
||||||
|
Foundation = 70,
|
||||||
|
Kugane = 111,
|
||||||
|
}
|
||||||
1
LightlessSync/Interop/InteropModel/GlobalModels.cs
Normal file
1
LightlessSync/Interop/InteropModel/GlobalModels.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using AddressBookEntryTuple = (string Name, int World, int City, int Ward, int PropertyType, int Plot, int Apartment, bool ApartmentSubdivision, bool AliasEnabled, string Alias);
|
||||||
129
LightlessSync/Interop/Ipc/IpcCallerLifestream.cs
Normal file
129
LightlessSync/Interop/Ipc/IpcCallerLifestream.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using Lifestream.Enums;
|
||||||
|
using LightlessSync.Interop.Ipc.Framework;
|
||||||
|
using LightlessSync.Services.Mediator;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
|
||||||
|
namespace LightlessSync.Interop.Ipc;
|
||||||
|
|
||||||
|
public sealed class IpcCallerLifestream : IpcServiceBase
|
||||||
|
{
|
||||||
|
private static readonly IpcServiceDescriptor LifestreamDescriptor = new("Lifestream", "Lifestream", new Version(0, 0, 0, 0));
|
||||||
|
|
||||||
|
private readonly ICallGateSubscriber<string, object> _executeLifestreamCommand;
|
||||||
|
private readonly ICallGateSubscriber<AddressBookEntryTuple, bool> _isHere;
|
||||||
|
private readonly ICallGateSubscriber<AddressBookEntryTuple, object> _goToHousingAddress;
|
||||||
|
private readonly ICallGateSubscriber<bool> _isBusy;
|
||||||
|
private readonly ICallGateSubscriber<object> _abort;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _changeWorld;
|
||||||
|
private readonly ICallGateSubscriber<uint, bool> _changeWorldById;
|
||||||
|
private readonly ICallGateSubscriber<string, bool> _aetheryteTeleport;
|
||||||
|
private readonly ICallGateSubscriber<uint, bool> _aetheryteTeleportById;
|
||||||
|
private readonly ICallGateSubscriber<bool> _canChangeInstance;
|
||||||
|
private readonly ICallGateSubscriber<int> _getCurrentInstance;
|
||||||
|
private readonly ICallGateSubscriber<int> _getNumberOfInstances;
|
||||||
|
private readonly ICallGateSubscriber<int, object> _changeInstance;
|
||||||
|
private readonly ICallGateSubscriber<(ResidentialAetheryteKind, int, int)> _getCurrentPlotInfo;
|
||||||
|
|
||||||
|
public IpcCallerLifestream(IDalamudPluginInterface pi, LightlessMediator lightlessMediator, ILogger<IpcCallerLifestream> logger)
|
||||||
|
: base(logger, lightlessMediator, pi, LifestreamDescriptor)
|
||||||
|
{
|
||||||
|
_executeLifestreamCommand = pi.GetIpcSubscriber<string, object>("Lifestream.ExecuteCommand");
|
||||||
|
_isHere = pi.GetIpcSubscriber<AddressBookEntryTuple, bool>("Lifestream.IsHere");
|
||||||
|
_goToHousingAddress = pi.GetIpcSubscriber<AddressBookEntryTuple, object>("Lifestream.GoToHousingAddress");
|
||||||
|
_isBusy = pi.GetIpcSubscriber<bool>("Lifestream.IsBusy");
|
||||||
|
_abort = pi.GetIpcSubscriber<object>("Lifestream.Abort");
|
||||||
|
_changeWorld = pi.GetIpcSubscriber<string, bool>("Lifestream.ChangeWorld");
|
||||||
|
_changeWorldById = pi.GetIpcSubscriber<uint, bool>("Lifestream.ChangeWorldById");
|
||||||
|
_aetheryteTeleport = pi.GetIpcSubscriber<string, bool>("Lifestream.AetheryteTeleport");
|
||||||
|
_aetheryteTeleportById = pi.GetIpcSubscriber<uint, bool>("Lifestream.AetheryteTeleportById");
|
||||||
|
_canChangeInstance = pi.GetIpcSubscriber<bool>("Lifestream.CanChangeInstance");
|
||||||
|
_getCurrentInstance = pi.GetIpcSubscriber<int>("Lifestream.GetCurrentInstance");
|
||||||
|
_getNumberOfInstances = pi.GetIpcSubscriber<int>("Lifestream.GetNumberOfInstances");
|
||||||
|
_changeInstance = pi.GetIpcSubscriber<int, object>("Lifestream.ChangeInstance");
|
||||||
|
_getCurrentPlotInfo = pi.GetIpcSubscriber<(ResidentialAetheryteKind, int, int)>("Lifestream.GetCurrentPlotInfo");
|
||||||
|
CheckAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteLifestreamCommand(string command)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_executeLifestreamCommand.InvokeAction(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHere(AddressBookEntryTuple entry)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _isHere.InvokeFunc(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoToHousingAddress(AddressBookEntryTuple entry)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_goToHousingAddress.InvokeAction(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBusy()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _isBusy.InvokeFunc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Abort()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_abort.InvokeAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeWorld(string worldName)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _changeWorld.InvokeFunc(worldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AetheryteTeleport(string aetheryteName)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _aetheryteTeleport.InvokeFunc(aetheryteName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeWorldById(uint worldId)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _changeWorldById.InvokeFunc(worldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AetheryteTeleportById(uint aetheryteId)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _aetheryteTeleportById.InvokeFunc(aetheryteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanChangeInstance()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return false;
|
||||||
|
return _canChangeInstance.InvokeFunc();
|
||||||
|
}
|
||||||
|
public int GetCurrentInstance()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return -1;
|
||||||
|
return _getCurrentInstance.InvokeFunc();
|
||||||
|
}
|
||||||
|
public int GetNumberOfInstances()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return -1;
|
||||||
|
return _getNumberOfInstances.InvokeFunc();
|
||||||
|
}
|
||||||
|
public void ChangeInstance(int instanceNumber)
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return;
|
||||||
|
_changeInstance.InvokeAction(instanceNumber);
|
||||||
|
}
|
||||||
|
public (ResidentialAetheryteKind, int, int)? GetCurrentPlotInfo()
|
||||||
|
{
|
||||||
|
if (!APIAvailable) return (ResidentialAetheryteKind.None, -1, -1);
|
||||||
|
return _getCurrentPlotInfo.InvokeFunc();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
public IpcManager(ILogger<IpcManager> logger, LightlessMediator mediator,
|
public IpcManager(ILogger<IpcManager> logger, LightlessMediator mediator,
|
||||||
IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc,
|
IpcCallerPenumbra penumbraIpc, IpcCallerGlamourer glamourerIpc, IpcCallerCustomize customizeIpc, IpcCallerHeels heelsIpc,
|
||||||
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio) : base(logger, mediator)
|
IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio,
|
||||||
|
IpcCallerLifestream ipcCallerLifestream) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
CustomizePlus = customizeIpc;
|
CustomizePlus = customizeIpc;
|
||||||
Heels = heelsIpc;
|
Heels = heelsIpc;
|
||||||
@@ -17,6 +18,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles = moodlesIpc;
|
Moodles = moodlesIpc;
|
||||||
PetNames = ipcCallerPetNames;
|
PetNames = ipcCallerPetNames;
|
||||||
Brio = ipcCallerBrio;
|
Brio = ipcCallerBrio;
|
||||||
|
Lifestream = ipcCallerLifestream;
|
||||||
|
|
||||||
if (Initialized)
|
if (Initialized)
|
||||||
{
|
{
|
||||||
@@ -44,8 +46,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
public IpcCallerPenumbra Penumbra { get; }
|
public IpcCallerPenumbra Penumbra { get; }
|
||||||
public IpcCallerMoodles Moodles { get; }
|
public IpcCallerMoodles Moodles { get; }
|
||||||
public IpcCallerPetNames PetNames { get; }
|
public IpcCallerPetNames PetNames { get; }
|
||||||
|
|
||||||
public IpcCallerBrio Brio { get; }
|
public IpcCallerBrio Brio { get; }
|
||||||
|
public IpcCallerLifestream Lifestream { get; }
|
||||||
|
|
||||||
private void PeriodicApiStateCheck()
|
private void PeriodicApiStateCheck()
|
||||||
{
|
{
|
||||||
@@ -58,5 +60,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
|
|||||||
Moodles.CheckAPI();
|
Moodles.CheckAPI();
|
||||||
PetNames.CheckAPI();
|
PetNames.CheckAPI();
|
||||||
Brio.CheckAPI();
|
Brio.CheckAPI();
|
||||||
|
Lifestream.CheckAPI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>2.0.2</Version>
|
<Version>2.0.3</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" 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="Microsoft.Extensions.Hosting" Version="10.0.1" />
|
||||||
<PackageReference Include="Glamourer.Api" Version="2.8.0" />
|
<PackageReference Include="Glamourer.Api" Version="2.8.0" />
|
||||||
<PackageReference Include="NReco.Logging.File" Version="1.3.1" />
|
<PackageReference Include="NReco.Logging.File" Version="1.3.1" />
|
||||||
|
|||||||
@@ -1423,7 +1423,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
private Task _visibilityGraceTask;
|
private Task _visibilityGraceTask;
|
||||||
|
|
||||||
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
|
||||||
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
|
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
|
||||||
{
|
{
|
||||||
var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
|
var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
@@ -1577,24 +1577,37 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
RecordFailure("Handler not available for application", "HandlerUnavailable");
|
RecordFailure("Handler not available for application", "HandlerUnavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
|
||||||
|
|
||||||
var appToken = _applicationCancellationTokenSource?.Token;
|
if (_applicationTask != null && !_applicationTask.IsCompleted)
|
||||||
while ((!_applicationTask?.IsCompleted ?? false)
|
|
||||||
&& !downloadToken.IsCancellationRequested
|
|
||||||
&& (!appToken?.IsCancellationRequested ?? false))
|
|
||||||
{
|
{
|
||||||
Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
|
Logger.LogDebug("[BASE-{appBase}] Cancelling current data application (Id: {id}) for player ({handler})", applicationBase, _applicationId, PlayerName);
|
||||||
await Task.Delay(250).ConfigureAwait(false);
|
|
||||||
|
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
|
||||||
|
var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(downloadToken, timeoutCts.Token);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _applicationTask.WaitAsync(combinedCts.Token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("[BASE-{appBase}] Timeout waiting for application task {id} to complete, proceeding anyway", applicationBase, _applicationId);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
timeoutCts.Dispose();
|
||||||
|
combinedCts.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false))
|
if (downloadToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
_forceFullReapply = true;
|
_forceFullReapply = true;
|
||||||
RecordFailure("Application cancelled", "Cancellation");
|
RecordFailure("Application cancelled", "Cancellation");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
|
|
||||||
var token = _applicationCancellationTokenSource.Token;
|
var token = _applicationCancellationTokenSource.Token;
|
||||||
|
|
||||||
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token);
|
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token);
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
services.AddSingleton<IdDisplayHandler>();
|
services.AddSingleton<IdDisplayHandler>();
|
||||||
services.AddSingleton<PlayerPerformanceService>();
|
services.AddSingleton<PlayerPerformanceService>();
|
||||||
services.AddSingleton<PenumbraTempCollectionJanitor>();
|
services.AddSingleton<PenumbraTempCollectionJanitor>();
|
||||||
|
services.AddSingleton<LocationShareService>();
|
||||||
|
|
||||||
services.AddSingleton<TextureMetadataHelper>(sp =>
|
services.AddSingleton<TextureMetadataHelper>(sp =>
|
||||||
new TextureMetadataHelper(sp.GetRequiredService<ILogger<TextureMetadataHelper>>(), gameData));
|
new TextureMetadataHelper(sp.GetRequiredService<ILogger<TextureMetadataHelper>>(), gameData));
|
||||||
@@ -372,6 +373,11 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
sp.GetRequiredService<DalamudUtilService>(),
|
sp.GetRequiredService<DalamudUtilService>(),
|
||||||
sp.GetRequiredService<LightlessMediator>()));
|
sp.GetRequiredService<LightlessMediator>()));
|
||||||
|
|
||||||
|
services.AddSingleton(sp => new IpcCallerLifestream(
|
||||||
|
pluginInterface,
|
||||||
|
sp.GetRequiredService<LightlessMediator>(),
|
||||||
|
sp.GetRequiredService<ILogger<IpcCallerLifestream>>()));
|
||||||
|
|
||||||
services.AddSingleton(sp => new IpcManager(
|
services.AddSingleton(sp => new IpcManager(
|
||||||
sp.GetRequiredService<ILogger<IpcManager>>(),
|
sp.GetRequiredService<ILogger<IpcManager>>(),
|
||||||
sp.GetRequiredService<LightlessMediator>(),
|
sp.GetRequiredService<LightlessMediator>(),
|
||||||
@@ -382,7 +388,9 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
sp.GetRequiredService<IpcCallerHonorific>(),
|
sp.GetRequiredService<IpcCallerHonorific>(),
|
||||||
sp.GetRequiredService<IpcCallerMoodles>(),
|
sp.GetRequiredService<IpcCallerMoodles>(),
|
||||||
sp.GetRequiredService<IpcCallerPetNames>(),
|
sp.GetRequiredService<IpcCallerPetNames>(),
|
||||||
sp.GetRequiredService<IpcCallerBrio>()));
|
sp.GetRequiredService<IpcCallerBrio>(),
|
||||||
|
sp.GetRequiredService<IpcCallerLifestream>()
|
||||||
|
));
|
||||||
|
|
||||||
// Notifications / HTTP
|
// Notifications / HTTP
|
||||||
services.AddSingleton(sp => new NotificationService(
|
services.AddSingleton(sp => new NotificationService(
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using LightlessSync.UI;
|
|||||||
using LightlessSync.UI.Services;
|
using LightlessSync.UI.Services;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using Lumina.Excel.Sheets;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
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);
|
_logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
if (!IsWorldValid(target.TargetHomeWorld.RowId))
|
||||||
if (!IsWorldValid(world))
|
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId);
|
_logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId);
|
||||||
return;
|
return;
|
||||||
@@ -226,9 +224,8 @@ internal class ContextMenuService : IHostedService
|
|||||||
{
|
{
|
||||||
if (args.Target is not MenuTargetDefault target)
|
if (args.Target is not MenuTargetDefault target)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId))
|
||||||
if (!IsWorldValid(world))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -237,7 +234,7 @@ internal class ContextMenuService : IHostedService
|
|||||||
|
|
||||||
if (targetData == null || targetData.Address == nint.Zero)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +249,7 @@ internal class ContextMenuService : IHostedService
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Notify in chat when NotificationService is disabled
|
// 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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -312,37 +309,8 @@ internal class ContextMenuService : IHostedService
|
|||||||
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private World GetWorld(uint worldId)
|
private bool IsWorldValid(uint worldId)
|
||||||
{
|
{
|
||||||
var sheet = _gameData.GetExcelSheet<World>()!;
|
return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId);
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using Dalamud.Game.ClientState.Conditions;
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.Text;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
using LightlessSync.API.Dto.CharaData;
|
||||||
@@ -26,6 +28,7 @@ using System.Text;
|
|||||||
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
|
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
|
||||||
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||||
|
using Map = Lumina.Excel.Sheets.Map;
|
||||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||||
|
|
||||||
namespace LightlessSync.Services;
|
namespace LightlessSync.Services;
|
||||||
@@ -57,6 +60,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
private string _lastGlobalBlockReason = string.Empty;
|
private string _lastGlobalBlockReason = string.Empty;
|
||||||
private ushort _lastZone = 0;
|
private ushort _lastZone = 0;
|
||||||
private ushort _lastWorldId = 0;
|
private ushort _lastWorldId = 0;
|
||||||
|
private uint _lastMapId = 0;
|
||||||
private bool _sentBetweenAreas = false;
|
private bool _sentBetweenAreas = false;
|
||||||
private Lazy<ulong> _cid;
|
private Lazy<ulong> _cid;
|
||||||
|
|
||||||
@@ -86,7 +90,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
WorldData = new(() =>
|
WorldData = new(() =>
|
||||||
{
|
{
|
||||||
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(clientLanguage)!
|
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 or 201 }))
|
||||||
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
||||||
});
|
});
|
||||||
JobData = new(() =>
|
JobData = new(() =>
|
||||||
@@ -659,7 +664,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
|
|
||||||
var location = new LocationInfo();
|
var location = new LocationInfo();
|
||||||
location.ServerId = _playerState.CurrentWorld.RowId;
|
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.TerritoryId = _clientState.TerritoryType;
|
||||||
location.MapId = _clientState.MapId;
|
location.MapId = _clientState.MapId;
|
||||||
if (houseMan != null)
|
if (houseMan != null)
|
||||||
@@ -685,7 +690,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
var outside = houseMan->OutdoorTerritory;
|
var outside = houseMan->OutdoorTerritory;
|
||||||
var house = outside->HouseId;
|
var house = outside->HouseId;
|
||||||
location.WardId = house.WardIndex + 1u;
|
location.WardId = house.WardIndex + 1u;
|
||||||
location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
//location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
||||||
location.DivisionId = houseMan->GetCurrentDivision();
|
location.DivisionId = houseMan->GetCurrentDivision();
|
||||||
}
|
}
|
||||||
//_logger.LogWarning(LocationToString(location));
|
//_logger.LogWarning(LocationToString(location));
|
||||||
@@ -713,10 +718,10 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
str += $" - {MapData.Value[(ushort)location.MapId].MapName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (location.InstanceId is not 0)
|
if (location.InstanceId is not 0)
|
||||||
// {
|
{
|
||||||
// str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
str += ((SeIconChar)(57520 + location.InstanceId)).ToIconString();
|
||||||
// }
|
}
|
||||||
|
|
||||||
if (location.WardId is not 0)
|
if (location.WardId is not 0)
|
||||||
{
|
{
|
||||||
@@ -1135,6 +1140,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
Mediator.Publish(new ZoneSwitchEndMessage());
|
Mediator.Publish(new ZoneSwitchEndMessage());
|
||||||
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
|
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;
|
var localPlayer = _objectTable.LocalPlayer;
|
||||||
if (localPlayer != null)
|
if (localPlayer != null)
|
||||||
|
|||||||
137
LightlessSync/Services/LocationShareService.cs
Normal file
137
LightlessSync/Services/LocationShareService.cs
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
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<MapChangedMessage>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSharingStatus(List<string> users, DateTimeOffset expireAt)
|
||||||
|
{
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
AddStatus(user, expireAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,5 +135,7 @@ public record ChatChannelsUpdated : MessageBase;
|
|||||||
public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase;
|
public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Message) : MessageBase;
|
||||||
public record GroupCollectionChangedMessage : MessageBase;
|
public record GroupCollectionChangedMessage : MessageBase;
|
||||||
public record OpenUserProfileMessage(UserData User) : 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 S2094
|
||||||
#pragma warning restore MA0048 // File name must match type name
|
#pragma warning restore MA0048 // File name must match type name
|
||||||
@@ -105,6 +105,7 @@ public class UiFactory
|
|||||||
groupData: groupData,
|
groupData: groupData,
|
||||||
isLightfinderContext: isLightfinderContext,
|
isLightfinderContext: isLightfinderContext,
|
||||||
lightfinderCid: lightfinderCid,
|
lightfinderCid: lightfinderCid,
|
||||||
performanceCollector: _performanceCollectorService);
|
performanceCollector: _performanceCollectorService,
|
||||||
|
_apiController);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ public class DrawUserPair
|
|||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly PlayerPerformanceConfigService _performanceConfigService;
|
private readonly PlayerPerformanceConfigService _performanceConfigService;
|
||||||
private readonly LightlessConfigService _configService;
|
private readonly LightlessConfigService _configService;
|
||||||
|
private readonly LocationShareService _locationShareService;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
private readonly PairLedger _pairLedger;
|
private readonly PairLedger _pairLedger;
|
||||||
private float _menuWidth = -1;
|
private float _menuWidth = -1;
|
||||||
@@ -57,6 +58,7 @@ public class DrawUserPair
|
|||||||
UiSharedService uiSharedService,
|
UiSharedService uiSharedService,
|
||||||
PlayerPerformanceConfigService performanceConfigService,
|
PlayerPerformanceConfigService performanceConfigService,
|
||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
|
LocationShareService locationShareService,
|
||||||
CharaDataManager charaDataManager,
|
CharaDataManager charaDataManager,
|
||||||
PairLedger pairLedger)
|
PairLedger pairLedger)
|
||||||
{
|
{
|
||||||
@@ -74,6 +76,7 @@ public class DrawUserPair
|
|||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
_performanceConfigService = performanceConfigService;
|
_performanceConfigService = performanceConfigService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_locationShareService = locationShareService;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
_pairLedger = pairLedger;
|
_pairLedger = pairLedger;
|
||||||
}
|
}
|
||||||
@@ -216,6 +219,48 @@ public class DrawUserPair
|
|||||||
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions));
|
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(_pair.UserData, permissions));
|
||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty));
|
UiSharedService.AttachToolTip("Changes VFX sync permissions with this user." + (individual ? individualText : string.Empty));
|
||||||
|
|
||||||
|
ImGui.SetCursorPosX(10f);
|
||||||
|
_uiSharedService.IconText(FontAwesomeIcon.Globe);
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.BeginMenu("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 async Task ToggleLocationSharing(List<string> users, DateTimeOffset expireAt)
|
||||||
|
{
|
||||||
|
var updated = await _apiController.ToggleLocationSharing(new LocationSharingToggleDto(users, expireAt)).ConfigureAwait(false);
|
||||||
|
if (updated)
|
||||||
|
{
|
||||||
|
_locationShareService.UpdateSharingStatus(users, expireAt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawIndividualMenu()
|
private void DrawIndividualMenu()
|
||||||
@@ -574,6 +619,71 @@ public class DrawUserPair
|
|||||||
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
var individualVFXDisabled = (_pair.UserPair?.OwnPermissions.IsDisableVFX() ?? false) || (_pair.UserPair?.OtherPermissions.IsDisableVFX() ?? false);
|
||||||
var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky();
|
var individualIsSticky = _pair.UserPair!.OwnPermissions.IsSticky();
|
||||||
var individualIcon = individualIsSticky ? FontAwesomeIcon.ArrowCircleUp : FontAwesomeIcon.InfoCircle;
|
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 shareLocationToOther = expireAt > DateTimeOffset.UtcNow;
|
||||||
|
var shareColor = shareLocation switch
|
||||||
|
{
|
||||||
|
true when shareLocationToOther => UIColors.Get("LightlessGreen"),
|
||||||
|
true when !shareLocationToOther => UIColors.Get("LightlessBlue"),
|
||||||
|
_ => UIColors.Get("LightlessYellow"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shareLocation || shareLocationToOther)
|
||||||
|
{
|
||||||
|
currentRightSide -= (_uiSharedService.GetIconSize(shareLocationIcon).X + spacingX);
|
||||||
|
ImGui.SameLine(currentRightSide);
|
||||||
|
using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther))
|
||||||
|
_uiSharedService.IconText(shareLocationIcon);
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
|
||||||
|
if (_pair.IsOnline)
|
||||||
|
{
|
||||||
|
if (shareLocation)
|
||||||
|
{
|
||||||
|
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("NOT Sharing location with you. o(TヘTo)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("User not online. (´・ω・`)?");
|
||||||
|
}
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
if (shareLocationToOther)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Sharing your location. ヾ(•ω•`)o");
|
||||||
|
if (expireAt != DateTimeOffset.MaxValue)
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("Expires at " + expireAt.ToLocalTime().ToString("g"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.TextUnformatted("NOT sharing your location.  ̄へ ̄");
|
||||||
|
}
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky)
|
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2183,7 +2183,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
bool toggleClicked = false;
|
bool toggleClicked = false;
|
||||||
if (showToggle)
|
if (showToggle)
|
||||||
{
|
{
|
||||||
var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
|
var icon = !isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
|
||||||
Vector2 iconSize;
|
Vector2 iconSize;
|
||||||
using (_uiSharedService.IconFont.Push())
|
using (_uiSharedService.IconFont.Push())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class DrawEntityFactory
|
|||||||
private readonly LightlessConfigService _configService;
|
private readonly LightlessConfigService _configService;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
|
private readonly LocationShareService _locationShareService;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
private readonly SelectTagForPairUi _selectTagForPairUi;
|
private readonly SelectTagForPairUi _selectTagForPairUi;
|
||||||
private readonly RenamePairTagUi _renamePairTagUi;
|
private readonly RenamePairTagUi _renamePairTagUi;
|
||||||
@@ -53,6 +54,7 @@ public class DrawEntityFactory
|
|||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
UiSharedService uiSharedService,
|
UiSharedService uiSharedService,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfigService,
|
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||||
|
LocationShareService locationShareService,
|
||||||
CharaDataManager charaDataManager,
|
CharaDataManager charaDataManager,
|
||||||
SelectTagForSyncshellUi selectTagForSyncshellUi,
|
SelectTagForSyncshellUi selectTagForSyncshellUi,
|
||||||
RenameSyncshellTagUi renameSyncshellTagUi,
|
RenameSyncshellTagUi renameSyncshellTagUi,
|
||||||
@@ -72,6 +74,7 @@ public class DrawEntityFactory
|
|||||||
_configService = configService;
|
_configService = configService;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
|
_locationShareService = locationShareService;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
_selectTagForSyncshellUi = selectTagForSyncshellUi;
|
_selectTagForSyncshellUi = selectTagForSyncshellUi;
|
||||||
_renameSyncshellTagUi = renameSyncshellTagUi;
|
_renameSyncshellTagUi = renameSyncshellTagUi;
|
||||||
@@ -162,6 +165,7 @@ public class DrawEntityFactory
|
|||||||
_uiSharedService,
|
_uiSharedService,
|
||||||
_playerPerformanceConfigService,
|
_playerPerformanceConfigService,
|
||||||
_configService,
|
_configService,
|
||||||
|
_locationShareService,
|
||||||
_charaDataManager,
|
_charaDataManager,
|
||||||
_pairLedger);
|
_pairLedger);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Dalamud.Interface.Colors;
|
|||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Lifestream.Enums;
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Data.Comparer;
|
using LightlessSync.API.Data.Comparer;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
@@ -84,6 +85,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
private bool _pairDiagnosticsEnabled;
|
private bool _pairDiagnosticsEnabled;
|
||||||
private string? _selectedPairDebugUid = null;
|
private string? _selectedPairDebugUid = null;
|
||||||
private string _lightfinderIconInput = string.Empty;
|
private string _lightfinderIconInput = string.Empty;
|
||||||
|
private bool _showLightfinderRendererWarning = false;
|
||||||
|
private LightfinderLabelRenderer _pendingLightfinderRenderer = LightfinderLabelRenderer.Pictomancy;
|
||||||
private bool _lightfinderIconInputInitialized = false;
|
private bool _lightfinderIconInputInitialized = false;
|
||||||
private int _lightfinderIconPresetIndex = -1;
|
private int _lightfinderIconPresetIndex = -1;
|
||||||
private static readonly LightlessConfig DefaultConfig = new();
|
private static readonly LightlessConfig DefaultConfig = new();
|
||||||
@@ -1241,7 +1244,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.TreePop();
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard"))
|
||||||
{
|
{
|
||||||
if (LastCreatedCharacterData != null)
|
if (LastCreatedCharacterData != null)
|
||||||
@@ -1257,6 +1259,39 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
||||||
|
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to Limsa [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
|
||||||
|
{
|
||||||
|
_ipcManager.Lifestream.ExecuteLifestreamCommand("limsa");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to JoyHouse [LIFESTREAM TEST]") && _ipcManager.Lifestream.APIAvailable)
|
||||||
|
{
|
||||||
|
var twintania = _dalamudUtilService.WorldData.Value
|
||||||
|
.FirstOrDefault(kvp => kvp.Value.Equals("Twintania", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
int ward = 29;
|
||||||
|
int plot = 7;
|
||||||
|
|
||||||
|
AddressBookEntryTuple addressEntry = (
|
||||||
|
Name: "",
|
||||||
|
World: (int)twintania.Key,
|
||||||
|
City: (int)ResidentialAetheryteKind.Kugane,
|
||||||
|
Ward: ward,
|
||||||
|
PropertyType: 0,
|
||||||
|
Plot: plot,
|
||||||
|
Apartment: 1,
|
||||||
|
ApartmentSubdivision: false,
|
||||||
|
AliasEnabled: false,
|
||||||
|
Alias: ""
|
||||||
|
);
|
||||||
|
|
||||||
|
_logger.LogInformation("going to: {address}", addressEntry);
|
||||||
|
|
||||||
|
_ipcManager.Lifestream.GoToHousingAddress(addressEntry);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
|
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
|
||||||
{
|
{
|
||||||
_configService.Current.LogLevel = l;
|
_configService.Current.LogLevel = l;
|
||||||
@@ -2372,7 +2407,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
var labelRenderer = _configService.Current.LightfinderLabelRenderer;
|
var labelRenderer = _configService.Current.LightfinderLabelRenderer;
|
||||||
var labelRendererLabel = labelRenderer switch
|
var labelRendererLabel = labelRenderer switch
|
||||||
{
|
{
|
||||||
LightfinderLabelRenderer.SignatureHook => "Native nameplate (sig hook)",
|
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||||
_ => "ImGui Overlay",
|
_ => "ImGui Overlay",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2382,18 +2417,25 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
var optionLabel = option switch
|
var optionLabel = option switch
|
||||||
{
|
{
|
||||||
LightfinderLabelRenderer.SignatureHook => "Native Nameplate (sig hook)",
|
LightfinderLabelRenderer.SignatureHook => "Native Nameplate Rendering",
|
||||||
_ => "ImGui Overlay",
|
_ => "ImGui Overlay",
|
||||||
};
|
};
|
||||||
|
|
||||||
var selected = option == labelRenderer;
|
var selected = option == labelRenderer;
|
||||||
if (ImGui.Selectable(optionLabel, selected))
|
if (ImGui.Selectable(optionLabel, selected))
|
||||||
{
|
{
|
||||||
_configService.Current.LightfinderLabelRenderer = option;
|
if (option == LightfinderLabelRenderer.SignatureHook)
|
||||||
_configService.Save();
|
{
|
||||||
_nameplateService.RequestRedraw();
|
_pendingLightfinderRenderer = option;
|
||||||
|
_showLightfinderRendererWarning = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_configService.Current.LightfinderLabelRenderer = option;
|
||||||
|
_configService.Save();
|
||||||
|
_nameplateService.RequestRedraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected)
|
if (selected)
|
||||||
ImGui.SetItemDefaultFocus();
|
ImGui.SetItemDefaultFocus();
|
||||||
}
|
}
|
||||||
@@ -2401,6 +2443,34 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.EndCombo();
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_showLightfinderRendererWarning)
|
||||||
|
{
|
||||||
|
ImGui.SetNextWindowSize(new Vector2(450f, 0f), ImGuiCond.Appearing);
|
||||||
|
ImGui.OpenPopup("Nameplate Warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginPopupModal("Nameplate Warning", ref _showLightfinderRendererWarning, ImGuiWindowFlags.AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui.TextColored(UIColors.Get("DimRed"), "USE AT YOUR RISK!");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextWrapped("Writing on to the native Nameplates is known to be unstable and MAY cause crashes. DO NOT REPORT THOSE CRASHES TO DALAMUD. We will also not be supporting Nameplate crashes. You have been warned.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
ImGui.TextWrapped("By accepting this warning, you understand that you are using this feature at risk of crashing.");
|
||||||
|
ImGui.Spacing();
|
||||||
|
|
||||||
|
var buttonWidth = ImGui.GetContentRegionAvail().X;
|
||||||
|
if (ImGui.Button("I Understand", new Vector2(buttonWidth, 0)))
|
||||||
|
{
|
||||||
|
_configService.Current.LightfinderLabelRenderer = _pendingLightfinderRenderer;
|
||||||
|
_configService.Save();
|
||||||
|
_nameplateService.RequestRedraw();
|
||||||
|
_showLightfinderRendererWarning = false;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
_uiShared.DrawHelpText("Choose how Lightfinder labels render: the default ImGui overlay or native nameplate nodes via signature hook.");
|
_uiShared.DrawHelpText("Choose how Lightfinder labels render: the default ImGui overlay or native nameplate nodes via signature hook.");
|
||||||
|
|
||||||
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using LightlessSync.Services.ServerConfiguration;
|
|||||||
using LightlessSync.UI.Services;
|
using LightlessSync.UI.Services;
|
||||||
using LightlessSync.UI.Tags;
|
using LightlessSync.UI.Tags;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
private readonly PairUiService _pairUiService;
|
private readonly PairUiService _pairUiService;
|
||||||
private readonly ServerConfigurationManager _serverManager;
|
private readonly ServerConfigurationManager _serverManager;
|
||||||
private readonly ProfileTagService _profileTagService;
|
private readonly ProfileTagService _profileTagService;
|
||||||
|
private readonly ApiController _apiController;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
private readonly UserData? _userData;
|
private readonly UserData? _userData;
|
||||||
private readonly GroupData? _groupData;
|
private readonly GroupData? _groupData;
|
||||||
@@ -60,7 +62,8 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
GroupData? groupData,
|
GroupData? groupData,
|
||||||
bool isLightfinderContext,
|
bool isLightfinderContext,
|
||||||
string? lightfinderCid,
|
string? lightfinderCid,
|
||||||
PerformanceCollectorService performanceCollector)
|
PerformanceCollectorService performanceCollector,
|
||||||
|
ApiController apiController)
|
||||||
: base(logger, mediator, BuildWindowTitle(
|
: base(logger, mediator, BuildWindowTitle(
|
||||||
userData,
|
userData,
|
||||||
groupData,
|
groupData,
|
||||||
@@ -94,6 +97,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
.Apply();
|
.Apply();
|
||||||
|
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
_apiController = apiController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair? Pair { get; }
|
public Pair? Pair { get; }
|
||||||
@@ -248,19 +252,33 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
ResetBannerTexture();
|
ResetBannerTexture();
|
||||||
_lastBannerPicture = bannerBytes;
|
_lastBannerPicture = bannerBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? noteText = null;
|
string? noteText = null;
|
||||||
string statusLabel = _isLightfinderContext ? "Exploring" : "Offline";
|
|
||||||
|
var isSelfProfile = !_isLightfinderContext
|
||||||
|
&& _userData is not null
|
||||||
|
&& !string.IsNullOrEmpty(_apiController.UID)
|
||||||
|
&& string.Equals(_userData.UID, _apiController.UID, StringComparison.Ordinal);
|
||||||
|
|
||||||
|
string statusLabel = _isLightfinderContext
|
||||||
|
? "Exploring"
|
||||||
|
: isSelfProfile ? "Online" : "Offline";
|
||||||
|
|
||||||
string? visiblePlayerName = null;
|
string? visiblePlayerName = null;
|
||||||
bool directPair = false;
|
bool directPair = false;
|
||||||
bool youPaused = false;
|
bool youPaused = false;
|
||||||
bool theyPaused = false;
|
bool theyPaused = false;
|
||||||
List<string> syncshellLines = [];
|
List<string> syncshellLines = [];
|
||||||
|
|
||||||
|
if (!_isLightfinderContext)
|
||||||
|
{
|
||||||
|
noteText = _serverManager.GetNoteForUid(_userData!.UID);
|
||||||
|
}
|
||||||
|
|
||||||
if (!_isLightfinderContext && Pair != null)
|
if (!_isLightfinderContext && Pair != null)
|
||||||
{
|
{
|
||||||
var snapshot = _pairUiService.GetSnapshot();
|
var snapshot = _pairUiService.GetSnapshot();
|
||||||
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
|
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
|
||||||
|
|
||||||
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
|
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
|
||||||
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
|
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
|
||||||
|
|
||||||
@@ -282,11 +300,15 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
|
|||||||
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
|
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
|
||||||
? groupInfo.GroupAliasOrGID
|
? groupInfo.GroupAliasOrGID
|
||||||
: gid;
|
: gid;
|
||||||
|
|
||||||
var groupNote = _serverManager.GetNoteForGid(gid);
|
var groupNote = _serverManager.GetNoteForGid(gid);
|
||||||
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
|
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSelfProfile)
|
||||||
|
statusLabel = "Online";
|
||||||
}
|
}
|
||||||
|
|
||||||
var presenceTokens = new List<PresenceToken>
|
var presenceTokens = new List<PresenceToken>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
|
|
||||||
var purple = UIColors.Get("LightlessPurple");
|
var purple = UIColors.Get("LightlessPurple");
|
||||||
var gradLeft = purple.WithAlpha(0.0f);
|
var gradLeft = purple.WithAlpha(0.0f);
|
||||||
var gradRight = purple.WithAlpha(0.85f);
|
var gradRight = purple.WithAlpha(0.85f);
|
||||||
|
|
||||||
uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
|
uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
|
||||||
@@ -162,7 +162,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var subtitlePos = new Vector2(
|
var subtitlePos = new Vector2(
|
||||||
pMin.X + 12f * scale,
|
pMin.X + 12f * scale,
|
||||||
titlePos.Y + titleHeight - 2f * scale);
|
titlePos.Y + titleHeight - 2f * scale);
|
||||||
|
|
||||||
ImGui.SetCursorScreenPos(subtitlePos);
|
ImGui.SetCursorScreenPos(subtitlePos);
|
||||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||||
@@ -392,25 +392,27 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server.");
|
UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server.");
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.SetNextItemWidth(150);
|
|
||||||
|
|
||||||
using (ImRaii.Disabled(!_autoPruneEnabled))
|
|
||||||
{
|
|
||||||
_uiSharedService.DrawCombo(
|
|
||||||
"Day(s) of inactivity",
|
|
||||||
[1, 3, 7, 14, 30, 90],
|
|
||||||
days => $"{days} day(s)",
|
|
||||||
selected =>
|
|
||||||
{
|
|
||||||
_autoPruneDays = selected;
|
|
||||||
SavePruneSettings();
|
|
||||||
},
|
|
||||||
_autoPruneDays);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_autoPruneEnabled)
|
if (!_autoPruneEnabled)
|
||||||
{
|
{
|
||||||
|
ImGui.BeginDisabled();
|
||||||
|
}
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.SetNextItemWidth(150);
|
||||||
|
_uiSharedService.DrawCombo(
|
||||||
|
"Day(s) of inactivity (gets checked hourly)",
|
||||||
|
[0, 1, 3, 7, 14, 30, 90],
|
||||||
|
(count) => count == 0 ? "2 hours(s)" : count + " day(s)",
|
||||||
|
selected =>
|
||||||
|
{
|
||||||
|
_autoPruneDays = selected;
|
||||||
|
SavePruneSettings();
|
||||||
|
},
|
||||||
|
_autoPruneDays);
|
||||||
|
|
||||||
|
if (!_autoPruneEnabled)
|
||||||
|
{
|
||||||
|
ImGui.EndDisabled();
|
||||||
UiSharedService.ColorTextWrapped(
|
UiSharedService.ColorTextWrapped(
|
||||||
"Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.",
|
"Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.",
|
||||||
ImGuiColors.DalamudGrey);
|
ImGuiColors.DalamudGrey);
|
||||||
@@ -593,7 +595,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
_uiSharedService.DrawCombo(
|
_uiSharedService.DrawCombo(
|
||||||
"Day(s) of inactivity",
|
"Day(s) of inactivity",
|
||||||
[0, 1, 3, 7, 14, 30, 90],
|
[0, 1, 3, 7, 14, 30, 90],
|
||||||
(count) => count == 0 ? "15 minute(s)" : count + " day(s)",
|
(count) => count == 0 ? "2 hours(s)" : count + " day(s)",
|
||||||
(selected) =>
|
(selected) =>
|
||||||
{
|
{
|
||||||
_pruneDays = selected;
|
_pruneDays = selected;
|
||||||
@@ -663,8 +665,8 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
var style = ImGui.GetStyle();
|
var style = ImGui.GetStyle();
|
||||||
float fullW = ImGui.GetContentRegionAvail().X;
|
float fullW = ImGui.GetContentRegionAvail().X;
|
||||||
|
|
||||||
float colIdentity = fullW * 0.45f;
|
float colIdentity = fullW * 0.45f;
|
||||||
float colMeta = fullW * 0.35f;
|
float colMeta = fullW * 0.35f;
|
||||||
float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f;
|
float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
@@ -873,7 +875,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline);
|
var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline);
|
||||||
UiSharedService.ColorText(text, boolcolor);
|
UiSharedService.ColorText(text, boolcolor);
|
||||||
|
|
||||||
if (ImGui.IsItemClicked())
|
if (ImGui.IsItemClicked())
|
||||||
ImGui.SetClipboardText(pair.UserData.AliasOrUID);
|
ImGui.SetClipboardText(pair.UserData.AliasOrUID);
|
||||||
|
|
||||||
@@ -1093,6 +1095,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
|
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SavePruneSettings()
|
private void SavePruneSettings()
|
||||||
{
|
{
|
||||||
if (_autoPruneDays <= 0)
|
if (_autoPruneDays <= 0)
|
||||||
@@ -1100,8 +1103,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
|||||||
_autoPruneEnabled = false;
|
_autoPruneEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabled = _autoPruneEnabled && _autoPruneDays > 0;
|
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: _autoPruneEnabled, AutoPruneDays: _autoPruneDays);
|
||||||
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: enabled, AutoPruneDays: enabled ? _autoPruneDays : 0);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
private readonly Dictionary<string, DateTime> _oauthTokenExpiry = [];
|
private readonly Dictionary<string, DateTime> _oauthTokenExpiry = [];
|
||||||
private bool _penumbraExists = false;
|
private bool _penumbraExists = false;
|
||||||
private bool _petNamesExists = false;
|
private bool _petNamesExists = false;
|
||||||
|
private bool _lifestreamExists = false;
|
||||||
private int _serverSelectionIndex = -1;
|
private int _serverSelectionIndex = -1;
|
||||||
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
public UiSharedService(ILogger<UiSharedService> logger, IpcManager ipcManager, ApiController apiController,
|
||||||
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
CacheMonitor cacheMonitor, FileDialogManager fileDialogManager,
|
||||||
@@ -112,6 +113,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
_moodlesExists = _ipcManager.Moodles.APIAvailable;
|
_moodlesExists = _ipcManager.Moodles.APIAvailable;
|
||||||
_petNamesExists = _ipcManager.PetNames.APIAvailable;
|
_petNamesExists = _ipcManager.PetNames.APIAvailable;
|
||||||
_brioExists = _ipcManager.Brio.APIAvailable;
|
_brioExists = _ipcManager.Brio.APIAvailable;
|
||||||
|
_lifestreamExists = _ipcManager.Lifestream.APIAvailable;
|
||||||
});
|
});
|
||||||
|
|
||||||
UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e =>
|
||||||
@@ -1105,6 +1107,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
|||||||
ColorText("Brio", GetBoolColor(_brioExists));
|
ColorText("Brio", GetBoolColor(_brioExists));
|
||||||
AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State));
|
AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State));
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ColorText("Lifestream", GetBoolColor(_lifestreamExists));
|
||||||
|
AttachToolTip(BuildPluginTooltip("Lifestream", _lifestreamExists, _ipcManager.Lifestream.State));
|
||||||
|
|
||||||
if (!_penumbraExists || !_glamourerExists)
|
if (!_penumbraExists || !_glamourerExists)
|
||||||
{
|
{
|
||||||
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Lightless Sync.");
|
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Lightless Sync.");
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
private readonly TextureMetadataHelper _textureMetadataHelper;
|
private readonly TextureMetadataHelper _textureMetadataHelper;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
|
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
|
||||||
|
private readonly SemaphoreSlim _decompressGate =
|
||||||
|
new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
|
||||||
|
|
||||||
private volatile bool _disableDirectDownloads;
|
private volatile bool _disableDirectDownloads;
|
||||||
private int _consecutiveDirectDownloadFailures;
|
private int _consecutiveDirectDownloadFailures;
|
||||||
@@ -500,6 +502,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveStatus(string key)
|
||||||
|
{
|
||||||
|
lock (_downloadStatusLock)
|
||||||
|
{
|
||||||
|
_downloadStatus.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DecompressBlockFileAsync(
|
private async Task DecompressBlockFileAsync(
|
||||||
string downloadStatusKey,
|
string downloadStatusKey,
|
||||||
string blockFilePath,
|
string blockFilePath,
|
||||||
@@ -522,32 +532,57 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// sanity check length
|
||||||
if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue)
|
if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue)
|
||||||
throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}");
|
throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}");
|
||||||
|
|
||||||
|
// safe cast after check
|
||||||
|
var len = checked((int)fileLengthBytes);
|
||||||
|
|
||||||
if (!replacementLookup.TryGetValue(fileHash, out var repl))
|
if (!replacementLookup.TryGetValue(fileHash, out var repl))
|
||||||
{
|
{
|
||||||
Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash);
|
Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash);
|
||||||
// still need to skip bytes:
|
fileBlockStream.Seek(len, SeekOrigin.Current);
|
||||||
var skip = checked((int)fileLengthBytes);
|
|
||||||
fileBlockStream.Position += skip;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decompress
|
||||||
var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension);
|
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);
|
// read compressed data
|
||||||
|
|
||||||
var len = checked((int)fileLengthBytes);
|
|
||||||
var compressed = new byte[len];
|
var compressed = new byte[len];
|
||||||
|
|
||||||
await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false);
|
await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false);
|
||||||
|
|
||||||
MungeBuffer(compressed);
|
if (len == 0)
|
||||||
var decompressed = LZ4Wrapper.Unwrap(compressed);
|
{
|
||||||
|
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
|
||||||
|
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
|
MungeBuffer(compressed);
|
||||||
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
|
|
||||||
|
// 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)
|
catch (EndOfStreamException)
|
||||||
{
|
{
|
||||||
@@ -568,6 +603,10 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
Logger.LogError(ex, "{dlName}: Error during block file read", downloadLabel);
|
Logger.LogError(ex, "{dlName}: Error during block file read", downloadLabel);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RemoveStatus(downloadStatusKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<DownloadFileTransfer>> InitiateDownloadList(
|
public async Task<List<DownloadFileTransfer>> InitiateDownloadList(
|
||||||
@@ -605,20 +644,16 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
.. await FilesGetSizes(hashes, ct).ConfigureAwait(false),
|
.. 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))
|
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
|
||||||
{
|
{
|
||||||
if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
|
if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
|
||||||
_orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
|
_orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentDownloads = downloadFileInfoFromService
|
CurrentDownloads = [.. downloadFileInfoFromService
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.Select(d => new DownloadFileTransfer(d))
|
.Select(d => new DownloadFileTransfer(d))
|
||||||
.Where(d => d.CanBeTransferred)
|
.Where(d => d.CanBeTransferred)];
|
||||||
.ToList();
|
|
||||||
|
|
||||||
return CurrentDownloads;
|
return CurrentDownloads;
|
||||||
}
|
}
|
||||||
@@ -843,6 +878,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
||||||
Logger.LogDebug("Finished direct download of {hash}.", directDownload.Hash);
|
Logger.LogDebug("Finished direct download of {hash}.", directDownload.Hash);
|
||||||
|
|
||||||
|
RemoveStatus(directDownload.DirectDownloadUrl!);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException ex)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -200,5 +200,21 @@ public partial class ApiController
|
|||||||
|
|
||||||
await UserPushData(new(visibleCharacters, character, censusDto)).ConfigureAwait(false);
|
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<bool> ToggleLocationSharing(LocationSharingToggleDto dto)
|
||||||
|
{
|
||||||
|
if (!IsConnected) return false;
|
||||||
|
return await _lightlessHub!.InvokeAsync<bool>(nameof(ToggleLocationSharing), dto).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore MA0040
|
#pragma warning restore MA0040
|
||||||
@@ -259,6 +259,13 @@ public partial class ApiController
|
|||||||
ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData)));
|
ExecuteSafely(() => Mediator.Publish(new GPoseLobbyReceiveWorldData(userData, worldData)));
|
||||||
return Task.CompletedTask;
|
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)
|
public void OnDownloadReady(Action<Guid> act)
|
||||||
{
|
{
|
||||||
@@ -441,6 +448,12 @@ public partial class ApiController
|
|||||||
_lightlessHub!.On(nameof(Client_GposeLobbyPushWorldData), act);
|
_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)
|
private void ExecuteSafely(Action act)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -606,6 +606,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto));
|
OnGposeLobbyPushCharacterData((dto) => _ = Client_GposeLobbyPushCharacterData(dto));
|
||||||
OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data));
|
OnGposeLobbyPushPoseData((dto, data) => _ = Client_GposeLobbyPushPoseData(dto, data));
|
||||||
OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data));
|
OnGposeLobbyPushWorldData((dto, data) => _ = Client_GposeLobbyPushWorldData(dto, data));
|
||||||
|
OnReceiveLocation((dto, time) => _ = Client_SendLocationToClient(dto, time));
|
||||||
|
|
||||||
_healthCheckTokenSource?.Cancel();
|
_healthCheckTokenSource?.Cancel();
|
||||||
_healthCheckTokenSource?.Dispose();
|
_healthCheckTokenSource?.Dispose();
|
||||||
@@ -774,5 +775,6 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
|
|||||||
|
|
||||||
ServerState = state;
|
ServerState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#pragma warning restore MA0040
|
#pragma warning restore MA0040
|
||||||
|
|||||||
@@ -76,6 +76,19 @@
|
|||||||
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.Caching.Memory": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[10.0.1, )",
|
||||||
|
"resolved": "10.0.1",
|
||||||
|
"contentHash": "NxqSP0Ky4dZ5ybszdZCqs1X2C70s+dXflqhYBUh/vhcQVTIooNCXIYnLVbafoAFGZMs51d9+rHxveXs0ZC3SQQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Caching.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Logging.Abstractions": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Options": "10.0.1",
|
||||||
|
"Microsoft.Extensions.Primitives": "10.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Hosting": {
|
"Microsoft.Extensions.Hosting": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[10.0.1, )",
|
"requested": "[10.0.1, )",
|
||||||
@@ -233,6 +246,14 @@
|
|||||||
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
"Microsoft.AspNetCore.SignalR.Common": "10.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.Caching.Abstractions": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.1",
|
||||||
|
"contentHash": "Vb1vVAQDxHpXVdL9fpOX2BzeV7bbhzG4pAcIKRauRl0/VfkE8mq0f+fYC+gWICh3dlzTZInJ/cTeBS2MgU/XvQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.Primitives": "10.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Configuration": {
|
"Microsoft.Extensions.Configuration": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "10.0.1",
|
"resolved": "10.0.1",
|
||||||
@@ -618,7 +639,7 @@
|
|||||||
"FlatSharp.Compiler": "[7.9.0, )",
|
"FlatSharp.Compiler": "[7.9.0, )",
|
||||||
"FlatSharp.Runtime": "[7.9.0, )",
|
"FlatSharp.Runtime": "[7.9.0, )",
|
||||||
"OtterGui": "[1.0.0, )",
|
"OtterGui": "[1.0.0, )",
|
||||||
"Penumbra.Api": "[5.13.0, )",
|
"Penumbra.Api": "[5.13.1, )",
|
||||||
"Penumbra.String": "[1.0.7, )"
|
"Penumbra.String": "[1.0.7, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user