diff --git a/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs new file mode 100644 index 0000000..cd5e57c --- /dev/null +++ b/LightlessSync/Interop/InteropModel/Enums/ResidentialAetheryteKind.cs @@ -0,0 +1,11 @@ +namespace Lifestream.Enums; + +public enum ResidentialAetheryteKind +{ + None = -1, + 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..0243e59 --- /dev/null +++ b/LightlessSync/Interop/Ipc/IpcCallerLifestream.cs @@ -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 _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; + 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) + { + _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"); + _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(); + } + + 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(); + } +} \ 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 9b0e8c7..9e80ff7 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -373,6 +373,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(), @@ -383,7 +388,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 d1db351..d6b435f 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; @@ -1249,7 +1250,6 @@ public class SettingsUi : WindowMediatorSubscriberBase ImGui.TreePop(); } -#endif if (_uiShared.IconTextButton(FontAwesomeIcon.Copy, "[DEBUG] Copy Last created Character Data to clipboard")) { if (LastCreatedCharacterData != null) @@ -1265,6 +1265,39 @@ 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.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(), (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."); 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, )" } },