Compare commits

..

7 Commits

Author SHA1 Message Date
defnotken
f76ded344b Testing decompression stuff
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m5s
2025-12-30 08:54:49 -06:00
defnotken
e4b4e90640 Merge branch 'decompression-bullshit' into dev 2025-12-30 08:54:17 -06:00
defnotken
3308a475d7 Merge branch '2.0.2' into dev 2025-12-30 08:54:02 -06:00
defnotken
de2b537fb1 bump
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m7s
2025-12-27 22:03:19 -06:00
defnotken
838f4d1b1b Merge branch '2.0.2' into dev 2025-12-27 22:02:45 -06:00
defnotken
3a838077ac Merge branch '2.0.2' into dev
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 2m10s
2025-12-27 21:40:07 -06:00
defnotken
fe9122e0d2 build out dev 2025-12-27 20:50:09 -06:00
20 changed files with 55 additions and 537 deletions

View File

@@ -1,11 +0,0 @@
namespace Lifestream.Enums;
public enum ResidentialAetheryteKind
{
None = -1,
Uldah = 9,
Gridania = 2,
Limsa = 8,
Foundation = 70,
Kugane = 111,
}

View File

@@ -1 +0,0 @@
global using AddressBookEntryTuple = (string Name, int World, int City, int Ward, int PropertyType, int Plot, int Apartment, bool ApartmentSubdivision, bool AliasEnabled, string Alias);

View File

@@ -1,129 +0,0 @@
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();
}
}

View File

@@ -7,8 +7,7 @@ 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, IpcCallerHonorific honorificIpc, IpcCallerMoodles moodlesIpc, IpcCallerPetNames ipcCallerPetNames, IpcCallerBrio ipcCallerBrio) : base(logger, mediator)
IpcCallerLifestream ipcCallerLifestream) : base(logger, mediator)
{ {
CustomizePlus = customizeIpc; CustomizePlus = customizeIpc;
Heels = heelsIpc; Heels = heelsIpc;
@@ -18,7 +17,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
Moodles = moodlesIpc; Moodles = moodlesIpc;
PetNames = ipcCallerPetNames; PetNames = ipcCallerPetNames;
Brio = ipcCallerBrio; Brio = ipcCallerBrio;
Lifestream = ipcCallerLifestream;
if (Initialized) if (Initialized)
{ {
@@ -46,8 +44,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()
{ {
@@ -60,6 +58,5 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase
Moodles.CheckAPI(); Moodles.CheckAPI();
PetNames.CheckAPI(); PetNames.CheckAPI();
Brio.CheckAPI(); Brio.CheckAPI();
Lifestream.CheckAPI();
} }
} }

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>2.0.3</Version> <Version>2.0.1.71</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,7 +37,6 @@
</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" />

View File

@@ -140,7 +140,6 @@ 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));
@@ -373,11 +372,6 @@ 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>(),
@@ -388,9 +382,7 @@ 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(

View File

@@ -10,6 +10,7 @@ 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,7 +173,8 @@ internal class ContextMenuService : IHostedService
return; return;
} }
if (!IsWorldValid(target.TargetHomeWorld.RowId)) var world = GetWorld(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;
@@ -225,7 +227,8 @@ internal class ContextMenuService : IHostedService
if (args.Target is not MenuTargetDefault target) if (args.Target is not MenuTargetDefault target)
return; return;
if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId)) var world = GetWorld(target.TargetHomeWorld.RowId);
if (!IsWorldValid(world))
return; return;
try try
@@ -234,7 +237,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, target.TargetHomeWorld.Value.Name); _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name);
return; return;
} }
@@ -249,7 +252,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}@{target.TargetHomeWorld.Value.Name}.", NotificationType.Info); NotifyInChat($"Pair request sent to {target.TargetName}@{world.Name}.", NotificationType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -309,8 +312,37 @@ internal class ContextMenuService : IHostedService
p.HomeWorld.RowId == target.TargetHomeWorld.RowId); p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
} }
private bool IsWorldValid(uint worldId) private World GetWorld(uint worldId)
{ {
return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId); var sheet = _gameData.GetExcelSheet<World>()!;
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]);
} }
} }

View File

@@ -1,13 +1,11 @@
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;
@@ -28,7 +26,6 @@ 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;
@@ -60,7 +57,6 @@ 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;
@@ -90,8 +86,7 @@ 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(() =>
@@ -664,7 +659,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; //location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; //TODO:Need API update first
location.TerritoryId = _clientState.TerritoryType; location.TerritoryId = _clientState.TerritoryType;
location.MapId = _clientState.MapId; location.MapId = _clientState.MapId;
if (houseMan != null) if (houseMan != null)
@@ -690,7 +685,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));
@@ -718,10 +713,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)
{ {
@@ -1141,18 +1136,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
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)
{ {

View File

@@ -1,137 +0,0 @@
using LightlessSync.API.Data;
using LightlessSync.API.Dto.CharaData;
using LightlessSync.API.Dto.User;
using LightlessSync.Services.Mediator;
using LightlessSync.WebAPI;
using Microsoft.Extensions.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);
}
}
}
}

View File

@@ -135,7 +135,5 @@ 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

View File

@@ -37,7 +37,6 @@ 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;
@@ -58,7 +57,6 @@ 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)
{ {
@@ -76,7 +74,6 @@ 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;
} }
@@ -219,48 +216,6 @@ 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()
@@ -620,71 +575,6 @@ public class DrawUserPair
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)
{ {
currentRightSide -= (_uiSharedService.GetIconSize(individualIcon).X + spacingX); currentRightSide -= (_uiSharedService.GetIconSize(individualIcon).X + spacingX);

View File

@@ -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())
{ {

View File

@@ -29,7 +29,6 @@ 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;
@@ -54,7 +53,6 @@ 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,
@@ -74,7 +72,6 @@ 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;
@@ -165,7 +162,6 @@ public class DrawEntityFactory
_uiSharedService, _uiSharedService,
_playerPerformanceConfigService, _playerPerformanceConfigService,
_configService, _configService,
_locationShareService,
_charaDataManager, _charaDataManager,
_pairLedger); _pairLedger);
} }

View File

@@ -5,7 +5,6 @@ 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;
@@ -1244,6 +1243,7 @@ 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)
@@ -1259,39 +1259,6 @@ 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;

View File

@@ -79,7 +79,6 @@ 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,
@@ -113,7 +112,6 @@ 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 =>
@@ -1107,10 +1105,6 @@ 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.");

View File

@@ -200,21 +200,5 @@ 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

View File

@@ -260,13 +260,6 @@ public partial class ApiController
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)
{ {
if (_initialized) return; if (_initialized) return;
@@ -448,12 +441,6 @@ 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

View File

@@ -606,7 +606,6 @@ 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();
@@ -775,6 +774,5 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IL
ServerState = state; ServerState = state;
} }
} }
#pragma warning restore MA0040 #pragma warning restore MA0040

View File

@@ -76,19 +76,6 @@
"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, )",
@@ -246,14 +233,6 @@
"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",
@@ -639,7 +618,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.1, )", "Penumbra.Api": "[5.13.0, )",
"Penumbra.String": "[1.0.7, )" "Penumbra.String": "[1.0.7, )"
} }
}, },