From fb58d8657d251ca3493fe456f992c8da1ef5f537 Mon Sep 17 00:00:00 2001 From: defnotken Date: Tue, 30 Dec 2025 23:43:22 -0600 Subject: [PATCH 1/3] Lifestream IPC witrh Debug Example --- .../Enums/ResidentialAetheryteKind.cs | 10 ++ .../Interop/InteropModel/GlobalModels.cs | 1 + .../Interop/Ipc/IpcCallerLifestream.cs | 93 +++++++++++++++++++ LightlessSync/Interop/Ipc/IpcManager.cs | 7 +- LightlessSync/Plugin.cs | 9 +- LightlessSync/UI/SettingsUi.cs | 32 +++++++ LightlessSync/UI/UISharedService.cs | 6 ++ 7 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs create mode 100644 LightlessSync/Interop/InteropModel/GlobalModels.cs create mode 100644 LightlessSync/Interop/Ipc/IpcCallerLifestream.cs diff --git a/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs new file mode 100644 index 0000000..af7c18e --- /dev/null +++ b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs @@ -0,0 +1,10 @@ +namespace Lifestream.Enums; + +public enum ResidentialAetheryteKind +{ + Uldah = 9, + Gridania = 2, + Limsa = 8, + Foundation = 70, + Kugane = 111, +} \ No newline at end of file diff --git a/LightlessSync/Interop/InteropModel/GlobalModels.cs b/LightlessSync/Interop/InteropModel/GlobalModels.cs new file mode 100644 index 0000000..a02cbc6 --- /dev/null +++ b/LightlessSync/Interop/InteropModel/GlobalModels.cs @@ -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); \ No newline at end of file diff --git a/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs b/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs new file mode 100644 index 0000000..2ced314 --- /dev/null +++ b/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs @@ -0,0 +1,93 @@ +using Dalamud.Plugin; +using Dalamud.Plugin.Ipc; +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 _executeLifestreamCommand; + private readonly ICallGateSubscriber _isHere; + private readonly ICallGateSubscriber _goToHousingAddress; + private readonly ICallGateSubscriber _isBusy; + private readonly ICallGateSubscriber _abort; + private readonly ICallGateSubscriber _changeWorld; + private readonly ICallGateSubscriber _changeWorldById; + private readonly ICallGateSubscriber _aetheryteTeleport; + private readonly ICallGateSubscriber _aetheryteTeleportById; + + public IpcCallerLifestream(IDalamudPluginInterface pi, LightlessMediator lightlessMediator, ILogger logger) + : base(logger, lightlessMediator, pi, LifestreamDescriptor) + { + _executeLifestreamCommand = pi.GetIpcSubscriber("Lifestream.ExecuteCommand"); + _isHere = pi.GetIpcSubscriber("Lifestream.IsHere"); + _goToHousingAddress = pi.GetIpcSubscriber("Lifestream.GoToHousingAddress"); + _isBusy = pi.GetIpcSubscriber("Lifestream.IsBusy"); + _abort = pi.GetIpcSubscriber("Lifestream.Abort"); + _changeWorld = pi.GetIpcSubscriber("Lifestream.ChangeWorld"); + _changeWorldById = pi.GetIpcSubscriber("Lifestream.ChangeWorldById"); + _aetheryteTeleport = pi.GetIpcSubscriber("Lifestream.AetheryteTeleport"); + _aetheryteTeleportById = pi.GetIpcSubscriber("Lifestream.AetheryteTeleportById"); + 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); + } +} \ No newline at end of file diff --git a/LightlessSync/Interop/Ipc/IpcManager.cs b/LightlessSync/Interop/Ipc/IpcManager.cs index 59d17c7..f77b084 100644 --- a/LightlessSync/Interop/Ipc/IpcManager.cs +++ b/LightlessSync/Interop/Ipc/IpcManager.cs @@ -7,7 +7,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase { public IpcManager(ILogger logger, LightlessMediator mediator, 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; Heels = heelsIpc; @@ -17,6 +18,7 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase Moodles = moodlesIpc; PetNames = ipcCallerPetNames; Brio = ipcCallerBrio; + Lifestream = ipcCallerLifestream; if (Initialized) { @@ -44,8 +46,8 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase public IpcCallerPenumbra Penumbra { get; } public IpcCallerMoodles Moodles { get; } public IpcCallerPetNames PetNames { get; } - public IpcCallerBrio Brio { get; } + public IpcCallerLifestream Lifestream { get; } private void PeriodicApiStateCheck() { @@ -58,5 +60,6 @@ public sealed partial class IpcManager : DisposableMediatorSubscriberBase Moodles.CheckAPI(); PetNames.CheckAPI(); Brio.CheckAPI(); + Lifestream.CheckAPI(); } } \ No newline at end of file diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 2d46b43..e0b31b9 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -372,6 +372,11 @@ public sealed class Plugin : IDalamudPlugin sp.GetRequiredService(), sp.GetRequiredService())); + services.AddSingleton(sp => new IpcCallerLifestream( + pluginInterface, + sp.GetRequiredService(), + sp.GetRequiredService>())); + services.AddSingleton(sp => new IpcManager( sp.GetRequiredService>(), sp.GetRequiredService(), @@ -382,7 +387,9 @@ public sealed class Plugin : IDalamudPlugin sp.GetRequiredService(), sp.GetRequiredService(), sp.GetRequiredService(), - sp.GetRequiredService())); + sp.GetRequiredService(), + sp.GetRequiredService() + )); // Notifications / HTTP services.AddSingleton(sp => new NotificationService( diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 1c86580..365b010 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -5,6 +5,7 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Utility; +using Lifestream.Enums; using LightlessSync.API.Data; using LightlessSync.API.Data.Comparer; using LightlessSync.API.Data.Enum; @@ -1259,6 +1260,37 @@ public class SettingsUi : WindowMediatorSubscriberBase UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server."); + if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to Limsa [LIFESTREAM TEST]")) + { + _ipcManager.Lifestream.ExecuteLifestreamCommand("limsa"); + } + + if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to JoyHouse [LIFESTREAM TEST]")) + { + 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); + } + _uiShared.DrawCombo("Log Level", Enum.GetValues(), (l) => l.ToString(), (l) => { _configService.Current.LogLevel = l; diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index fc5225c..514f31e 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -79,6 +79,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase private readonly Dictionary _oauthTokenExpiry = []; private bool _penumbraExists = false; private bool _petNamesExists = false; + private bool _lifestreamExists = false; private int _serverSelectionIndex = -1; public UiSharedService(ILogger logger, IpcManager ipcManager, ApiController apiController, CacheMonitor cacheMonitor, FileDialogManager fileDialogManager, @@ -112,6 +113,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase _moodlesExists = _ipcManager.Moodles.APIAvailable; _petNamesExists = _ipcManager.PetNames.APIAvailable; _brioExists = _ipcManager.Brio.APIAvailable; + _lifestreamExists = _ipcManager.Lifestream.APIAvailable; }); UidFont = _pluginInterface.UiBuilder.FontAtlas.NewDelegateFontHandle(e => @@ -1105,6 +1107,10 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase ColorText("Brio", GetBoolColor(_brioExists)); AttachToolTip(BuildPluginTooltip("Brio", _brioExists, _ipcManager.Brio.State)); + ImGui.SameLine(); + ColorText("Lifestream", GetBoolColor(_lifestreamExists)); + AttachToolTip(BuildPluginTooltip("Lifestream", _lifestreamExists, _ipcManager.Lifestream.State)); + 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."); From df33a0f0a2f8ab6e8bd92e7be0aeb7d6c54c6309 Mon Sep 17 00:00:00 2001 From: defnotken Date: Thu, 1 Jan 2026 17:27:12 -0600 Subject: [PATCH 2/3] Move buttons to debug --- LightlessSync/UI/SettingsUi.cs | 9 +++++---- LightlessSync/packages.lock.json | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 365b010..cb03cc5 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1244,7 +1244,6 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.TreePop(); } -#endif if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard")) { if (LastCreatedCharacterData != null) @@ -1260,12 +1259,12 @@ public class SettingsUi : WindowMediatorSubscriberBase UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server."); - if (_uiShared.IconTextButton(FontAwesomeIcon.Home, "Teleport to Limsa [LIFESTREAM TEST]")) + 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]")) + 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)); @@ -1281,7 +1280,7 @@ public class SettingsUi : WindowMediatorSubscriberBase PropertyType: 0, Plot: plot, Apartment: 1, - ApartmentSubdivision: false, + ApartmentSubdivision: false, AliasEnabled: false, Alias: "" ); @@ -1290,6 +1289,8 @@ public class SettingsUi : WindowMediatorSubscriberBase _ipcManager.Lifestream.GoToHousingAddress(addressEntry); } +#endif + _uiShared.DrawCombo("Log Level", Enum.GetValues(), (l) => l.ToString(), (l) => { diff --git a/LightlessSync/packages.lock.json b/LightlessSync/packages.lock.json index d47880c..45d7722 100644 --- a/LightlessSync/packages.lock.json +++ b/LightlessSync/packages.lock.json @@ -76,6 +76,19 @@ "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": { "type": "Direct", "requested": "[10.0.1, )", @@ -233,6 +246,14 @@ "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": { "type": "Transitive", "resolved": "10.0.1", @@ -618,7 +639,7 @@ "FlatSharp.Compiler": "[7.9.0, )", "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.13.0, )", + "Penumbra.Api": "[5.13.1, )", "Penumbra.String": "[1.0.7, )" } }, From de9c9955efc838702da407471b9b0be753b76343 Mon Sep 17 00:00:00 2001 From: defnotken Date: Sun, 4 Jan 2026 00:54:40 -0600 Subject: [PATCH 3/3] add more functionality for future features. --- .../Enums/ResidentialAetheryteKind.cs | 1 + .../Interop/Ipc/IpcCallerLifestream.cs | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs index af7c18e..cd5e57c 100644 --- a/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs +++ b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs @@ -2,6 +2,7 @@ public enum ResidentialAetheryteKind { + None = -1, Uldah = 9, Gridania = 2, Limsa = 8, diff --git a/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs b/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs index 2ced314..0243e59 100644 --- a/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs +++ b/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs @@ -1,5 +1,6 @@ using Dalamud.Plugin; using Dalamud.Plugin.Ipc; +using Lifestream.Enums; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; @@ -20,6 +21,11 @@ public sealed class IpcCallerLifestream : IpcServiceBase private readonly ICallGateSubscriber _changeWorldById; private readonly ICallGateSubscriber _aetheryteTeleport; private readonly ICallGateSubscriber _aetheryteTeleportById; + private readonly ICallGateSubscriber _canChangeInstance; + private readonly ICallGateSubscriber _getCurrentInstance; + private readonly ICallGateSubscriber _getNumberOfInstances; + private readonly ICallGateSubscriber _changeInstance; + private readonly ICallGateSubscriber<(ResidentialAetheryteKind, int, int)> _getCurrentPlotInfo; public IpcCallerLifestream(IDalamudPluginInterface pi, LightlessMediator lightlessMediator, ILogger logger) : base(logger, lightlessMediator, pi, LifestreamDescriptor) @@ -33,6 +39,11 @@ public sealed class IpcCallerLifestream : IpcServiceBase _changeWorldById = pi.GetIpcSubscriber("Lifestream.ChangeWorldById"); _aetheryteTeleport = pi.GetIpcSubscriber("Lifestream.AetheryteTeleport"); _aetheryteTeleportById = pi.GetIpcSubscriber("Lifestream.AetheryteTeleportById"); + _canChangeInstance = pi.GetIpcSubscriber("Lifestream.CanChangeInstance"); + _getCurrentInstance = pi.GetIpcSubscriber("Lifestream.GetCurrentInstance"); + _getNumberOfInstances = pi.GetIpcSubscriber("Lifestream.GetNumberOfInstances"); + _changeInstance = pi.GetIpcSubscriber("Lifestream.ChangeInstance"); + _getCurrentPlotInfo = pi.GetIpcSubscriber<(ResidentialAetheryteKind, int, int)>("Lifestream.GetCurrentPlotInfo"); CheckAPI(); } @@ -84,10 +95,35 @@ public sealed class IpcCallerLifestream : IpcServiceBase 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(); + } } \ No newline at end of file