├── meta ├── config.ini └── front.md ├── Lifestream ├── images │ └── icon.png ├── Enums │ ├── PropertyType.cs │ ├── AetheryteUseState.cs │ ├── BasePositionHorizontal.cs │ ├── BasePositionVertical.cs │ ├── DCVType.cs │ ├── WorldChangeAetheryte.cs │ └── ResidentialAetheryteKind.cs ├── Schedulers │ └── GenericSched.cs ├── Movement │ ├── README.md │ ├── MovementConfig.cs │ ├── PlayerMoveControllerFlyInput.cs │ ├── OverrideAfk.cs │ ├── CameraEx.cs │ └── OverrideCamera.cs ├── Data │ ├── Path.cs │ ├── HouseEnterMode.cs │ ├── SortMode.cs │ ├── LiCommandBehavior.cs │ ├── HousingData.cs │ ├── PlotInfo.cs │ ├── MultiPath.cs │ ├── StaticData.cs │ ├── MultiPathEntry.cs │ ├── CustomAliasKind.cs │ ├── TravelBanInfo.cs │ ├── AutoPropertyData.cs │ ├── HousePathData.cs │ ├── AddressBookFolder.cs │ ├── WotsitIntegrationIncludes.cs │ ├── ZoneDetail.cs │ ├── CustomAlias.cs │ ├── AddressBookEntry.cs │ └── Config.cs ├── Systems │ ├── Residential │ │ ├── ResidentialZoneInfo.cs │ │ ├── ResidentialAetheryte.cs │ │ └── ResidentialAethernet.cs │ ├── IAetheryte.cs │ ├── Legacy │ │ ├── TinyAetheryte.cs │ │ └── DataStore.cs │ └── Custom │ │ └── CustomAetheryte.cs ├── Game │ ├── UnknownStruct.cs │ ├── InputData.cs │ └── Memory.cs ├── IPC │ ├── SplatoonCache.cs │ ├── Wotsit │ │ ├── WotsitEntry.cs │ │ └── WotsitManager.cs │ ├── VnavmeshIPC.cs │ ├── YesAlreadyManager.cs │ ├── IpcUtils.cs │ ├── SplatoonManager.cs │ └── TextAdvanceIPC.cs ├── Tasks │ ├── CrossDC │ │ ├── TaskLogoutAndRelog.cs │ │ ├── TaskSelectChara.cs │ │ ├── TaskLogout.cs │ │ ├── TaskReturnToHomeDC.cs │ │ ├── TaskReturnToHomeWorldCharaSelect.cs │ │ └── TaskChangeDatacenter.cs │ ├── Utility │ │ ├── TaskDesktopNotification.cs │ │ ├── TaskWaitUntilInHomeWorld.cs │ │ ├── TaskWaitUntilInWorld.cs │ │ ├── FlightTasks.cs │ │ ├── TaskRemoveAfkStatus.cs │ │ ├── TaskApproachAetheryteIfNeeded.cs │ │ ├── TaskMoveToHouse.cs │ │ ├── TaskMultipathExecute.cs │ │ ├── TaskMount.cs │ │ └── TaskGeneratePath.cs │ ├── Shortcuts │ │ ├── TaskMBShortcut.cs │ │ └── TaskISShortcut.cs │ ├── TaskSettings.cs │ ├── SameWorld │ │ ├── TaskTpAndWaitForArrival.cs │ │ ├── TaskFirmanentTeleport.cs │ │ ├── TaskTpToResidentialAetheryte.cs │ │ ├── TaskTpToAethernetDestination.cs │ │ ├── TaskAethernetTeleport.cs │ │ ├── TaskReturnToGateway.cs │ │ ├── TaskApproachAndInteractWithApartmentEntrance.cs │ │ ├── TaskApproachHousingAetheryte.cs │ │ ├── TaskGoToResidentialDistrict.cs │ │ ├── TaskChangeInstance.cs │ │ └── TaskAetheryteAethernetTeleport.cs │ ├── Login │ │ └── TaskConnectAndOpenCharaSelect.cs │ └── CrossWorld │ │ ├── TaskEnforceWorld.cs │ │ ├── TaskTPAndChangeWorld.cs │ │ └── TaskChangeWorld.cs ├── Colors.cs ├── Services │ ├── HttpClientProvider.cs │ ├── NetworkDebugger.cs │ ├── CustomAliasFileSystemManager.cs │ ├── DtrManager.cs │ ├── !Service.cs │ ├── TeleportService.cs │ ├── ContextMenuManager.cs │ ├── InstanceHandler.cs │ ├── AddressBookFileSystemManager.cs │ └── TerritoryWatcher.cs ├── Lifestream.json ├── CSExtensions │ └── AddonAreaMapExtensions.cs ├── GUI │ ├── MainGui.cs │ ├── Windows │ │ ├── GameCloseWindow.cs │ │ └── SelectWorldWindow.cs │ ├── UtilsUI.cs │ ├── TabUtility.cs │ ├── UIServiceAccount.cs │ ├── ProgressOverlay.cs │ ├── TabTravelBan.cs │ └── InputWardDetailDialog.cs ├── AtkReaders │ ├── ReaderMansionSelectRoom.cs │ ├── ReaderTelepotTown.cs │ └── ReaderLobbyDKTWorldList.cs ├── Global.cs ├── Paissa │ └── PaissaData.cs ├── StaticData.json └── Lifestream.csproj ├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── Trigger Update Pluginmaster.yml │ └── TriggerUpdateMetadata.yml ├── .gitmodules ├── CONTRIBUTING.md ├── .gitattributes └── README.md /meta/config.ini: -------------------------------------------------------------------------------- 1 | donate=full 2 | front 3 | state=active 4 | install=normal 5 | -------------------------------------------------------------------------------- /Lifestream/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightmareXIV/Lifestream/HEAD/Lifestream/images/icon.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # IDE0051: Remove unused private members 4 | dotnet_diagnostic.IDE0051.severity = none 5 | -------------------------------------------------------------------------------- /Lifestream/Enums/PropertyType.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | public enum PropertyType 3 | { 4 | House, Apartment 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Schedulers/GenericSched.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Schedulers; 2 | 3 | internal static unsafe class GenericSched 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Movement/README.md: -------------------------------------------------------------------------------- 1 | Movement code is authored by awgil (https://github.com/awgil) 2 | https://github.com/awgil/ffxiv_navmesh 3 | -------------------------------------------------------------------------------- /Lifestream/Data/Path.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | [Serializable] 3 | public class Path 4 | { 5 | public List Points = []; 6 | } 7 | -------------------------------------------------------------------------------- /Lifestream/Enums/AetheryteUseState.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | public enum AetheryteUseState 3 | { 4 | None, Normal, Residential, Custom 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Enums/BasePositionHorizontal.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | 3 | public enum BasePositionHorizontal 4 | { 5 | Middle, Left, Right 6 | } 7 | -------------------------------------------------------------------------------- /Lifestream/Enums/BasePositionVertical.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | 3 | public enum BasePositionVertical 4 | { 5 | Middle, Top, Bottom 6 | } 7 | -------------------------------------------------------------------------------- /Lifestream/Enums/DCVType.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | 3 | internal enum DCVType 4 | { 5 | Unknown, HomeToGuest, GuestToHome, GuestToGuest 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: NightmareXIV 4 | ko_fi: nightmarexiv 5 | custom: crypto.nightmarexiv.com 6 | -------------------------------------------------------------------------------- /Lifestream/Data/HouseEnterMode.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | public enum HouseEnterMode 3 | { 4 | None, Walk_to_door, Enter_house, Enter_workshop 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Enums/WorldChangeAetheryte.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | 3 | public enum WorldChangeAetheryte 4 | { 5 | Uldah = 9, 6 | Gridania = 2, 7 | Limsa = 8, 8 | } 9 | -------------------------------------------------------------------------------- /Lifestream/Data/SortMode.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | public enum SortMode 3 | { 4 | Manual, Name, NameReversed, World, WorldReversed, Ward, WardReversed, Plot, PlotReversed, 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Data/LiCommandBehavior.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | public enum LiCommandBehavior 3 | { 4 | Return_to_Home_World, Open_World_Change_Menu, Open_Configuration, Do_Nothing 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Data/HousingData.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | 3 | namespace Lifestream.Data; 4 | public class HousingData : IEzConfig 5 | { 6 | public Dictionary> Data = []; 7 | } 8 | -------------------------------------------------------------------------------- /Lifestream/Data/PlotInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | [Serializable] 3 | public class PlotInfo 4 | { 5 | public uint AethernetID; 6 | public Vector3 Front; 7 | public List Path = []; 8 | } 9 | -------------------------------------------------------------------------------- /Lifestream/Data/MultiPath.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | [Serializable] 3 | public class MultiPath 4 | { 5 | internal Guid GUID = Guid.NewGuid(); 6 | public string Name = ""; 7 | public List Entries = []; 8 | } 9 | -------------------------------------------------------------------------------- /Lifestream/Enums/ResidentialAetheryteKind.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Enums; 2 | 3 | public enum ResidentialAetheryteKind 4 | { 5 | Uldah = 9, 6 | Gridania = 2, 7 | Limsa = 8, 8 | Foundation = 70, 9 | Kugane = 111, 10 | } 11 | -------------------------------------------------------------------------------- /Lifestream/Systems/Residential/ResidentialZoneInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Systems.Residential; 2 | public class ResidentialZoneInfo 3 | { 4 | public List Aetherytes = []; 5 | public Vector2 SubdivisionModifier; 6 | } 7 | -------------------------------------------------------------------------------- /Lifestream/Systems/IAetheryte.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Systems; 2 | public interface IAetheryte 3 | { 4 | Vector2 Position { get; set; } 5 | uint TerritoryType { get; set; } 6 | uint ID { get; set; } 7 | string Name { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /Lifestream/Game/UnknownStruct.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Game; 2 | 3 | [StructLayout(LayoutKind.Explicit, Size = 0x40)] 4 | internal unsafe struct UnknownStruct 5 | { 6 | [FieldOffset(4)] public byte unk_4; 7 | [FieldOffset(8)] public int SelectedItem; 8 | } 9 | -------------------------------------------------------------------------------- /Lifestream/Data/StaticData.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | 3 | namespace Lifestream.Data; 4 | 5 | public class StaticData : IEzConfig 6 | { 7 | public Dictionary CustomPositions = []; 8 | public Dictionary SortOrder = []; 9 | } 10 | -------------------------------------------------------------------------------- /Lifestream/Data/MultiPathEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | [Serializable] 3 | public class MultiPathEntry 4 | { 5 | internal Guid GUID = Guid.NewGuid(); 6 | public uint Territory; 7 | public bool Sprint = false; 8 | public List Points = []; 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/Trigger Update Pluginmaster.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Update Pluginmaster 2 | 3 | on: 4 | release: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | TriggerUpdatePluginmaster: 9 | uses: 10 | NightmareXIV/MyDalamudPlugins/.github/workflows/Trigger_Update_Pluginmaster.yml@main 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /.github/workflows/TriggerUpdateMetadata.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Update Metadata 2 | on: 3 | push: 4 | paths: 5 | - meta/** 6 | workflow_dispatch: 7 | 8 | jobs: 9 | TriggerUpdateMetadata: 10 | uses: 11 | NightmareXIV/MyDalamudPlugins/.github/workflows/Update_Metadata.yml@main 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /Lifestream/IPC/SplatoonCache.cs: -------------------------------------------------------------------------------- 1 | using ECommons.SplatoonAPI; 2 | 3 | namespace Lifestream.IPC; 4 | public class SplatoonCache 5 | { 6 | public List WaymarkLineCache = []; 7 | public int WaymarkLinePos = 0; 8 | public List WaymarkPointCache = []; 9 | public int WaymarkPointPos = 0; 10 | } 11 | -------------------------------------------------------------------------------- /Lifestream/Data/CustomAliasKind.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | public enum CustomAliasKind 3 | { 4 | Teleport_to_Aetheryte, Move_to_point, Navmesh_to_point, Change_world, Use_Aethernet, Circular_movement, Interact, Mount_Up, Select_Yes, Select_List_Option, Confirm_Contents_Finder, Wait_for_Transition, Return_to_Home_World 5 | } 6 | -------------------------------------------------------------------------------- /Lifestream/Data/TravelBanInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | [Serializable] 3 | public class TravelBanInfo 4 | { 5 | internal string ID = Guid.NewGuid().ToString(); 6 | public bool IsEnabled = true; 7 | public string CharaName = ""; 8 | public int CharaHomeWorld = 0; 9 | public List BannedFrom = []; 10 | public List BannedTo = []; 11 | } 12 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskLogoutAndRelog.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Schedulers; 2 | 3 | namespace Lifestream.Tasks.CrossDC; 4 | 5 | internal static class TaskLogoutAndRelog 6 | { 7 | internal static void Enqueue(string nameWithWorld) 8 | { 9 | TaskLogout.Enqueue(); 10 | P.TaskManager.Enqueue(DCChange.TitleScreenClickStart, TaskSettings.Timeout1M); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskDesktopNotification.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Tasks; 2 | 3 | internal static unsafe class TaskDesktopNotification 4 | { 5 | internal static void Enqueue(string s) 6 | { 7 | P.TaskManager.Enqueue(() => 8 | { 9 | if(CSFramework.Instance()->WindowInactive) 10 | { 11 | Utils.TryNotify(s); 12 | } 13 | }, "TaskNotify"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Lifestream/Colors.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream; 2 | public static class Colors 3 | { 4 | public static readonly Vector4 TabBlue = ImGuiEx.Vector4FromRGB(0xade7ff, 0.75f); 5 | public static readonly Vector4 TabGreen = ImGuiEx.Vector4FromRGB(0xadffd9, 0.75f); 6 | public static readonly Vector4 TabYellow = ImGuiEx.Vector4FromRGB(0xfaffad, 0.75f); 7 | public static readonly Vector4 TabPurple = ImGuiEx.Vector4FromRGB(0xffadfa, 0.75f); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Lifestream/Data/AutoPropertyData.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Tasks.Shortcuts; 2 | 3 | namespace Lifestream.Data; 4 | public class AutoPropertyData 5 | { 6 | public bool Enabled = true; 7 | public TaskPropertyShortcut.PropertyType Type; 8 | 9 | public AutoPropertyData() { } 10 | 11 | public AutoPropertyData(bool enabled, TaskPropertyShortcut.PropertyType type) 12 | { 13 | Enabled = enabled; 14 | Type = type; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Lifestream/Movement/MovementConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Lifestream.Movement; 8 | public unsafe sealed class MovementConfig 9 | { 10 | public bool StopOnStuck = false; 11 | public float StuckTolerance = 0.05f; 12 | public int StuckTimeoutMs = 500; 13 | public bool CancelMoveOnUserInput = false; 14 | public bool AlignCameraToMovement = false; 15 | } -------------------------------------------------------------------------------- /Lifestream/Movement/PlayerMoveControllerFlyInput.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Movement; 2 | 3 | [StructLayout(LayoutKind.Explicit, Size = 0x18)] 4 | public unsafe struct PlayerMoveControllerFlyInput 5 | { 6 | [FieldOffset(0x0)] public float Forward; 7 | [FieldOffset(0x4)] public float Left; 8 | [FieldOffset(0x8)] public float Up; 9 | [FieldOffset(0xC)] public float Turn; 10 | [FieldOffset(0x10)] public float u10; 11 | [FieldOffset(0x14)] public byte DirMode; 12 | [FieldOffset(0x15)] public byte HaveBackwardOrStrafe; 13 | } -------------------------------------------------------------------------------- /Lifestream/Services/HttpClientProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | 3 | namespace Lifestream.Services; 4 | public class HttpClientProvider : IDisposable 5 | { 6 | private HttpClient Client; 7 | private HttpClientProvider() 8 | { 9 | } 10 | 11 | public void Dispose() 12 | { 13 | Client?.Dispose(); 14 | } 15 | 16 | public HttpClient Get() 17 | { 18 | Client ??= new() 19 | { 20 | Timeout = TimeSpan.FromSeconds(30) 21 | }; 22 | return Client; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Lifestream/Data/HousePathData.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Enums; 2 | 3 | namespace Lifestream.Data; 4 | [Serializable] 5 | public class HousePathData 6 | { 7 | public ResidentialAetheryteKind ResidentialDistrict; 8 | public int Ward; 9 | public int Plot; 10 | public List PathToEntrance = []; 11 | public List PathToWorkshop = []; 12 | public bool IsPrivate; 13 | public ulong CID; 14 | public bool EnableHouseEnterModeOverride = false; 15 | public HouseEnterMode EnterModeOverride = HouseEnterMode.None; 16 | } 17 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Shortcuts/TaskMBShortcut.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Types; 2 | using ECommons.GameFunctions; 3 | using ECommons.GameHelpers; 4 | using ECommons.Throttlers; 5 | using FFXIVClientStructs.FFXIV.Client.Game.Control; 6 | using Lifestream.Data; 7 | using Lifestream.Tasks.SameWorld; 8 | using Lumina.Excel.Sheets; 9 | 10 | namespace Lifestream.Tasks.Shortcuts; 11 | public static unsafe class TaskMBShortcut 12 | { 13 | public static void Enqueue() 14 | { 15 | StaticAlias.UldahMarketboard.Enqueue(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskWaitUntilInHomeWorld.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Schedulers; 3 | 4 | namespace Lifestream.Tasks; 5 | 6 | internal static class TaskWaitUntilInHomeWorld 7 | { 8 | internal static void Enqueue() 9 | { 10 | P.TaskManager.Enqueue(() => Player.Available && Player.IsInHomeWorld, "Waiting until player returns to home world", TaskSettings.TimeoutInfinite); 11 | P.TaskManager.Enqueue(DCChange.WaitUntilNotBusy, "Waiting until player is not busy (TaskWaitUntilInHomeWorld)", TaskSettings.Timeout1M); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /meta/front.md: -------------------------------------------------------------------------------- 1 | ## Key features 2 | - Visit another world with one command, even if on another data center 3 | - One-click to travel between aetherytes 4 | - Quickly switch between area instances 5 | - Use shortcuts to go to your home, fc house, apartment, grand company, market board 6 | - Create address books where you can store residential addresses of your friends or venues and go there at any time with one click 7 | 8 | ## Optional dependencies 9 | - [vnavmesh](https://github.com/awgil/ffxiv_navmesh) from `https://puni.sh/api/repository/veyn` for pathing to your grand company automatically 10 | -------------------------------------------------------------------------------- /Lifestream/Movement/OverrideAfk.cs: -------------------------------------------------------------------------------- 1 | using ECommons.CSExtensions; 2 | using FFXIVClientStructs.FFXIV.Client.UI; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Lifestream.Movement; 10 | internal unsafe static class OverrideAFK 11 | { 12 | public static void ResetTimers() 13 | { 14 | var module = UIModule.Instance()->GetInputTimerModule(); 15 | module->AfkTimer = 0; 16 | module->ContentInputTimer = 0; 17 | module->InputTimer = 0; 18 | module->Unk1C = 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskSelectChara.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using Lifestream.Tasks.Login; 3 | 4 | namespace Lifestream.Tasks.CrossDC; 5 | 6 | internal class TaskSelectChara 7 | { 8 | internal static unsafe void Enqueue(string charaName, uint charaHomeWorld, uint currentWorld) 9 | { 10 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 11 | P.TaskManager.Enqueue(() => TaskChangeCharacter.SelectCharacter(charaName, ExcelWorldHelper.GetName(charaHomeWorld), ExcelWorldHelper.GetName(currentWorld))); 12 | P.TaskManager.Enqueue(TaskChangeCharacter.ConfirmLogin); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Lifestream/Lifestream.json: -------------------------------------------------------------------------------- 1 | { 2 | "Author": "NightmareXIV", 3 | "Name": "Lifestream", 4 | "InternalName": "Lifestream", 5 | "Description": "- One click to teleport to any world or aethernet destination\n- Click on aetheryte shards inside zone map to teleport to them", 6 | "ApplicableVersion": "any", 7 | "Punchline": "A plugin to speed up Aethernet travel and World changing.", 8 | "RepoUrl": "https://github.com/NightmareXIV/Lifestream", 9 | "AcceptsFeedback": false, 10 | "IconUrl": "https://raw.githubusercontent.com/NightmareXIV/MyDalamudPlugins/main/icons/lifestream.png", 11 | "DalamudApiLevel": 14 12 | } -------------------------------------------------------------------------------- /Lifestream/Game/InputData.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Game; 2 | 3 | [StructLayout(LayoutKind.Explicit, Size = 0x40)] 4 | internal unsafe struct InputData 5 | { 6 | [FieldOffset(0)] internal fixed byte RawDump[0x40]; 7 | [FieldOffset(8)] internal nint* unk_8; 8 | [FieldOffset(16)] internal int unk_16; 9 | [FieldOffset(24)] internal byte unk_24; 10 | 11 | internal UnknownStruct* unk_8s => (UnknownStruct*)*unk_8; 12 | 13 | internal readonly Span RawDumpSpan 14 | { 15 | get 16 | { 17 | fixed(byte* ptr = RawDump) 18 | { 19 | return new Span(ptr, sizeof(InputData)); 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Lifestream/Movement/CameraEx.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Movement; 2 | 3 | [StructLayout(LayoutKind.Explicit, Size = 0x2B0)] 4 | public unsafe struct CameraEx 5 | { 6 | [FieldOffset(0x140)] public float DirH; // 0 is north, increases CW 7 | [FieldOffset(0x144)] public float DirV; // 0 is horizontal, positive is looking up, negative looking down 8 | [FieldOffset(0x148)] public float InputDeltaHAdjusted; 9 | [FieldOffset(0x14C)] public float InputDeltaVAdjusted; 10 | [FieldOffset(0x150)] public float InputDeltaH; 11 | [FieldOffset(0x154)] public float InputDeltaV; 12 | [FieldOffset(0x158)] public float DirVMin; // -85deg by default 13 | [FieldOffset(0x15C)] public float DirVMax; // +45deg by default 14 | } 15 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskLogout.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Schedulers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Lifestream.Tasks.CrossDC; 9 | public static class TaskLogout 10 | { 11 | public static void Enqueue() 12 | { 13 | P.TaskManager.Enqueue(DCChange.WaitUntilNotBusy, TaskSettings.Timeout1M); 14 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 15 | P.TaskManager.Enqueue(DCChange.Logout); 16 | P.TaskManager.Enqueue(DCChange.SelectYesLogout, TaskSettings.Timeout1M); 17 | P.TaskManager.Enqueue(DCChange.WaitUntilCanAutoLogin, TaskSettings.Timeout2M); 18 | } 19 | } -------------------------------------------------------------------------------- /Lifestream/Tasks/TaskSettings.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation.NeoTaskManager; 2 | 3 | namespace Lifestream.Tasks; 4 | public static class TaskSettings 5 | { 6 | public static readonly TaskManagerConfiguration Timeout1M = new(timeLimitMS: 60000); 7 | public static readonly TaskManagerConfiguration Timeout2M = new(timeLimitMS: 60000 * 2); 8 | public static readonly TaskManagerConfiguration Timeout5M = new(timeLimitMS: 60000 * 5); 9 | public static readonly TaskManagerConfiguration Timeout15M = new(timeLimitMS: 60000 * 15); 10 | public static readonly TaskManagerConfiguration Timeout60M = new(timeLimitMS: 60000 * 60); 11 | public static readonly TaskManagerConfiguration TimeoutInfinite = new(timeLimitMS: int.MaxValue); 12 | } 13 | -------------------------------------------------------------------------------- /Lifestream/Data/AddressBookFolder.cs: -------------------------------------------------------------------------------- 1 | using NightmareUI.OtterGuiWrapper.FileSystems.Generic; 2 | 3 | namespace Lifestream.Data; 4 | [Serializable] 5 | public class AddressBookFolder : IFileSystemStorage 6 | { 7 | internal bool IsCopy = false; 8 | public string ExportedName = ""; 9 | public Guid GUID { get; set; } = Guid.NewGuid(); 10 | public List Entries = []; 11 | public bool IsDefault = false; 12 | public SortMode SortMode = SortMode.Manual; 13 | 14 | public bool ShouldSerializeGUID() => !IsCopy; 15 | public bool ShouldSerializeIsDefault() => !IsCopy; 16 | public bool ShouldSerializeExportedName() => IsCopy; 17 | 18 | public string GetCustomName() => null; 19 | public void SetCustomName(string s) { } 20 | } 21 | -------------------------------------------------------------------------------- /Lifestream/CSExtensions/AddonAreaMapExtensions.cs: -------------------------------------------------------------------------------- 1 | using FFXIVClientStructs.FFXIV.Client.UI; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Lifestream.CSExtensions; 9 | public static unsafe class AddonAreaMapExtensions 10 | { 11 | extension(AddonAreaMap addon) 12 | { 13 | private short* HoveredCoordsPtr => (short*)((nint)(&addon) + 1968); 14 | private float HoveredX => (float)addon.HoveredCoordsPtr[0] + (float)addon.HoveredCoordsPtr[1] / 10f; 15 | private float HoveredY => (float)addon.HoveredCoordsPtr[2] + (float)addon.HoveredCoordsPtr[3] / 10f; 16 | public Vector2 HoveredCoords => new(addon.HoveredX, addon.HoveredY); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ECommons"] 2 | path = ECommons 3 | url = https://github.com/NightmareXIV/ECommons 4 | [submodule "ClickLib"] 5 | path = ClickLib 6 | url = https://github.com/Limiana/ClickLib 7 | [submodule "AutoRetainerAPI"] 8 | path = AutoRetainerAPI 9 | url = https://github.com/PunishXIV/AutoRetainerAPI 10 | [submodule "OtterGui"] 11 | path = OtterGui 12 | url = https://github.com/Limiana/OtterGui 13 | [submodule "NightmareUI"] 14 | path = NightmareUI 15 | url = https://github.com/NightmareXIV/NightmareUI 16 | [submodule "NightmareUI.OtterGuiWrapper"] 17 | path = NightmareUI.OtterGuiWrapper 18 | url = https://github.com/NightmareXIV/NightmareUI.OtterGuiWrapper 19 | [submodule "FFXIVClientStructs"] 20 | path = FFXIVClientStructs 21 | url = https://github.com/Limiana/FFXIVClientStructs 22 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskTpAndWaitForArrival.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Schedulers; 3 | 4 | namespace Lifestream.Tasks.SameWorld; 5 | public static class TaskTpAndWaitForArrival 6 | { 7 | public static void Enqueue(uint aetheryte) 8 | { 9 | P.TaskManager.EnqueueMulti( 10 | new(() => Player.Interactable && IsScreenReady(), "WaitUntilPlayerInteractable"), 11 | new(() => WorldChange.ExecuteTPToAethernetDestination(aetheryte), $"ExecuteTPToAethernetDestination({aetheryte})"), 12 | new(() => Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51], "WaitUntilBetweenAreas"), 13 | new(() => Player.Interactable, "WaitUntilPlayerInteractable", TaskSettings.Timeout2M) 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskFirmanentTeleport.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using ECommons.Throttlers; 3 | using Lifestream.Schedulers; 4 | 5 | namespace Lifestream.Tasks.SameWorld; 6 | 7 | internal static class TaskFirmanentTeleport 8 | { 9 | internal static void Enqueue() 10 | { 11 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 12 | P.TaskManager.Enqueue(WorldChange.TargetValidAetheryte); 13 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 14 | P.TaskManager.Enqueue(() => 15 | { 16 | if(!Player.Available) return false; 17 | return Utils.TrySelectSpecificEntry(Lang.TravelToFirmament, () => EzThrottler.Throttle("SelectString")); 18 | }, $"TeleportToFirmamentSelect {Lang.TravelToFirmament}"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Lifestream/GUI/MainGui.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Funding; 2 | 3 | namespace Lifestream.GUI; 4 | 5 | internal static unsafe class MainGui 6 | { 7 | internal static void Draw() 8 | { 9 | PatreonBanner.DrawRight(); 10 | ImGuiEx.EzTabBar("LifestreamTabs", PatreonBanner.Text, 11 | ("Address Book", TabAddressBook.Draw, null, true), 12 | ("House Registration", UIHouseReg.Draw, null, true), 13 | ("Custom Alias", TabCustomAlias.Draw, null, true), 14 | ("Utility", TabUtility.Draw, null, true), 15 | ("Settings", UISettings.Draw, null, true), 16 | ("Help", DrawHelp, null, true), 17 | ("Debug", UIDebug.Draw, ImGuiColors.DalamudGrey3, true) 18 | ); 19 | } 20 | 21 | private static void DrawHelp() 22 | { 23 | ImGuiEx.TextWrapped(Lang.Help); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskWaitUntilInWorld.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using ECommons.GameHelpers; 3 | 4 | namespace Lifestream.Tasks; 5 | 6 | internal static class TaskWaitUntilInWorld 7 | { 8 | internal static void Enqueue(string world, bool checkDc) 9 | { 10 | P.TaskManager.Enqueue(() => Task(world, checkDc), nameof(TaskWaitUntilInWorld), TaskSettings.TimeoutInfinite); 11 | } 12 | 13 | internal static bool Task(string world, bool checkDc) 14 | { 15 | if(checkDc) 16 | { 17 | if(Player.Available && ExcelWorldHelper.Get(world)?.DataCenter.RowId == Svc.ClientState.LocalPlayer.CurrentWorld.Value.DataCenter.RowId) 18 | { 19 | return true; 20 | } 21 | } 22 | if(Player.Available && Player.CurrentWorld == world) 23 | { 24 | return true; 25 | } 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/FlightTasks.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation; 2 | using ECommons.Throttlers; 3 | using FFXIVClientStructs.FFXIV.Client.Game; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Lifestream.Tasks.Utility; 11 | public static unsafe class FlightTasks 12 | { 13 | public static bool? FlyIfCan() 14 | { 15 | if(Svc.Condition[ConditionFlag.InFlight]) 16 | { 17 | return true; 18 | } 19 | if(Utils.CanFly()) 20 | { 21 | if(ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 2) == 0 && EzThrottler.Throttle("Jump", 100)) 22 | { 23 | Chat.ExecuteGeneralAction(2); 24 | } 25 | } 26 | else 27 | { 28 | return null; 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskTpToResidentialAetheryte.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Enums; 3 | using Lifestream.Schedulers; 4 | 5 | namespace Lifestream.Tasks.SameWorld; 6 | public static class TaskTpToResidentialAetheryte 7 | { 8 | public static void Insert(ResidentialAetheryteKind target) 9 | { 10 | P.TaskManager.Insert(() => Player.Interactable && P.Territory == target.GetTerritory(), "WaitUntilPlayerInteractable", TaskSettings.Timeout2M); 11 | P.TaskManager.Insert(WorldChange.WaitUntilNotBusy, TaskSettings.Timeout2M); 12 | P.TaskManager.Insert(() => Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51], "WaitUntilBetweenAreas"); 13 | P.TaskManager.Insert(() => WorldChange.ExecuteTPToAethernetDestination((uint)target), $"ExecuteTPToAethernetDestination {target}"); 14 | if(C.WaitForScreenReady) P.TaskManager.Insert(Utils.WaitForScreen); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskTpToAethernetDestination.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Enums; 3 | using Lifestream.Schedulers; 4 | 5 | namespace Lifestream.Tasks.SameWorld; 6 | 7 | internal static class TaskTpToAethernetDestination 8 | { 9 | internal static void Enqueue(WorldChangeAetheryte worldChangeAetheryte) 10 | { 11 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 12 | P.TaskManager.Enqueue(() => WorldChange.ExecuteTPToAethernetDestination((uint)worldChangeAetheryte)); 13 | P.TaskManager.Enqueue(() => Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51], "WaitUntilBetweenAreas"); 14 | P.TaskManager.Enqueue(WorldChange.WaitUntilNotBusy, TaskSettings.Timeout2M); 15 | P.TaskManager.Enqueue(() => Player.Interactable && P.Territory == worldChangeAetheryte.GetTerritory(), "WaitUntilPlayerInteractable", TaskSettings.Timeout2M); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Lifestream/Data/WotsitIntegrationIncludes.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.Data; 2 | public class WotsitIntegrationIncludedItems 3 | { 4 | public bool WorldSelect = true; 5 | public bool PropertyAuto = false; 6 | // The built-in Wotsit integration for Teleporter usually ranks higher 7 | // than our custom entries, so we disable these by default. 8 | public bool PropertyPrivate = false; 9 | public bool PropertyFreeCompany = false; 10 | public bool PropertyApartment = false; 11 | // Inn does routing so it's better than the built-in Wotsit integration. 12 | public bool PropertyInn = true; 13 | public bool GrandCompany = true; 14 | // MarketBoard does routing so it's better than the built-in Wotsit 15 | // integration. 16 | public bool MarketBoard = true; 17 | public bool IslandSanctuary = true; 18 | 19 | public bool AetheryteAethernet = true; 20 | public bool AddressBook = true; 21 | public bool CustomAlias = true; 22 | } 23 | -------------------------------------------------------------------------------- /Lifestream/Services/NetworkDebugger.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Network; 2 | using Dalamud.Memory; 3 | 4 | namespace Lifestream.Services; 5 | public unsafe class NetworkDebugger : IDisposable 6 | { 7 | private NetworkDebugger() 8 | { 9 | Svc.GameNetwork.NetworkMessage += GameNetwork_NetworkMessage; 10 | } 11 | 12 | private void GameNetwork_NetworkMessage(nint dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, Dalamud.Game.Network.NetworkMessageDirection direction) 13 | { 14 | if(direction == NetworkMessageDirection.ZoneDown && opCode == 0x18a) 15 | { 16 | var mem = MemoryHelper.ReadRaw(dataPtr, 40); 17 | var mem2 = MemoryHelper.ReadRaw(dataPtr + 40, 40); 18 | PluginLog.Information(mem.ToHexString() + "\n" + mem2.ToHexString()); 19 | } 20 | } 21 | 22 | public void Dispose() 23 | { 24 | Svc.GameNetwork.NetworkMessage -= GameNetwork_NetworkMessage; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Lifestream/AtkReaders/ReaderMansionSelectRoom.cs: -------------------------------------------------------------------------------- 1 | using ECommons.UIHelpers; 2 | using FFXIVClientStructs.FFXIV.Component.GUI; 3 | 4 | namespace Lifestream.AtkReaders; 5 | public unsafe class ReaderMansionSelectRoom(AtkUnitBase* UnitBase, int BeginOffset = 0) : AtkReader(UnitBase, BeginOffset) 6 | { 7 | public uint LoadStatus => ReadUInt(0) ?? 0; 8 | public bool IsLoaded => LoadStatus == 4; 9 | public int Section => ReadInt(1) ?? -1; 10 | public uint ExistingSectionsCount => ReadUInt(5) ?? 0; 11 | public uint SectionRoomsCount => ReadUInt(41) ?? 0; 12 | public List Rooms => Loop(42, 12, 15); 13 | 14 | public class RoomInfo(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 15 | { 16 | public uint AccessState => ReadUInt(0) ?? 0; 17 | public int IconID => ReadInt(1) ?? 0; 18 | 19 | public string RoomNumber => ReadString(3); 20 | public string Owner => ReadString(4); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Lifestream/GUI/Windows/GameCloseWindow.cs: -------------------------------------------------------------------------------- 1 | using NightmareUI.ImGuiElements; 2 | 3 | namespace Lifestream.GUI.Windows; 4 | public class GameCloseWindow : Window 5 | { 6 | public int World = 0; 7 | private WorldSelector WorldSelector = new() 8 | { 9 | EmptyName = "Disabled", 10 | }; 11 | public GameCloseWindow() : base("Lifestream Scheduler", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.AlwaysAutoResize) 12 | { 13 | RespectCloseHotkey = false; 14 | ShowCloseButton = false; 15 | } 16 | 17 | public override void Draw() 18 | { 19 | if(World == 0) 20 | { 21 | ImGuiEx.Text("Inactive, select target world"); 22 | } 23 | else 24 | { 25 | ImGuiEx.Text(EColor.RedBright, "Active"); 26 | } 27 | ImGuiEx.Text($"Shutdown game upon arriving to:"); 28 | ImGui.SetNextItemWidth(200f.Scale()); 29 | WorldSelector.Draw(ref World); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Lifestream/GUI/UtilsUI.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.GUI; 2 | public static class UtilsUI 3 | { 4 | public static void DrawSection(string name, Vector4? color, Action drawAction) 5 | { 6 | ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(7f)); 7 | if(ImGui.BeginTable(name, 1, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit)) 8 | { 9 | ImGui.TableSetupColumn(name, ImGuiTableColumnFlags.WidthStretch); 10 | if(color != null) ImGui.PushStyleColor(ImGuiCol.TableHeaderBg, color.Value); 11 | ImGui.TableHeadersRow(); 12 | if(color != null) ImGui.PopStyleColor(); 13 | ImGui.TableNextRow(); 14 | ImGui.TableNextColumn(); 15 | Safe(drawAction); 16 | ImGui.EndTable(); 17 | } 18 | ImGui.Dummy(new(5f)); 19 | ImGui.PopStyleVar(); 20 | } 21 | 22 | public static void NextSection() 23 | { 24 | ImGui.TableNextRow(); 25 | ImGui.TableNextColumn(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Lifestream/AtkReaders/ReaderTelepotTown.cs: -------------------------------------------------------------------------------- 1 | using ECommons.UIHelpers; 2 | using FFXIVClientStructs.FFXIV.Component.GUI; 3 | 4 | namespace Lifestream.AtkReaders; 5 | 6 | internal unsafe class ReaderTelepotTown(AtkUnitBase* UnitBase, int BeginOffset = 0) : AtkReader(UnitBase, BeginOffset) 7 | { 8 | internal uint NumEntries => ReadUInt(0) ?? 0; 9 | internal uint CurrentDestination => ReadUInt(1) ?? 0; 10 | internal List DestinationData => Loop(6, 4, 20); 11 | internal List DestinationName => Loop(262, 1, 20); 12 | 13 | internal unsafe class Names(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 14 | { 15 | internal string Name => ReadSeString(0).GetText(); 16 | } 17 | 18 | internal unsafe class Data(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 19 | { 20 | internal uint Type => ReadUInt(0).Value; 21 | internal uint State => ReadUInt(1).Value; 22 | internal uint IconID => ReadUInt(2).Value; 23 | internal uint CallbackData => ReadUInt(3).Value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Login/TaskConnectAndOpenCharaSelect.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Lifestream.Tasks.Login; 9 | public static unsafe class TaskConnectAndOpenCharaSelect 10 | { 11 | public static bool Enqueue(string charaName, string homeWorld) 12 | { 13 | var account = Utils.GetServiceAccount($"{charaName}@{homeWorld}"); 14 | if(ExcelWorldHelper.Get(homeWorld) == null) throw new NullReferenceException("Target world not found"); 15 | if(Utils.CanAutoLogin()) 16 | { 17 | TaskChangeCharacter.ConnectToDc(homeWorld, account); 18 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 19 | P.TaskManager.Enqueue(() => TaskChangeCharacter.SelectCharacter(charaName, homeWorld, homeWorld, onlyChangeWorld: true), $"Select chara {charaName}@{homeWorld}", new(timeLimitMS: 5.Minutes())); 20 | return true; 21 | } 22 | PluginLog.Error("Can not log in now"); 23 | return false; 24 | } 25 | } -------------------------------------------------------------------------------- /Lifestream/GUI/TabUtility.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Paissa; 3 | using NightmareUI.ImGuiElements; 4 | using NightmareUI.PrimaryUI; 5 | 6 | namespace Lifestream.GUI; 7 | public static class TabUtility 8 | { 9 | public static int TargetWorldID = 0; 10 | private static WorldSelector WorldSelector = new() 11 | { 12 | DisplayCurrent = false, 13 | EmptyName = "Disabled", 14 | ShouldHideWorld = (x) => x == Player.Object?.CurrentWorld.RowId 15 | }; 16 | private static PaissaImporter PaissaImporter = new(); 17 | 18 | public static void Draw() 19 | { 20 | new NuiBuilder() 21 | .Section("Shutdown game upon arriving to the world") 22 | .Widget(() => 23 | { 24 | ImGuiEx.SetNextItemFullWidth(); 25 | WorldSelector.Draw(ref TargetWorldID); 26 | }) 27 | .Section("Import house listings from PaissaDB") 28 | .Widget(() => 29 | { 30 | ImGuiEx.SetNextItemFullWidth(); 31 | PaissaImporter.Draw(); 32 | }) 33 | .Draw(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Lifestream/Global.cs: -------------------------------------------------------------------------------- 1 | global using Dalamud.Game.ClientState.Conditions; 2 | global using Dalamud.Interface; 3 | global using Dalamud.Interface.Colors; 4 | global using Dalamud.Interface.Utility; 5 | global using Dalamud.Interface.Windowing; 6 | global using Dalamud.Plugin; 7 | global using ECommons; 8 | global using ECommons.DalamudServices; 9 | global using ECommons.DalamudServices.Legacy; 10 | global using ECommons.ImGuiMethods; 11 | global using ECommons.Logging; 12 | global using ECommons.Schedulers; 13 | global using Dalamud.Bindings.ImGui; 14 | global using Lifestream.CSExtensions; 15 | global using System; 16 | global using System.Collections.Generic; 17 | global using System.Linq; 18 | global using System.Numerics; 19 | global using System.Runtime.InteropServices; 20 | global using System.Text; 21 | global using System.Threading.Tasks; 22 | global using static ECommons.GenericHelpers; 23 | global using static Lifestream.Lifestream; 24 | global using AddressBookFS = OtterGui.Filesystem.FileSystem; 25 | global using CSFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework; 26 | global using S = Lifestream.Services.Service; 27 | global using Player = ECommons.GameHelpers.LegacyPlayer.Player; -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossWorld/TaskEnforceWorld.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation.NeoTaskManager.Tasks; 2 | using ECommons.GameHelpers; 3 | using Lifestream.Enums; 4 | using Lifestream.Schedulers; 5 | 6 | namespace Lifestream.Tasks.CrossWorld; 7 | public static class TaskEnforceWorld 8 | { 9 | public static void Enqueue(string destinationWorld, WorldChangeAetheryte? gateway) 10 | { 11 | P.TaskManager.Enqueue(() => 12 | { 13 | if(Player.Interactable && IsScreenReady()) 14 | { 15 | if(Player.CurrentWorld != destinationWorld) 16 | { 17 | P.TaskManager.InsertMulti([ 18 | new(WorldChange.WaitUntilNotBusy, TaskSettings.TimeoutInfinite), 19 | new DelayTask(1000), 20 | new(() => TaskTPAndChangeWorld.Enqueue(destinationWorld, gateway.Value.AdjustGateway(), true), $"TpAndChangeWorld {destinationWorld} at {gateway.Value}"), 21 | new(() => TaskWaitUntilInWorld.Task(destinationWorld, false)) 22 | ]); 23 | } 24 | return true; 25 | } 26 | return false; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Lifestream/Data/ZoneDetail.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Systems.Custom; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Lifestream.Data; 9 | public unsafe class ZoneDetail 10 | { 11 | public List Aetherytes = []; 12 | public float MaxInteractionDistance = 4.6f; 13 | public List GenericAetheryteNames = []; 14 | 15 | public ZoneDetail(List aetherytes) 16 | { 17 | Aetherytes = aetherytes; 18 | } 19 | 20 | public ZoneDetail(List aetherytes, float maxInteractionDistance) 21 | { 22 | Aetherytes = aetherytes; 23 | MaxInteractionDistance = maxInteractionDistance; 24 | } 25 | 26 | public ZoneDetail(List aetherytes, List genericAetheryteNames) : this(aetherytes) 27 | { 28 | GenericAetheryteNames = genericAetheryteNames; 29 | } 30 | 31 | public ZoneDetail(List aetherytes, float maxInteractionDistance, List genericAetheryteNames) 32 | { 33 | Aetherytes = aetherytes; 34 | MaxInteractionDistance = maxInteractionDistance; 35 | GenericAetheryteNames = genericAetheryteNames; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskReturnToHomeDC.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Schedulers; 2 | using Lifestream.Tasks.Login; 3 | 4 | namespace Lifestream.Tasks.CrossDC; 5 | 6 | internal class TaskReturnToHomeDC 7 | { 8 | internal static void Enqueue(string charaName, uint charaWorld, uint currentLoginWorld) 9 | { 10 | void tasks() 11 | { 12 | PluginLog.Debug($"Beginning returning home process."); 13 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 14 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 15 | P.TaskManager.Enqueue(() => DCChange.OpenContextMenuForChara(charaName, charaWorld, currentLoginWorld), nameof(DCChange.OpenContextMenuForChara), TaskSettings.Timeout5M); 16 | P.TaskManager.Enqueue(DCChange.SelectReturnToHomeWorld); 17 | P.TaskManager.Enqueue(DCChange.ConfirmDcVisit, TaskSettings.Timeout2M); 18 | P.TaskManager.Enqueue(() => DCChange.ConfirmDcVisit2(null, null, 0, 0, tasks), "ConfirmDCVisit2", TaskSettings.Timeout2M); 19 | } 20 | tasks(); 21 | P.TaskManager.Enqueue(DCChange.SelectOk, TaskSettings.TimeoutInfinite); 22 | P.TaskManager.Enqueue(() => DCChange.SelectServiceAccount(Utils.GetServiceAccount(charaName, charaWorld)), $"SelectServiceAccount_{charaName}@{charaWorld}", TaskSettings.Timeout1M); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Lifestream/IPC/Wotsit/WotsitEntry.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Tasks.SameWorld; 2 | 3 | namespace Lifestream.IPC; 4 | 5 | public class WotsitEntry 6 | { 7 | public string DisplayName { get; init; } 8 | public string SearchString { get; init; } 9 | public uint IconId { get; init; } 10 | public Delegate Callback { get; init; } 11 | 12 | public static WotsitEntry AetheryteAethernetTeleport(string townName, string name, uint aetheryteId, uint aethernetId) 13 | { 14 | var searchStr = name + (townName != null ? $" - {townName}" : ""); 15 | return new WotsitEntry 16 | { 17 | DisplayName = "Teleport to " + searchStr, 18 | SearchString = searchStr, 19 | IconId = 111, 20 | Callback = () => TaskAetheryteAethernetTeleport.Enqueue(aetheryteId, aethernetId), 21 | }; 22 | } 23 | 24 | // Callback is intentionally not included in equality checks. 25 | public override int GetHashCode() => HashCode.Combine(DisplayName, SearchString, IconId); 26 | public override bool Equals(object obj) => obj is WotsitEntry entry && Equals(entry); 27 | public bool Equals(WotsitEntry other) => DisplayName == other.DisplayName && SearchString == other.SearchString && IconId == other.IconId; 28 | 29 | public override string ToString() => $"{GetType().Name}(\"{DisplayName}\", \"{SearchString}\", {IconId})"; 30 | } 31 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskRemoveAfkStatus.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation; 2 | using ECommons.GameHelpers; 3 | using ECommons.Throttlers; 4 | 5 | namespace Lifestream.Tasks; 6 | 7 | internal static class TaskRemoveAfkStatus 8 | { 9 | public static readonly ConditionFlag[] MoveCancelConditions = [ConditionFlag.InThatPosition]; 10 | 11 | internal static void Enqueue() 12 | { 13 | P.TaskManager.Enqueue(() => 14 | { 15 | if(Player.Object.OnlineStatus.RowId == 17) 16 | { 17 | if(EzThrottler.Throttle("RemoveAfk")) 18 | { 19 | Chat.SendMessage("/afk off"); 20 | P.TaskManager.InsertTask(new(() => Player.Object.OnlineStatus.RowId != 17, "WaitUntilNotAfk")); 21 | } 22 | } 23 | if(MoveCancelConditions.Select(x => Svc.Condition[x]).Any(x => x)) 24 | { 25 | P.TaskManager.InsertMulti( 26 | new(() => Chat.ExecuteCommand("/automove on"), "Enable automove (AntiEmote)"), 27 | new(() => Chat.ExecuteCommand("/automove off"), "Disable automove (AntiEmote)"), 28 | new(() => !MoveCancelConditions.Select(x => Svc.Condition[x]).Any(x => x), "WaitUntilNotEmoting") 29 | ); 30 | } 31 | return true; 32 | }, "Remove afk/busy status"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskAethernetTeleport.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Schedulers; 2 | using Lifestream.Systems.Legacy; 3 | 4 | namespace Lifestream.Tasks.SameWorld; 5 | 6 | internal static class TaskAethernetTeleport 7 | { 8 | internal static void Enqueue(TinyAetheryte a) 9 | { 10 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 11 | P.TaskManager.Enqueue(WorldChange.TargetValidAetheryte); 12 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 13 | if(S.Data.DataStore.Aetherytes.ContainsKey(P.ActiveAetheryte.Value)) P.TaskManager.Enqueue(WorldChange.SelectAethernet); 14 | P.TaskManager.EnqueueDelay(C.SlowTeleport ? C.SlowTeleportThrottle : 0); 15 | P.TaskManager.Enqueue(() => WorldChange.TeleportToAethernetDestination(a.Name), nameof(WorldChange.TeleportToAethernetDestination)); 16 | } 17 | 18 | internal static void Enqueue(string destination) 19 | { 20 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 21 | P.TaskManager.Enqueue(WorldChange.TargetValidAetheryte); 22 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 23 | P.TaskManager.EnqueueDelay(C.SlowTeleport ? C.SlowTeleportThrottle : 0); 24 | P.TaskManager.Enqueue(() => WorldChange.TeleportToAethernetDestination(destination), nameof(WorldChange.TeleportToAethernetDestination)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskReturnToGateway.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Enums; 3 | using Lifestream.Schedulers; 4 | 5 | namespace Lifestream.Tasks.SameWorld; 6 | 7 | public static class TaskReturnToGateway 8 | { 9 | public static void Enqueue(WorldChangeAetheryte gateway, bool force = false) 10 | { 11 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 12 | P.TaskManager.Enqueue(WaitUntilInteractable); 13 | P.TaskManager.Enqueue(() => 14 | { 15 | gateway = Utils.AdjustGateway(gateway); 16 | if(force || P.Territory != gateway.GetTerritory()) 17 | { 18 | P.TaskManager.InsertMulti( 19 | new(() => WorldChange.ExecuteTPToAethernetDestination((uint)gateway), $"ExecuteTPToAethernetDestination({gateway})"), 20 | new(() => Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51], "WaitUntilBetweenAreas"), 21 | new(WorldChange.WaitUntilNotBusy, TaskSettings.Timeout2M), 22 | new(() => Player.Interactable && P.Territory == gateway.GetTerritory(), "WaitUntilPlayerInteractable", TaskSettings.Timeout2M) 23 | ); 24 | } 25 | return true; 26 | }, "TaskReturnToGatewayMaster"); 27 | } 28 | 29 | public static bool? WaitUntilInteractable() => Player.Interactable; 30 | } 31 | -------------------------------------------------------------------------------- /Lifestream/Services/CustomAliasFileSystemManager.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | using Lifestream.Data; 3 | using NightmareUI.OtterGuiWrapper.FileSystems.Generic; 4 | 5 | namespace Lifestream.Services; 6 | public class CustomAliasFileSystemManager 7 | { 8 | public GenericFileSystem FileSystem; 9 | private CustomAliasFileSystemManager() 10 | { 11 | FileSystem = new(C.CustomAliases, "CustomAlias"); 12 | FileSystem.Selector.OnBeforeItemCreation += Selector_OnBeforeItemCreation; 13 | FileSystem.Selector.OnBeforeCopy += Selector_OnBeforeCopy; 14 | FileSystem.Selector.OnImportPopupOpen += Selector_OnImportPopupOpen; 15 | } 16 | private void Selector_OnBeforeCopy(CustomAlias original, ref CustomAlias copy) 17 | { 18 | if(FileSystem.FindLeaf(original, out var leaf)) 19 | { 20 | copy.ExportedName = leaf.Name; 21 | } 22 | } 23 | 24 | private void Selector_OnBeforeItemCreation(ref CustomAlias item) 25 | { 26 | item.GUID = Guid.NewGuid(); 27 | } 28 | 29 | private void Selector_OnImportPopupOpen(string clipboardText, ref string newName) 30 | { 31 | try 32 | { 33 | var item = EzConfig.DefaultSerializationFactory.Deserialize(clipboardText); 34 | if(item != null && item.ExportedName != null) 35 | { 36 | newName = item.ExportedName; 37 | } 38 | } 39 | catch(Exception e) { } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Lifestream/Services/DtrManager.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Gui.Dtr; 2 | using Dalamud.Game.Text.SeStringHandling; 3 | using ECommons.EzEventManager; 4 | 5 | namespace Lifestream.Services; 6 | public class DtrManager : IDisposable 7 | { 8 | public static Dictionary InstanceNumbers = new() 9 | { 10 | [1] = "\ue0b1", 11 | [2] = "\ue0b2", 12 | [3] = "\ue0b3", 13 | [4] = "\ue0b4", 14 | [5] = "\ue0b5", 15 | [6] = "\ue0b6", 16 | [7] = "\ue0b7", 17 | [8] = "\ue0b8", 18 | [9] = "\ue0b9", 19 | }; 20 | public static string Name = "LifestreamInstance"; 21 | public IDtrBarEntry Entry; 22 | private DtrManager() 23 | { 24 | Entry = Svc.DtrBar.Get(Name); 25 | Entry.Shown = false; 26 | new EzTerritoryChanged(OnTerritoryChanged); 27 | Refresh(); 28 | } 29 | 30 | public void Refresh() => OnTerritoryChanged(Svc.ClientState.TerritoryType); 31 | 32 | private void OnTerritoryChanged(ushort obj) 33 | { 34 | Entry.Shown = false; 35 | if(C.EnableDtrBar && S.InstanceHandler.GetInstance() > 0) 36 | { 37 | var str = InstanceNumbers.SafeSelect(obj); 38 | if(str != null) 39 | { 40 | Entry.Text = str; 41 | Entry.Tooltip = $"You are in instance {obj}"; 42 | Entry.Shown = true; 43 | } 44 | } 45 | } 46 | 47 | public void Dispose() 48 | { 49 | Entry.Remove(); 50 | Entry = null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Thank you for considering contributing to this project! 2 | ## Before you proceed with pull request, please consider the following: 3 | ### Do not refactor 4 | Even if current code is unoptimal, please avoid refactoring existing code. 5 | - Do not replace opcodes with hooks 6 | - Do not replace static data with dynamic data 7 | - Do not optimize utility functions 8 | - Do not replace if trees with switch statements 9 | - Do not change accessibility modifiers 10 | - Do not replace manual ImGui.Begin calls with windows 11 | If you know how to significantly improve something, please create an issue describing method of improvement instead. 12 | 13 | ### Keep your additions in uniform with the rest of the code 14 | Even if it's not the most optimal, please consider doing this. If you're adding commands, UI elements or IPC methods in addition to the existing ones, please ensure that they are coded similar to existing ones. If you're adding new functions, please ensure that they are working similar to existing ones. 15 | 16 | ### If possible, keep additions contained 17 | If you would add new functions, please create own classes for them and keep them contained. The plugin should not become dependent on added functions; the plugin's ability to work should not be harmed if added functions become unavailable and have to be disabled. 18 | 19 | ### No configuration resets 20 | Users should not experience any partial or full configuration resets upon updating. 21 | 22 | ## Most welcomed changes 23 | Please feel free to: 24 | - Correct spelling mistakes 25 | - Adjust UI components sizes if you find out that they are not properly rendered on some screens 26 | -------------------------------------------------------------------------------- /Lifestream/Services/!Service.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Game; 2 | using Lifestream.GUI; 3 | using Lifestream.GUI.Windows; 4 | using Lifestream.IPC; 5 | using Lifestream.Systems.Custom; 6 | using Lifestream.Systems.Legacy; 7 | using Lifestream.Systems.Residential; 8 | using static ECommons.Singletons.SingletonServiceManager; 9 | 10 | namespace Lifestream.Services; 11 | 12 | public static class Service 13 | { 14 | [Priority(-1)] 15 | public static class Data 16 | { 17 | public static DataStore DataStore; 18 | public static ResidentialAethernet ResidentialAethernet; 19 | public static CustomAethernet CustomAethernet; 20 | } 21 | 22 | public static class Gui 23 | { 24 | public static SelectWorldWindow SelectWorldWindow; 25 | public static Overlay Overlay; 26 | public static ProgressOverlay ProgressOverlay; 27 | } 28 | 29 | public static class Ipc 30 | { 31 | public static IPCProvider IPCProvider; 32 | public static TextAdvanceIPC TextAdvanceIPC; 33 | public static VnavmeshIPC VnavmeshIPC; 34 | public static WotsitManager WotsitManager; 35 | public static SplatoonManager SplatoonManager; 36 | } 37 | 38 | public static Memory Memory; 39 | public static TeleportService TeleportService; 40 | public static InstanceHandler InstanceHandler; 41 | public static ContextMenuManager ContextMenuManager; 42 | public static AddressBookFileSystemManager AddressBookFileSystemManager; 43 | public static CustomAliasFileSystemManager CustomAliasFileSystemManager; 44 | public static HttpClientProvider HttpClientProvider; 45 | public static DtrManager DtrManager; 46 | public static MapHanderService MapHanderService; 47 | public static SearchHelperOverlay SearchHelperOverlay; 48 | } 49 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskApproachAetheryteIfNeeded.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Enums; 2 | using ECommons.Automation.NeoTaskManager.Tasks; 3 | using ECommons.GameHelpers; 4 | using ECommons.Throttlers; 5 | using Lifestream.Schedulers; 6 | 7 | namespace Lifestream.Tasks.Utility; 8 | public static class TaskApproachAetheryteIfNeeded 9 | { 10 | public static void Enqueue() 11 | { 12 | P.TaskManager.Enqueue(() => 13 | { 14 | if(Utils.ApproachConditionIsMet()) 15 | { 16 | P.TaskManager.InsertMulti( 17 | C.WaitForScreenReady ? new(Utils.WaitForScreen) : null, 18 | new FrameDelayTask(10), 19 | new(TargetReachableAetheryte), 20 | new(WorldChange.LockOn), 21 | new(WorldChange.EnableAutomove), 22 | new(WaitUntilAetheryteExists), 23 | new(WorldChange.DisableAutomove) 24 | ); 25 | } 26 | }, "TaskApproachAetheryteIfNeeded Master Task"); 27 | } 28 | 29 | public static bool WaitUntilAetheryteExists() 30 | { 31 | if(!Player.Available) return false; 32 | return P.ActiveAetheryte != null && P.ActiveAetheryte.Value.IsAetheryte; 33 | } 34 | 35 | 36 | public static bool TargetReachableAetheryte() 37 | { 38 | if(!Player.Available) return false; 39 | var a = Utils.GetReachableAetheryte(x => x.ObjectKind == ObjectKind.Aetheryte); 40 | if(a != null) 41 | { 42 | if(!a.IsTarget() && EzThrottler.Throttle("TargetReachableAetheryte", 200)) 43 | { 44 | Svc.Targets.SetTarget(a); 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Lifestream/Services/TeleportService.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using FFXIVClientStructs.FFXIV.Client.Game; 3 | using FFXIVClientStructs.FFXIV.Client.Game.UI; 4 | 5 | namespace Lifestream.Services; 6 | public unsafe class TeleportService 7 | { 8 | private TeleportService() { } 9 | 10 | public bool TeleportToAetheryte(uint id, uint sub = 0, bool wait = false) 11 | { 12 | if(!CanTeleport(out var err)) 13 | { 14 | InternalLog.Warning(err); 15 | return false; 16 | } 17 | foreach(var x in Svc.AetheryteList) 18 | { 19 | if(x.AetheryteId == id && x.SubIndex == sub) 20 | { 21 | Telepo.Instance()->Teleport(id, (byte)sub); 22 | if(wait) 23 | { 24 | P.TaskManager.InsertMulti( 25 | new(() => !IsScreenReady()), 26 | new(() => IsScreenReady() && Player.Interactable) 27 | ); 28 | } 29 | return true; 30 | } 31 | } 32 | InternalLog.Warning($"Could not find teleport destination for {id}"); 33 | return false; 34 | } 35 | 36 | public bool CanTeleport(out string error) 37 | { 38 | error = null; 39 | if(!Player.Interactable) 40 | { 41 | error = ("Can't teleport - no player"); 42 | return false; 43 | } 44 | if(ActionManager.Instance()->GetActionStatus(ActionType.Action, 5) != 0) 45 | { 46 | error = ("Can't execute teleport action"); 47 | return false; 48 | } 49 | if(Player.IsAnimationLocked) 50 | { 51 | error = ("Can't teleport - animation locked"); 52 | return false; 53 | } 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Lifestream/Systems/Legacy/TinyAetheryte.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Utility; 2 | using Lumina.Excel.Sheets; 3 | 4 | namespace Lifestream.Systems.Legacy; 5 | 6 | public struct TinyAetheryte : IEquatable, IAetheryte 7 | { 8 | public Vector2 Position { get; set; } 9 | public uint TerritoryType { get; set; } 10 | public uint ID { get; set; } 11 | public string Name { get; set; } 12 | public uint Group { get; set; } 13 | public bool IsAetheryte { get; set; } 14 | public bool Invisible { get; set; } 15 | private Aetheryte Ref { get; init; } 16 | 17 | public TinyAetheryte(Vector2 position, uint territoryType, uint iD, uint group) 18 | { 19 | Ref = Svc.Data.GetExcelSheet().GetRow(iD); 20 | Position = position; 21 | TerritoryType = territoryType; 22 | ID = iD; 23 | Group = group; 24 | Name = Ref.AethernetName.Value.Name.ToDalamudString().GetText(); 25 | IsAetheryte = Ref.IsAetheryte; 26 | Invisible = Ref.Invisible; 27 | } 28 | 29 | public override bool Equals(object obj) 30 | { 31 | return obj is TinyAetheryte aetheryte && Equals(aetheryte); 32 | } 33 | 34 | public bool Equals(TinyAetheryte other) 35 | { 36 | return Position.Equals(other.Position) && 37 | TerritoryType == other.TerritoryType && 38 | ID == other.ID && 39 | Group == other.Group; 40 | } 41 | 42 | public override int GetHashCode() 43 | { 44 | return HashCode.Combine(Position, TerritoryType, ID, Group); 45 | } 46 | 47 | public static bool operator ==(TinyAetheryte left, TinyAetheryte right) 48 | { 49 | return left.Equals(right); 50 | } 51 | 52 | public static bool operator !=(TinyAetheryte left, TinyAetheryte right) 53 | { 54 | return !(left == right); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Lifestream/IPC/VnavmeshIPC.cs: -------------------------------------------------------------------------------- 1 | using ECommons.EzIpcManager; 2 | 3 | namespace Lifestream.IPC; 4 | #pragma warning disable CS8632 5 | public class VnavmeshIPC 6 | { 7 | [EzIPC("Nav.IsReady", wrapper: SafeWrapper.None)] private readonly Func IsReadyNoWrapper; 8 | public bool? IsReady() 9 | { 10 | try 11 | { 12 | return IsReadyNoWrapper(); 13 | } 14 | catch(Exception e) 15 | { 16 | DuoLog.Error($"Vnavmesh not found, navigation failed"); 17 | e.LogInternal(); 18 | return null; 19 | } 20 | } 21 | [EzIPC("Nav.%m")] public readonly Func BuildProgress; 22 | [EzIPC("Nav.%m")] public readonly Func Reload; 23 | [EzIPC("Nav.%m")] public readonly Func Rebuild; 24 | /// 25 | /// Vector3 from, Vector3 to, bool fly 26 | /// 27 | [EzIPC("Nav.%m")] public readonly Func>> Pathfind; 28 | 29 | [EzIPC("SimpleMove.%m")] public readonly Func PathfindAndMoveTo; 30 | [EzIPC("SimpleMove.%m")] public readonly Func PathfindInProgress; 31 | 32 | [EzIPC("Path.%m")] public readonly Action Stop; 33 | [EzIPC("Path.%m")] public readonly Func IsRunning; 34 | 35 | /// 36 | /// Vector3 p, float halfExtentXZ, float halfExtentY 37 | /// 38 | [EzIPC("Query.Mesh.%m")] public readonly Func NearestPoint; 39 | /// 40 | /// Vector3 p, bool allowUnlandable, float halfExtentXZ 41 | /// 42 | [EzIPC("Query.Mesh.%m")] public readonly Func PointOnFloor; 43 | 44 | public VnavmeshIPC() 45 | { 46 | ECommonsMain.ReducedLogging = true; 47 | EzIPC.Init(this, "vnavmesh", SafeWrapper.AnyException); 48 | ECommonsMain.ReducedLogging = false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Lifestream/Systems/Custom/CustomAetheryte.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Lifestream.Systems.Custom; 3 | public struct CustomAetheryte : IAetheryte, IEquatable 4 | { 5 | public Vector2 Position { get; set; } 6 | public uint TerritoryType { get; set; } 7 | public string Name { get; set; } 8 | public uint ID { get; set; } 9 | public Vector2? MapPosition { get; set; } = null; 10 | 11 | public CustomAetheryte() 12 | { 13 | } 14 | 15 | public CustomAetheryte(Vector2 position, uint territoryType, string name, uint iD) 16 | { 17 | Position = position; 18 | TerritoryType = territoryType; 19 | Name = name; 20 | ID = iD; 21 | } 22 | 23 | public CustomAetheryte(Vector2 position, uint territoryType, string name, uint iD, Vector2 mapPosition) 24 | { 25 | Position = position; 26 | TerritoryType = territoryType; 27 | Name = name; 28 | ID = iD; 29 | MapPosition = mapPosition; 30 | } 31 | 32 | public override bool Equals(object obj) 33 | { 34 | return obj is CustomAetheryte aetheryte && Equals(aetheryte); 35 | } 36 | 37 | public bool Equals(CustomAetheryte other) 38 | { 39 | return Position.Equals(other.Position) && 40 | TerritoryType == other.TerritoryType && 41 | Name == other.Name && 42 | ID == other.ID && 43 | EqualityComparer.Default.Equals(MapPosition, other.MapPosition); 44 | } 45 | 46 | public override int GetHashCode() 47 | { 48 | return HashCode.Combine(Position, TerritoryType, Name, ID, MapPosition); 49 | } 50 | 51 | public static bool operator ==(CustomAetheryte left, CustomAetheryte right) 52 | { 53 | return left.Equals(right); 54 | } 55 | 56 | public static bool operator !=(CustomAetheryte left, CustomAetheryte right) 57 | { 58 | return !(left == right); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Lifestream/AtkReaders/ReaderLobbyDKTWorldList.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using ECommons.UIHelpers; 3 | using FFXIVClientStructs.FFXIV.Component.GUI; 4 | 5 | namespace Lifestream.AtkReaders; 6 | public unsafe class ReaderLobbyDKTWorldList(AtkUnitBase* UnitBase, int BeginOffset = 0) : AtkReader(UnitBase, BeginOffset) 7 | { 8 | public string Source => ReadString(3); 9 | public string Destination => ReadString(4); 10 | public List Regions => Loop(14, 2 + 8 * 4, 4); 11 | public string SelectedDataCenter => ReadString(151); 12 | public List Worlds => Loop(154, 8, GetNumWorlds()); 13 | 14 | public int GetNumWorlds() 15 | { 16 | var dc = ExcelWorldHelper.GetDataCenters().FirstOrNull(x => x.Name.GetText() == SelectedDataCenter); 17 | if(dc == null) return 0; 18 | var worlds = ExcelWorldHelper.GetPublicWorlds(dc.Value.RowId); 19 | return worlds.Count(x => x.IsPublic()); 20 | } 21 | 22 | public unsafe class RegionInfo(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 23 | { 24 | private int DcInfoOffset = BeginOffset + 1; 25 | public string RegionTitle => ReadString(0); 26 | public List DataCenters => Loop(DcInfoOffset, 8, 4); 27 | 28 | public unsafe class DataCenterInfo(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 29 | { 30 | public uint Id => ReadUInt(0) ?? 0; 31 | public string Name => ReadString(1); 32 | public bool? Unk2 => ReadBool(2); 33 | public bool? Unk3 => ReadBool(3); 34 | public uint Unk4 => ReadUInt(4) ?? 0; 35 | } 36 | } 37 | 38 | public unsafe class WorldInfo(nint UnitBasePtr, int BeginOffset = 0) : AtkReader(UnitBasePtr, BeginOffset) 39 | { 40 | public string WorldName => ReadString(0); 41 | public bool IsAvailable => ReadUInt(6) == 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Lifestream/IPC/YesAlreadyManager.cs: -------------------------------------------------------------------------------- 1 | using ECommons.EzSharedDataManager; 2 | 3 | namespace Lifestream.IPC; 4 | 5 | internal static class YesAlreadyManager 6 | { 7 | internal static bool Reenable = false; 8 | internal static HashSet Data = null; 9 | 10 | internal static void GetData() 11 | { 12 | if(Data != null) return; 13 | if(EzSharedData.TryGet>("YesAlready.StopRequests", out var data)) 14 | { 15 | Data = data; 16 | } 17 | } 18 | 19 | internal static void DisableIfNeeded() 20 | { 21 | GetData(); 22 | if(Data != null) 23 | { 24 | PluginLog.Information("Disabling Yes Already (new)"); 25 | Data.Add(Svc.PluginInterface.InternalName); 26 | Reenable = true; 27 | } 28 | } 29 | 30 | internal static void EnableIfNeeded() 31 | { 32 | if(Reenable) 33 | { 34 | GetData(); 35 | if(Data != null) 36 | { 37 | PluginLog.Information("Enabling Yes Already (new)"); 38 | Data.Remove(Svc.PluginInterface.InternalName); 39 | Reenable = false; 40 | } 41 | } 42 | } 43 | 44 | internal static bool IsEnabled() 45 | { 46 | GetData(); 47 | if(Data != null) 48 | { 49 | return !Data.Contains(Svc.PluginInterface.InternalName); 50 | } 51 | return false; 52 | } 53 | 54 | internal static void Tick() 55 | { 56 | if(P.TaskManager.IsBusy) 57 | { 58 | if(IsEnabled()) 59 | { 60 | DisableIfNeeded(); 61 | } 62 | } 63 | else 64 | { 65 | if(Reenable) 66 | { 67 | EnableIfNeeded(); 68 | } 69 | } 70 | } 71 | 72 | internal static bool? WaitForYesAlreadyDisabledTask() 73 | { 74 | return !IsEnabled(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossWorld/TaskTPAndChangeWorld.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation.NeoTaskManager.Tasks; 2 | using Lifestream.Enums; 3 | using Lifestream.Schedulers; 4 | using Lifestream.Tasks.SameWorld; 5 | 6 | namespace Lifestream.Tasks.CrossWorld; 7 | 8 | internal static class TaskTPAndChangeWorld 9 | { 10 | internal static void Enqueue(string world, WorldChangeAetheryte gateway, bool insert) 11 | { 12 | P.TaskManager.BeginStack(); 13 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 14 | if(P.ActiveAetheryte != null && P.ActiveAetheryte.Value.IsWorldChangeAetheryte()) 15 | { 16 | TaskChangeWorld.Enqueue(world); 17 | } 18 | else 19 | { 20 | if(Utils.GetReachableWorldChangeAetheryte(!C.WalkToAetheryte) == null) 21 | { 22 | TaskTpToAethernetDestination.Enqueue(gateway); 23 | } 24 | P.TaskManager.EnqueueTask(new(() => 25 | { 26 | if((P.ActiveAetheryte == null || !P.ActiveAetheryte.Value.IsWorldChangeAetheryte()) && Utils.GetReachableWorldChangeAetheryte() != null) 27 | { 28 | P.TaskManager.InsertMulti( 29 | new FrameDelayTask(10), 30 | new(WorldChange.TargetReachableWorldChangeAetheryte), 31 | new(WorldChange.LockOn), 32 | new(WorldChange.EnableAutomove), 33 | new(WorldChange.WaitUntilMasterAetheryteExists), 34 | new(WorldChange.DisableAutomove) 35 | ); 36 | } 37 | }, "ConditionalLockonTask")); 38 | P.TaskManager.Enqueue(WorldChange.WaitUntilMasterAetheryteExists); 39 | P.TaskManager.EnqueueDelay(10, true); 40 | TaskChangeWorld.Enqueue(world); 41 | } 42 | if(insert) 43 | { 44 | P.TaskManager.InsertStack(); 45 | } 46 | else 47 | { 48 | P.TaskManager.EnqueueStack(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskReturnToHomeWorldCharaSelect.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation.UIInput; 2 | using ECommons.Throttlers; 3 | using ECommons.UIHelpers.AddonMasterImplementations; 4 | using FFXIVClientStructs.FFXIV.Component.GUI; 5 | using Lifestream.Schedulers; 6 | using Lifestream.Tasks.Login; 7 | 8 | namespace Lifestream.Tasks.CrossDC; 9 | public static unsafe class TaskReturnToHomeWorldCharaSelect 10 | { 11 | public static void Enqueue(string charaName, uint charaWorld, uint currentLoginWorld) 12 | { 13 | PluginLog.Debug($"Beginning returning home process."); 14 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 15 | P.TaskManager.Enqueue(() => DCChange.OpenContextMenuForChara(charaName, charaWorld, currentLoginWorld), nameof(DCChange.OpenContextMenuForChara), TaskSettings.Timeout5M); 16 | P.TaskManager.Enqueue(DCChange.SelectReturnToHomeWorld); 17 | P.TaskManager.Enqueue(ConfirmReturnToHomeWorld, TaskSettings.Timeout2M); 18 | P.TaskManager.Enqueue(DCChange.SelectOk, TaskSettings.TimeoutInfinite); 19 | P.TaskManager.Enqueue(() => 20 | { 21 | return !(TryGetAddonByName("NowLoading", out var nl) && nl->IsVisible) && TryGetAddonMaster(out var m) && m.IsAddonReady; 22 | }); 23 | } 24 | 25 | public static bool? ConfirmReturnToHomeWorld() 26 | { 27 | if(TryGetAddonByName("LobbyWKTCheckHome", out var addon) && IsAddonReady(addon)) 28 | { 29 | if(addon->GetComponentButtonById(3)->IsEnabled) 30 | { 31 | if(DCChange.DCThrottle && EzThrottler.Throttle("ConfirmHomeWorldVisit", 5000)) 32 | { 33 | PluginLog.Debug($"[DCChange] Confirming home world transfer"); 34 | addon->GetComponentButtonById(3)->ClickAddonButton(addon); 35 | return true; 36 | } 37 | } 38 | else 39 | { 40 | DCChange.DCRethrottle(); 41 | } 42 | } 43 | else 44 | { 45 | DCChange.DCRethrottle(); 46 | } 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Lifestream/Movement/OverrideCamera.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Hooking; 2 | using Dalamud.Utility.Signatures; 3 | using ECommons.MathHelpers; 4 | using FFXIVClientStructs.FFXIV.Client.System.Framework; 5 | using Service = ECommons.DalamudServices.Svc; 6 | 7 | namespace Lifestream.Movement; 8 | 9 | public unsafe class OverrideCamera : IDisposable 10 | { 11 | public bool Enabled 12 | { 13 | get => _rmiCameraHook.IsEnabled; 14 | set 15 | { 16 | if(value) 17 | _rmiCameraHook.Enable(); 18 | else 19 | _rmiCameraHook.Disable(); 20 | } 21 | } 22 | 23 | public bool IgnoreUserInput; // if true - override even if user tries to change camera orientation, otherwise override only if user does nothing 24 | public Angle DesiredAzimuth; 25 | public Angle DesiredAltitude; 26 | public Angle SpeedH = 360.Degrees(); // per second 27 | public Angle SpeedV = 360.Degrees(); // per second 28 | 29 | private delegate void RMICameraDelegate(CameraEx* self, int inputMode, float speedH, float speedV); 30 | [Signature("48 8B C4 53 48 81 EC ?? ?? ?? ?? 44 0F 29 50 ??")] 31 | private Hook _rmiCameraHook = null!; 32 | 33 | public OverrideCamera() 34 | { 35 | Service.Hook.InitializeFromAttributes(this); 36 | Service.Log.Information($"RMICamera address: 0x{_rmiCameraHook.Address:X}"); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | _rmiCameraHook.Dispose(); 42 | } 43 | 44 | private void RMICameraDetour(CameraEx* self, int inputMode, float speedH, float speedV) 45 | { 46 | _rmiCameraHook.Original(self, inputMode, speedH, speedV); 47 | if(IgnoreUserInput || inputMode == 0) // let user override... 48 | { 49 | var dt = Framework.Instance()->FrameDeltaTime; 50 | var deltaH = (DesiredAzimuth - self->DirH.Radians()).Normalized(); 51 | var deltaV = (DesiredAltitude - self->DirV.Radians()).Normalized(); 52 | var maxH = SpeedH.Rad * dt; 53 | var maxV = SpeedV.Rad * dt; 54 | self->InputDeltaH = Math.Clamp(deltaH.Rad, -maxH, maxH); 55 | self->InputDeltaV = Math.Clamp(deltaV.Rad, -maxV, maxV); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskMoveToHouse.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation; 2 | using ECommons.GameHelpers; 3 | using ECommons.Throttlers; 4 | using FFXIVClientStructs.FFXIV.Client.Game; 5 | using Lifestream.Data; 6 | using Action = Lumina.Excel.Sheets.Action; 7 | 8 | namespace Lifestream.Tasks.Utility; 9 | public static unsafe class TaskMoveToHouse 10 | { 11 | public static void Enqueue(PlotInfo info, bool includeFirst) 12 | { 13 | P.TaskManager.EnqueueMulti( 14 | new(() => UseSprint()), 15 | new(() => LoadPath(info, includeFirst), "LoadPath"), 16 | new(WaitUntilPathCompleted, TaskSettings.Timeout5M), 17 | C.AutoDismount?new(Utils.DismountIfNeeded):null 18 | ); 19 | } 20 | 21 | public static bool? UseSprint(bool? mount = null) 22 | { 23 | if(Player.IsAnimationLocked) return false; 24 | if(mount ?? C.UseMount) 25 | { 26 | if(!TaskMount.MountIfCan()) 27 | { 28 | return false; 29 | } 30 | } 31 | if(!C.UseSprintPeloton && !C.UsePeloton) return true; 32 | if(Player.Object?.StatusList?.Any(x => x.StatusId.EqualsAny(50, 1199, 4209)) == true) return true; 33 | List abilities = []; 34 | if(C.UseSprintPeloton) abilities.Add(3); 35 | if(C.UsePeloton) abilities.Add(7557); 36 | foreach(var ability in abilities) 37 | { 38 | if(ActionManager.Instance()->GetActionStatus(ActionType.Action, ability) == 0) 39 | { 40 | if(EzThrottler.Throttle("ExecSpritAction")) 41 | { 42 | Chat.ExecuteCommand($"/action \"{Svc.Data.GetExcelSheet().GetRow(ability).Name.GetText()}\""); 43 | return true; 44 | } 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | public static bool? LoadPath(PlotInfo info, bool includeFirst) 51 | { 52 | if(info.Path.Count == 0) return null; 53 | P.FollowPath.Stop(); 54 | P.FollowPath.Move([.. info.Path], true); 55 | if(!includeFirst) P.FollowPath.RemoveFirst(); 56 | return true; 57 | } 58 | 59 | public static bool WaitUntilPathCompleted() => P.FollowPath.Waypoints.Count == 0; 60 | } 61 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskMultipathExecute.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation; 2 | using ECommons.GameHelpers; 3 | using ECommons.Throttlers; 4 | using FFXIVClientStructs.FFXIV.Client.Game; 5 | using Lifestream.Data; 6 | 7 | namespace Lifestream.Tasks.Utility; 8 | public static unsafe class TaskMultipathExecute 9 | { 10 | private static uint LastTerritory = 0; 11 | 12 | public static void Enqueue(MultiPath path) 13 | { 14 | LastTerritory = 0; 15 | P.TaskManager.Enqueue(() => Execute(path), $"ExecuteMultipath {path.Name}", TaskSettings.TimeoutInfinite); 16 | } 17 | 18 | private static bool Execute(MultiPath mpath) 19 | { 20 | if(!Player.Interactable || !IsScreenReady()) 21 | { 22 | P.FollowPath.Stop(); 23 | return false; 24 | } 25 | var path = mpath.Entries.FirstOrDefault(x => x.Territory == P.Territory); 26 | if(P.Territory != LastTerritory) 27 | { 28 | P.FollowPath.Stop(); 29 | var points = (List)[.. path.Points]; 30 | var distance = float.MaxValue; 31 | var index = 0; 32 | for(var i = 0; i < points.Count; i++) 33 | { 34 | if(Vector3.Distance(Player.Object.Position, points[i]) < distance) 35 | { 36 | index = i; 37 | distance = Vector3.Distance(Player.Object.Position, points[i]); 38 | } 39 | } 40 | points = points[index..]; 41 | P.FollowPath.Move(points, true); 42 | LastTerritory = path.Territory; 43 | } 44 | else 45 | { 46 | if(Svc.Condition[ConditionFlag.InCombat] || path?.Sprint == true) 47 | { 48 | var status = ActionManager.Instance()->GetActionStatus(ActionType.Action, 3); 49 | if(status == 0) 50 | { 51 | if(EzThrottler.Throttle("UseSprint", 250)) 52 | { 53 | Chat.ExecuteCommand("/action Sprint"); 54 | } 55 | } 56 | } 57 | P.FollowPath.UpdateTimeout(10); 58 | if(P.FollowPath.Waypoints.Count == 0) 59 | { 60 | P.NotificationMasterApi.DisplayTrayNotification("Multipath completed"); 61 | return true; 62 | } 63 | } 64 | return false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskMount.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation; 2 | using ECommons.GameHelpers; 3 | using ECommons.Throttlers; 4 | using FFXIVClientStructs.FFXIV.Client.Game; 5 | using FFXIVClientStructs.FFXIV.Client.Game.UI; 6 | using Lumina.Excel.Sheets; 7 | 8 | namespace Lifestream.Tasks.Utility; 9 | public static unsafe class TaskMount 10 | { 11 | public static bool MountIfCan() 12 | { 13 | if(Svc.Condition[ConditionFlag.Mounted]) 14 | { 15 | return true; 16 | } 17 | if(C.Mount == -1) return true; 18 | if(Svc.Condition[ConditionFlag.MountOrOrnamentTransition] || Svc.Condition[ConditionFlag.Casting]) 19 | { 20 | EzThrottler.Throttle("CheckMount", 2000, true); 21 | } 22 | if(!EzThrottler.Check("CheckMount")) return false; 23 | if(ActionManager.Instance()->GetActionStatus(ActionType.GeneralAction, 9) == 0) 24 | { 25 | var mount = C.Mount; 26 | if(mount == 0 || !PlayerState.Instance()->IsMountUnlocked((uint)mount)) 27 | { 28 | var mounts = Svc.Data.GetExcelSheet().Where(x => x.Singular != "" && PlayerState.Instance()->IsMountUnlocked(x.RowId)); 29 | if(mounts.Any()) 30 | { 31 | if(mounts.Count() == 1) 32 | { 33 | var newMount = (int)mounts.First().RowId; 34 | PluginLog.Warning($"Mount {Utils.GetMountName(mount)} is not unlocked. Selecting {Utils.GetMountName(newMount)}."); 35 | mount = newMount; 36 | } 37 | else 38 | { 39 | mount = 0; 40 | } 41 | } 42 | else 43 | { 44 | PluginLog.Warning("No unlocked mounts found"); 45 | return true; 46 | } 47 | } 48 | if(!Player.IsAnimationLocked && EzThrottler.Throttle("SummonMount")) 49 | { 50 | if(mount == 0) 51 | { 52 | Chat.ExecuteGeneralAction(9); 53 | } 54 | else 55 | { 56 | Chat.ExecuteCommand($"/mount \"{Utils.GetMountName(mount)}\""); 57 | } 58 | } 59 | } 60 | else 61 | { 62 | return true; 63 | } 64 | return false; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Lifestream/Systems/Residential/ResidentialAetheryte.cs: -------------------------------------------------------------------------------- 1 | using Lumina.Excel.Sheets; 2 | 3 | namespace Lifestream.Systems.Residential; 4 | public struct ResidentialAetheryte : IEquatable, IAetheryte 5 | { 6 | public Vector2 Position { get; set; } 7 | public uint TerritoryType { get; set; } 8 | public string Name { get; set; } 9 | public uint ID { get; set; } 10 | public bool IsSubdivision; 11 | private HousingAethernet Ref; 12 | 13 | public ResidentialAetheryte(HousingAethernet data, bool isSubdivision, Vector2 subdivisionPositionModifier) 14 | { 15 | Ref = data; 16 | Name = data.PlaceName.Value.Name.GetText(); 17 | TerritoryType = data.TerritoryType.RowId; 18 | ID = data.RowId; 19 | IsSubdivision = isSubdivision; 20 | Position = GetCoordinates(); 21 | if(isSubdivision) Position += subdivisionPositionModifier; 22 | } 23 | 24 | public override bool Equals(object obj) 25 | { 26 | return obj is ResidentialAetheryte aetheryte && Equals(aetheryte); 27 | } 28 | 29 | public bool Equals(ResidentialAetheryte other) 30 | { 31 | return Position.Equals(other.Position) && 32 | TerritoryType == other.TerritoryType && 33 | Name == other.Name && 34 | ID == other.ID; 35 | } 36 | 37 | public override int GetHashCode() 38 | { 39 | return HashCode.Combine(Position, TerritoryType, Name, ID); 40 | } 41 | 42 | private readonly Vector2 GetCoordinates() 43 | { 44 | var AethersX = 0f; 45 | var AethersY = 0f; 46 | var reference = this; 47 | { 48 | var map = Svc.Data.GetExcelSheet().FirstOrDefault(m => m.TerritoryType.RowId == reference.Ref.TerritoryType.RowId); 49 | var scale = map.SizeFactor; 50 | if(Svc.Data.GetSubrowExcelSheet().AllRows().TryGetFirst(m => m.DataType == 4 && m.DataKey.RowId == reference.Ref.PlaceName.RowId, out var mapMarker)) 51 | { 52 | AethersX = Utils.ConvertMapMarkerToRawPosition(mapMarker.X, scale); 53 | AethersY = Utils.ConvertMapMarkerToRawPosition(mapMarker.Y, scale); 54 | } 55 | } 56 | return new(AethersX, AethersY); 57 | } 58 | 59 | public static bool operator ==(ResidentialAetheryte left, ResidentialAetheryte right) 60 | { 61 | return left.Equals(right); 62 | } 63 | 64 | public static bool operator !=(ResidentialAetheryte left, ResidentialAetheryte right) 65 | { 66 | return !(left == right); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Lifestream/Data/CustomAlias.cs: -------------------------------------------------------------------------------- 1 | using NightmareUI.OtterGuiWrapper.FileSystems.Generic; 2 | 3 | namespace Lifestream.Data; 4 | [Serializable] 5 | public class CustomAlias : IFileSystemStorage 6 | { 7 | public string ExportedName; 8 | public Guid GUID { get; set; } = Guid.NewGuid(); 9 | public string Alias = ""; 10 | public bool Enabled = true; 11 | public List Commands = []; 12 | 13 | public bool ShouldSerializeAlias() => Alias.Length > 0; 14 | public bool ShouldSerializeEnabled() => Enabled != true; 15 | public bool ShouldSerializeGUID() => GUID != Guid.Empty; 16 | 17 | public string GetCustomName() => null; 18 | public void SetCustomName(string s) { } 19 | 20 | public void Enqueue(bool force = false, int? inclusiveStart = null, int? exclusiveEnd = null) 21 | { 22 | foreach(var x in Commands) 23 | { 24 | if(!x.CanExecute(out var e)) 25 | { 26 | DuoLog.Error($"{e}"); 27 | return; 28 | } 29 | } 30 | if(force || !Utils.IsBusy()) 31 | { 32 | var cmds = Commands; 33 | if(inclusiveStart.HasValue && inclusiveStart.Value > 0 && inclusiveStart.Value < cmds.Count) 34 | { 35 | cmds = [.. cmds.Skip(inclusiveStart.Value)]; 36 | } 37 | 38 | if(exclusiveEnd.HasValue && exclusiveEnd.Value > 0 && exclusiveEnd.Value < cmds.Count) 39 | { 40 | cmds = [.. cmds.Take(exclusiveEnd.Value - (inclusiveStart ?? 0))]; 41 | } 42 | for(var i = 0; i < cmds.Count; i++) 43 | { 44 | List append = []; 45 | var cmd = cmds[i]; 46 | if(cmd.Kind.EqualsAny(CustomAliasKind.Move_to_point, CustomAliasKind.Navmesh_to_point, CustomAliasKind.Circular_movement) == true) 47 | { 48 | while(cmds.SafeSelect(i + 1)?.Kind == CustomAliasKind.Move_to_point) 49 | { 50 | if(this.IsChainedWithNext(i + (inclusiveStart ?? 0))) 51 | { 52 | var c = cmds[i + 1]; 53 | append.Add(c.Point.Scatter(c.Scatter)); 54 | i++; 55 | PluginLog.Information($"Appending command {i}"); 56 | } 57 | else 58 | { 59 | break; 60 | } 61 | } 62 | } 63 | cmd.Enqueue(append); 64 | } 65 | } 66 | else 67 | { 68 | Notify.Error("Lifestream is busy!"); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskApproachAndInteractWithApartmentEntrance.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Enums; 2 | using ECommons.GameFunctions; 3 | using ECommons.GameHelpers; 4 | using ECommons.Throttlers; 5 | using FFXIVClientStructs.FFXIV.Client.Game.Control; 6 | using Lifestream.Schedulers; 7 | 8 | namespace Lifestream.Tasks.SameWorld; 9 | public static class TaskApproachAndInteractWithApartmentEntrance 10 | { 11 | public static void Enqueue(bool betweenAreas) 12 | { 13 | if(betweenAreas) P.TaskManager.Enqueue(() => Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51], "WaitUntilBetweenAreas"); 14 | P.TaskManager.Enqueue(Utils.WaitForScreen); 15 | P.TaskManager.Enqueue(TargetApartmentEntrance); 16 | P.TaskManager.Enqueue(WorldChange.LockOn); 17 | P.TaskManager.Enqueue(WorldChange.EnableAutomove); 18 | P.TaskManager.Enqueue(() => Vector3.Distance(Player.Object.Position, Svc.Targets.Target.Position) < 3.5f, "ReachApartment"); 19 | P.TaskManager.Enqueue(WorldChange.DisableAutomove); 20 | P.TaskManager.Enqueue(InteractWithApartmentEntrance); 21 | } 22 | 23 | public static bool TargetApartmentEntrance() 24 | { 25 | //2007402 apartment building entrance 0 apartment building entrances 0 1 1 0 0 26 | foreach(var x in Svc.Objects.OrderBy(x => Vector3.Distance(x.Position, Player.Object.Position))) 27 | { 28 | if(x.DataId == 2007402) 29 | { 30 | if(!x.IsTarget()) 31 | { 32 | if(EzThrottler.Throttle("TargetApartment")) 33 | { 34 | Svc.Targets.SetTarget(x); 35 | } 36 | return false; 37 | } 38 | else 39 | { 40 | return true; 41 | } 42 | } 43 | } 44 | return false; 45 | } 46 | 47 | public static unsafe bool InteractWithApartmentEntrance() 48 | { 49 | if(Player.IsAnimationLocked) return false; 50 | if(!Utils.DismountIfNeeded()) return false; 51 | if(Svc.Targets.Target?.ObjectKind == ObjectKind.EventObj && Svc.Targets.Target?.DataId == 2007402) 52 | { 53 | if(EzThrottler.Throttle("InteractWithApartment", 5000)) 54 | { 55 | TargetSystem.Instance()->InteractWithObject(Svc.Targets.Target.Struct(), false); 56 | return true; 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | public static unsafe bool GoToMyApartment() 63 | { 64 | return Utils.TrySelectSpecificEntry(Lang.GoToMyApartment, () => EzThrottler.Throttle("SelectStringApartment")); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Lifestream/GUI/UIServiceAccount.cs: -------------------------------------------------------------------------------- 1 | namespace Lifestream.GUI; 2 | 3 | internal static class UIServiceAccount 4 | { 5 | internal static void Draw() 6 | { 7 | ImGuiEx.TextWrapped($"If you own more than 1 service accounts, you must assign each character to the correct service account.\nTo make character appear in this list, please log into it."); 8 | ImGui.Checkbox($"Get service account data from AutoRetainer", ref C.UseAutoRetainerAccounts); 9 | List ManagedByAR = []; 10 | if(P.AutoRetainerApi?.Ready == true && C.UseAutoRetainerAccounts) 11 | { 12 | var chars = P.AutoRetainerApi.GetRegisteredCharacters(); 13 | foreach(var c in chars) 14 | { 15 | var data = P.AutoRetainerApi.GetOfflineCharacterData(c); 16 | if(data != null) 17 | { 18 | var name = $"{data.Name}@{data.World}"; 19 | ManagedByAR.Add(name); 20 | ImGui.SetNextItemWidth(150f.Scale()); 21 | if(ImGui.BeginCombo($"{name}", data.ServiceAccount == -1 ? "Not selected" : $"Service account {data.ServiceAccount + 1}")) 22 | { 23 | for(var i = 0; i < 10; i++) 24 | { 25 | if(ImGui.Selectable($"Service account {i + 1}")) 26 | { 27 | C.ServiceAccounts[name] = i; 28 | data.ServiceAccount = i; 29 | P.AutoRetainerApi.WriteOfflineCharacterData(data); 30 | Notify.Info($"Setting saved to AutoRetainer"); 31 | } 32 | } 33 | ImGui.EndCombo(); 34 | } 35 | ImGui.SameLine(); 36 | ImGuiEx.Text(ImGuiColors.DalamudRed, $"Managed by AutoRetainer"); 37 | } 38 | } 39 | } 40 | foreach(var x in C.ServiceAccounts) 41 | { 42 | if(ManagedByAR.Contains(x.Key)) continue; 43 | ImGui.SetNextItemWidth(150f.Scale()); 44 | if(ImGui.BeginCombo($"{x.Key}", x.Value == -1 ? "Not selected" : $"Service account {x.Value + 1}")) 45 | { 46 | for(var i = 0; i < 10; i++) 47 | { 48 | if(ImGui.Selectable($"Service account {i + 1}")) C.ServiceAccounts[x.Key] = i; 49 | } 50 | ImGui.EndCombo(); 51 | } 52 | ImGui.SameLine(); 53 | if(ImGui.Button("Delete")) 54 | { 55 | new TickScheduler(() => C.ServiceAccounts.Remove(x.Key)); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Lifestream/GUI/Windows/SelectWorldWindow.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using ECommons.GameHelpers; 3 | using ECommons.SimpleGui; 4 | 5 | namespace Lifestream.GUI.Windows; 6 | public class SelectWorldWindow : Window 7 | { 8 | private SelectWorldWindow() : base("Lifestream: Select World", ImGuiWindowFlags.AlwaysAutoResize) 9 | { 10 | EzConfigGui.WindowSystem.AddWindow(this); 11 | } 12 | 13 | public override void Draw() 14 | { 15 | var worlds = S.Data.DataStore.DCWorlds.Concat(S.Data.DataStore.Worlds).Select(x => ExcelWorldHelper.Get(x)).OrderBy(x => x?.Name.ToString()); 16 | if(!worlds.Any()) 17 | { 18 | ImGuiEx.Text($"No available destinations"); 19 | return; 20 | } 21 | var datacenters = worlds.Select(x => x?.DataCenter).DistinctBy(x => x?.RowId).OrderBy(x => x.Value.ValueNullable?.Region).ToArray(); 22 | if(ImGui.BeginTable("LifestreamSelectWorld", datacenters.Length, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.BordersV | ImGuiTableFlags.BordersOuter)) 23 | { 24 | foreach(var dc in datacenters) 25 | { 26 | var modifier = ""; 27 | if(Player.Object?.HomeWorld.ValueNullable?.DataCenter.RowId == dc?.RowId) modifier += ""; 28 | if(Player.Object?.CurrentWorld.ValueNullable?.DataCenter.RowId != dc?.RowId) modifier += ""; 29 | ImGui.TableSetupColumn($"{modifier}{dc.Value.ValueNullable?.Name}"); 30 | } 31 | ImGui.TableHeadersRow(); 32 | ImGui.TableNextRow(); 33 | var buttonSize = Vector2.Zero; 34 | foreach(var w in worlds) 35 | { 36 | var newSize = ImGuiHelpers.GetButtonSize("" + w?.Name.ToString()); 37 | if(newSize.X > buttonSize.X) buttonSize = newSize; 38 | } 39 | buttonSize += new Vector2(0, C.ButtonHeightWorld); 40 | foreach(var dc in datacenters) 41 | { 42 | ImGui.TableNextColumn(); 43 | foreach(var world in worlds) 44 | { 45 | if(world?.DataCenter.RowId == dc?.RowId) 46 | { 47 | var modifier = ""; 48 | if(Player.Object?.HomeWorld.RowId == world?.RowId) modifier += ""; 49 | if(ImGuiEx.Button(modifier + world?.Name.ToString(), buttonSize, !Utils.IsBusy() && Player.Interactable && Player.Object?.CurrentWorld.RowId != world?.RowId)) 50 | { 51 | P.ProcessCommand("/li", world?.Name.ToString()); 52 | } 53 | } 54 | } 55 | } 56 | ImGui.EndTable(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Lifestream/Services/ContextMenuManager.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Gui.ContextMenu; 2 | using Dalamud.Game.Text; 3 | using ECommons.ExcelServices; 4 | using ECommons.UIHelpers.AddonMasterImplementations; 5 | using FFXIVClientStructs.FFXIV.Component.GUI; 6 | using Lifestream.GUI.Windows; 7 | 8 | namespace Lifestream.Services; 9 | public unsafe class ContextMenuManager : IDisposable 10 | { 11 | private ContextMenuManager() 12 | { 13 | Svc.ContextMenu.OnMenuOpened += ContextMenu_OnMenuOpened; 14 | } 15 | 16 | public void Dispose() 17 | { 18 | Svc.ContextMenu.OnMenuOpened -= ContextMenu_OnMenuOpened; 19 | } 20 | 21 | private void ContextMenu_OnMenuOpened(IMenuOpenedArgs args) 22 | { 23 | if(C.AllowDCTravelFromCharaSelect) 24 | { 25 | if(TryGetAddonMaster(out var m)) 26 | { 27 | if(args.Target is MenuTargetDefault target && m.Characters.TryGetFirst(x => x.IsSelected, out var chara)) 28 | { 29 | args.AddMenuItem(ConstructMenuItemFor(chara.Name, chara.HomeWorld, chara.CurrentWorld)); 30 | if(chara.HomeWorld != chara.CurrentWorld) 31 | { 32 | args.AddMenuItem(ConstructReturnHomeMenuItemFor(chara.Name, chara.HomeWorld, chara.CurrentWorld, chara.IsVisitingAnotherDC)); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | private MenuItem ConstructMenuItemFor(string name, uint homeWorld, uint currentWorld) 40 | { 41 | var ret = new MenuItem() 42 | { 43 | Name = "Lifestream", 44 | Prefix = (SeIconChar)'', 45 | OnClicked = (args) => 46 | { 47 | P.CharaSelectOverlay.Open(name, homeWorld); 48 | } 49 | }; 50 | return ret; 51 | } 52 | 53 | private MenuItem ConstructReturnHomeMenuItemFor(string name, uint homeWorld, uint currentWorld, bool isVisitingAnotherDC) 54 | { 55 | var ret = new MenuItem() 56 | { 57 | Name = "To Home World", 58 | Prefix = (SeIconChar)'', 59 | OnClicked = (args) => 60 | { 61 | if(isVisitingAnotherDC) 62 | { 63 | CharaSelectOverlay.ReconnectToValidDC(name, currentWorld, homeWorld, ExcelWorldHelper.Get(homeWorld).Value, false); 64 | } 65 | else 66 | { 67 | P.TaskManager.Enqueue(() => !(TryGetAddonByName("ContextMenu", out var c) && c->IsVisible)); 68 | P.TaskManager.Enqueue(() => CharaSelectOverlay.Command(name, currentWorld, homeWorld, ExcelWorldHelper.Get(homeWorld).Value, false)); 69 | } 70 | } 71 | }; 72 | return ret; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Lifestream/GUI/ProgressOverlay.cs: -------------------------------------------------------------------------------- 1 | using ECommons.SimpleGui; 2 | using ECommons.SplatoonAPI; 3 | 4 | namespace Lifestream.GUI; 5 | 6 | public class ProgressOverlay : Window 7 | { 8 | public ProgressOverlay() : base("Lifestream progress overlay", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize, true) 9 | { 10 | EzConfigGui.WindowSystem.AddWindow(this); 11 | IsOpen = true; 12 | RespectCloseHotkey = false; 13 | } 14 | 15 | public override void PreDraw() 16 | { 17 | SizeConstraints = new() 18 | { 19 | MinimumSize = new(ImGuiHelpers.MainViewport.Size.X, 0), 20 | MaximumSize = new(0, float.MaxValue) 21 | }; 22 | } 23 | 24 | public override void Draw() 25 | { 26 | CImGui.igBringWindowToDisplayBack(CImGui.igGetCurrentWindow()); 27 | if(ImGui.IsWindowHovered()) 28 | { 29 | ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); 30 | ImGui.SetTooltip("Right click to stop all tasks and movement"); 31 | if(ImGui.IsMouseClicked(ImGuiMouseButton.Right)) 32 | { 33 | P.TaskManager.Abort(); 34 | P.followPath?.Stop(); 35 | } 36 | } 37 | float percent; 38 | Vector4 col; 39 | string overlay; 40 | if(P.followPath != null && P.FollowPath.Waypoints.Count > 0) 41 | { 42 | percent = 1f - (float)P.FollowPath.Waypoints.Count / (float)P.FollowPath.MaxWaypoints; 43 | col = GradientColor.Get(EColor.Red, EColor.Violet); 44 | overlay = $"Lifestream Movement: {P.FollowPath.MaxWaypoints - P.FollowPath.Waypoints.Count}/{P.FollowPath.MaxWaypoints}"; 45 | if(Splatoon.IsConnected()) 46 | { 47 | S.Ipc.SplatoonManager.RenderPath(P.FollowPath.Waypoints); 48 | } 49 | } 50 | else 51 | { 52 | percent = 1f - (float)P.TaskManager.NumQueuedTasks / (float)P.TaskManager.MaxTasks; 53 | col = EColor.Violet; 54 | overlay = $"Lifestream Progress: {P.TaskManager.MaxTasks - P.TaskManager.NumQueuedTasks}/{P.TaskManager.MaxTasks}"; 55 | } 56 | ImGui.PushStyleColor(ImGuiCol.PlotHistogram, col); 57 | ImGui.ProgressBar(percent, new(ImGui.GetContentRegionAvail().X, 20), overlay); 58 | ImGui.PopStyleColor(); 59 | // Toggle ProgressOverlay position logic 60 | if(C.ProgressOverlayToTop) 61 | { 62 | Position = new(0, 0); 63 | } 64 | else 65 | { 66 | Position = new(0, ImGuiHelpers.MainViewport.Size.Y - ImGui.GetWindowSize().Y); 67 | } 68 | } 69 | 70 | public override bool DrawConditions() 71 | { 72 | //return ((P.TaskManager.IsBusy && P.TaskManager.MaxTasks > 0)) && !C.NoProgressBar; 73 | return ((P.TaskManager.IsBusy && P.TaskManager.MaxTasks > 0) || (P.followPath != null && P.FollowPath.Waypoints.Count > 0)) && !C.NoProgressBar; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Lifestream/Services/InstanceHandler.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.Addon.Lifecycle; 2 | using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; 3 | using Dalamud.Game.ClientState.Objects.Enums; 4 | using ECommons.Configuration; 5 | using ECommons.Throttlers; 6 | using ECommons.UIHelpers.AddonMasterImplementations; 7 | using FFXIVClientStructs.FFXIV.Client.Game.UI; 8 | using Lifestream.Tasks.SameWorld; 9 | 10 | namespace Lifestream.Services; 11 | public unsafe class InstanceHandler : IDisposable 12 | { 13 | private InstanceHandler() 14 | { 15 | Svc.AddonLifecycle.RegisterListener(AddonEvent.PostUpdate, "SelectString", OnPostUpdate); 16 | var gv = CSFramework.Instance()->GameVersionString; 17 | if(!gv.IsNullOrEmpty() && gv != C.GameVersion) 18 | { 19 | PluginLog.Information($"New game version detected, new {gv}, old {C.GameVersion}"); 20 | C.GameVersion = gv; 21 | C.PublicInstances = []; 22 | } 23 | C.PublicInstances[1291] = 3; 24 | } 25 | 26 | public Dictionary ExtraInstanceChangers = new() 27 | { 28 | [1291] = (1052625, 6.8f) 29 | }; 30 | 31 | public bool CanChangeInstance() 32 | { 33 | return C.ShowInstanceSwitcher && !Utils.IsDisallowedToUseAethernet() && !P.TaskManager.IsBusy && !IsOccupied() && S.InstanceHandler.GetInstance() != 0 && TaskChangeInstance.GetAetheryte() != null; 34 | } 35 | 36 | private void OnPostUpdate(AddonEvent type, AddonArgs args) 37 | { 38 | if( 39 | UIState.Instance()->PublicInstance.IsInstancedArea() 40 | && Svc.Targets.Target?.ObjectKind == ObjectKind.Aetheryte 41 | && Svc.Condition[ConditionFlag.OccupiedInQuestEvent] 42 | && TryGetAddonMaster(out var m) 43 | && m.IsAddonReady 44 | && (m.Entries.Any(x => x.Text.ContainsAny(Lang.TravelToInstancedArea)) || m.Text == Lang.ToReduceCongestion) 45 | ) 46 | { 47 | var inst = *S.Memory.MaxInstances; 48 | if(inst < 2 || inst > 9) 49 | { 50 | if(EzThrottler.Throttle("InstanceWarning", 5000)) PluginLog.Warning($"Instance count is wrong, received {inst}, please report to developer"); 51 | } 52 | else 53 | { 54 | if(C.PublicInstances.TryGetValue(P.Territory, out var value) && value == inst) 55 | { 56 | // 57 | } 58 | else 59 | { 60 | C.PublicInstances[P.Territory] = inst; 61 | EzConfig.Save(); 62 | } 63 | } 64 | } 65 | } 66 | 67 | public int GetInstance() 68 | { 69 | return (int)UIState.Instance()->PublicInstance.InstanceId; 70 | } 71 | 72 | public bool InstancesInitizliaed(out int maxInstances) 73 | { 74 | return C.PublicInstances.TryGetValue(P.Territory, out maxInstances); 75 | } 76 | 77 | public void Dispose() 78 | { 79 | Svc.AddonLifecycle.UnregisterListener(AddonEvent.PostUpdate, "SelectString", OnPostUpdate); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskApproachHousingAetheryte.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Enums; 2 | using ECommons.Automation; 3 | using ECommons.ExcelServices.TerritoryEnumeration; 4 | using ECommons.GameHelpers; 5 | using ECommons.Throttlers; 6 | using Lifestream.Schedulers; 7 | using Lifestream.Tasks.Utility; 8 | 9 | namespace Lifestream.Tasks.SameWorld; 10 | public static class TaskApproachHousingAetheryte 11 | { 12 | public static readonly (Vector3 Pos, float Distance) EmpyreumIMP = (new Vector3(20.540209f, -15.2f, 179.47063f), 29.78f); 13 | public static readonly (Vector3 Pos, float Distance) LavenderIMP = (new Vector3(3.1033986f, 2.8884888f, 191.80864f), 9.35f); 14 | public static readonly (Vector3 Pos, float Distance) ShiroIMP = (new Vector3(-103.11274f, 2.02f, 129.29942f), 8f); 15 | public static void Enqueue() 16 | { 17 | P.TaskManager.EnqueueMulti( 18 | C.WaitForScreenReady ? new(Utils.WaitForScreen) : null, 19 | new(() => TaskMoveToHouse.UseSprint(false)), 20 | new(MoveIMP), 21 | new(WaitUntilArrivesAtIMP), 22 | new(TargetNearestShard), 23 | new(WorldChange.LockOn), 24 | new(WorldChange.EnableAutomove), 25 | new(() => S.Data.ResidentialAethernet.ActiveAetheryte != null, "Wait until residential aetheryte exists"), 26 | new(WorldChange.DisableAutomove) 27 | ); 28 | } 29 | 30 | public static void MoveIMP() 31 | { 32 | if(P.Territory.EqualsAny(ResidentalAreas.Empyreum)) 33 | { 34 | P.FollowPath.Move([EmpyreumIMP.Pos], true); 35 | } 36 | else if(P.Territory.EqualsAny(ResidentalAreas.Shirogane, ResidentalAreas.The_Lavender_Beds)) 37 | { 38 | Chat.ExecuteCommand("/automove on"); 39 | } 40 | } 41 | 42 | public static bool WaitUntilArrivesAtIMP() 43 | { 44 | if(P.Territory == ResidentalAreas.Empyreum) 45 | { 46 | return !P.FollowPath.Waypoints.Any(); 47 | } 48 | if(P.Territory == ResidentalAreas.The_Lavender_Beds) 49 | { 50 | return Svc.Objects.Any(x => Utils.AethernetShards.Contains(x.DataId) && Vector3.Distance(Player.Object.Position, x.Position) < LavenderIMP.Distance); 51 | } 52 | if(P.Territory == ResidentalAreas.Shirogane) 53 | { 54 | return Player.Object.Position.Z < 128f; 55 | } 56 | return true; 57 | } 58 | 59 | //public static bool WaitUntilMovementStopped() => P.FollowPath.Waypoints.Count == 0; 60 | 61 | public static bool TargetNearestShard() 62 | { 63 | if(!Player.Interactable) return false; 64 | foreach(var x in Svc.Objects.OrderBy(z => Vector3.Distance(Player.Object.Position, z.Position))) 65 | { 66 | if(Utils.AethernetShards.Contains(x.DataId) && x.IsTargetable && x.ObjectKind == ObjectKind.EventObj) 67 | { 68 | if(EzThrottler.Throttle("TargetNearestShard")) 69 | { 70 | Svc.Targets.SetTarget(x); 71 | return true; 72 | } 73 | } 74 | } 75 | return false; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Utility/TaskGeneratePath.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Data; 3 | using Lumina.Excel.Sheets; 4 | using ResidentialAetheryte = Lifestream.Systems.Residential.ResidentialAetheryte; 5 | 6 | namespace Lifestream.Tasks.Utility; 7 | public static class TaskGeneratePath 8 | { 9 | public static void Enqueue(int plotNum, PlotInfo info) 10 | { 11 | P.TaskManager.Enqueue(() => !S.Ipc.VnavmeshIPC.PathfindInProgress()); 12 | P.TaskManager.Enqueue(() => 13 | { 14 | DuoLog.Information($"Pathfind begin for {plotNum + 1} from aetheryte {Svc.Data.GetExcelSheet().GetRow(info.AethernetID).PlaceName.Value.Name}"); 15 | var task = S.Ipc.VnavmeshIPC.Pathfind(Player.Object.Position, info.Front, false); 16 | P.TaskManager.InsertMulti( 17 | new(() => task.IsCompleted), 18 | new(() => 19 | { 20 | if(!task.IsCompletedSuccessfully) 21 | { 22 | DuoLog.Error($"-- Pathfind failed"); 23 | return null; 24 | } 25 | else 26 | { 27 | DuoLog.Information($"-- Success for {plotNum + 1}, distance={Utils.CalculatePathDistance([Player.Object.Position, .. task.Result])}"); 28 | info.Path = [.. task.Result]; 29 | Utils.SaveGeneratedHousingData(); 30 | return true; 31 | } 32 | }) 33 | ); 34 | }); 35 | } 36 | 37 | public static void EnqueueValidate(int plotNum, PlotInfo info, ResidentialAetheryte aetheryte) 38 | { 39 | P.TaskManager.Enqueue(() => !S.Ipc.VnavmeshIPC.PathfindInProgress()); 40 | P.TaskManager.Enqueue(() => 41 | { 42 | var task = S.Ipc.VnavmeshIPC.Pathfind(Player.Object.Position, info.Front, false); 43 | P.TaskManager.InsertMulti( 44 | new(() => task.IsCompleted), 45 | new(() => 46 | { 47 | if(!task.IsCompletedSuccessfully) 48 | { 49 | DuoLog.Error($"-- Pathfind failed"); 50 | return null; 51 | } 52 | else 53 | { 54 | var distanceNew = Utils.CalculatePathDistance([.. task.Result]); 55 | var distanceOld = Utils.CalculatePathDistance([.. info.Path]); 56 | if(distanceNew < distanceOld) 57 | { 58 | DuoLog.Warning($"-- For plot {plotNum + 1}, old distance was {distanceOld} > {distanceNew} new distance, replacing path and aetheryte from {Svc.Data.GetExcelSheet().GetRowOrDefault(info.AethernetID)?.PlaceName.ValueNullable?.Name}, please double-check path."); 59 | info.Path = [.. task.Result]; 60 | info.AethernetID = aetheryte.ID; 61 | Utils.SaveGeneratedHousingData(); 62 | } 63 | return true; 64 | } 65 | }) 66 | ); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Lifestream/Services/AddressBookFileSystemManager.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | using Lifestream.Data; 3 | using NightmareUI.OtterGuiWrapper.FileSystems.Generic; 4 | 5 | namespace Lifestream.Services; 6 | public class AddressBookFileSystemManager 7 | { 8 | public GenericFileSystem FileSystem; 9 | private AddressBookFileSystemManager() 10 | { 11 | FileSystem = new(C.AddressBookFolders, "AddressBook"); 12 | FileSystem.Selector.OnAfterDrawLeafName += Selector_OnAfterDrawLeafName; 13 | FileSystem.Selector.OnBeforeItemCreation += Selector_OnBeforeItemCreation; 14 | FileSystem.Selector.OnBeforeCopy += Selector_OnBeforeCopy; 15 | FileSystem.Selector.OnImportPopupOpen += Selector_OnImportPopupOpen; 16 | } 17 | private void Selector_OnBeforeCopy(AddressBookFolder original, ref AddressBookFolder copy) 18 | { 19 | copy.IsCopy = true; 20 | if(FileSystem.FindLeaf(original, out var leaf)) 21 | { 22 | copy.ExportedName = leaf.Name; 23 | } 24 | } 25 | 26 | private void Selector_OnBeforeItemCreation(ref AddressBookFolder item) 27 | { 28 | if(item.Entries == null) 29 | { 30 | item = null; 31 | Notify.Error($"Item contains invalid data"); 32 | } 33 | item.IsDefault = false; 34 | item.GUID = Guid.NewGuid(); 35 | } 36 | 37 | private void Selector_OnImportPopupOpen(string clipboardText, ref string newName) 38 | { 39 | try 40 | { 41 | var item = EzConfig.DefaultSerializationFactory.Deserialize(clipboardText); 42 | if(item != null && item.ExportedName != null && !item.ExportedName.EqualsIgnoreCase("Default Book")) 43 | { 44 | newName = item.ExportedName; 45 | } 46 | } 47 | catch(Exception e) { } 48 | } 49 | 50 | private void Selector_OnAfterDrawLeafName(AddressBookFS.Leaf leaf, GenericFileSystem.FileSystemSelector.State arg2, bool arg3) 51 | { 52 | if(ImGui.BeginDragDropTarget()) 53 | { 54 | if(ImGuiDragDrop.AcceptDragDropPayload("MoveRule", out Guid payload)) 55 | { 56 | AddressBookEntry entry = null; 57 | AddressBookFolder folder = null; 58 | foreach(var f in C.AddressBookFolders) 59 | { 60 | foreach(var e in f.Entries) 61 | { 62 | if(e.GUID == payload) 63 | { 64 | entry = e; 65 | folder = f; 66 | break; 67 | } 68 | } 69 | } 70 | if(entry == null) 71 | { 72 | Notify.Error("Could not move"); 73 | } 74 | else if(folder == leaf.Value) 75 | { 76 | Notify.Error($"Could not move to the same folder"); 77 | } 78 | else 79 | { 80 | folder.Entries.Remove(entry); 81 | leaf.Value.Entries.Add(entry); 82 | } 83 | } 84 | ImGui.EndDragDropTarget(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskGoToResidentialDistrict.cs: -------------------------------------------------------------------------------- 1 | 2 | using ECommons.Automation; 3 | using ECommons.Automation.UIInput; 4 | using ECommons.GameHelpers; 5 | using ECommons.Throttlers; 6 | using ECommons.UIHelpers.AddonMasterImplementations; 7 | using FFXIVClientStructs.FFXIV.Client.UI; 8 | using FFXIVClientStructs.FFXIV.Component.GUI; 9 | using Lifestream.Schedulers; 10 | using Callback = ECommons.Automation.Callback; 11 | 12 | namespace Lifestream.Tasks.SameWorld; 13 | public static unsafe class TaskGoToResidentialDistrict 14 | { 15 | public static void Enqueue(int ward) 16 | { 17 | if(ward < 1 || ward > 30) throw new ArgumentOutOfRangeException(nameof(ward)); 18 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 19 | P.TaskManager.Enqueue(WorldChange.TargetValidAetheryte); 20 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 21 | P.TaskManager.Enqueue(() => Utils.TrySelectSpecificEntry(Lang.ResidentialDistrict, () => EzThrottler.Throttle("SelectResidentialDistrict")), $"TaskGoToResidentialDistrictSelect {Lang.ResidentialDistrict}"); 22 | P.TaskManager.Enqueue(() => Utils.TrySelectSpecificEntry(Lang.GoToWard, () => EzThrottler.Throttle("SelectGoToWard")), $"TaskGoToResidentialDistrictSelect {Lang.GoToWard}"); 23 | if(ward > 1) P.TaskManager.Enqueue(() => SelectWard(ward)); 24 | P.TaskManager.Enqueue(GoToWard); 25 | P.TaskManager.Enqueue(ConfirmYesNoGoToWard); 26 | P.TaskManager.EnqueueTask(new(() => Player.Interactable && S.Data.ResidentialAethernet.IsInResidentialZone(), "Wait until player arrives")); 27 | } 28 | 29 | public static bool ConfirmYesNoGoToWard() 30 | { 31 | if(Svc.Condition[ConditionFlag.BetweenAreas] || Svc.Condition[ConditionFlag.BetweenAreas51]) return true; 32 | var x = (AddonSelectYesno*)Utils.GetSpecificYesno(true, Lang.TravelTo); 33 | if(x != null) 34 | { 35 | if(x->YesButton->IsEnabled && EzThrottler.Throttle("ConfirmTravelTo")) 36 | { 37 | new AddonMaster.SelectYesno(x).Yes(); 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | public static bool? SelectWard(int ward) 45 | { 46 | if(TryGetAddonByName("HousingSelectBlock", out var addon) && IsAddonReady(addon)) 47 | { 48 | if(ward == 1) 49 | { 50 | return true; 51 | } 52 | else 53 | { 54 | if(EzThrottler.Throttle("HousingSelectBlockSelectWard")) 55 | { 56 | Callback.Fire(addon, true, 1, ward - 1); 57 | return true; 58 | } 59 | } 60 | } 61 | return false; 62 | } 63 | 64 | public static bool? GoToWard() 65 | { 66 | if(TryGetAddonByName("HousingSelectBlock", out var addon) && IsAddonReady(addon)) 67 | { 68 | var button = addon->GetComponentButtonById(34); 69 | if(button->IsEnabled) 70 | { 71 | if(EzThrottler.Throttle("HousingSelectBlockConfirm")) 72 | { 73 | button->ClickAddonButton(addon); 74 | return true; 75 | } 76 | } 77 | } 78 | return false; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Lifestream/IPC/IpcUtils.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using Lifestream.GUI.Windows; 3 | 4 | namespace Lifestream.IPC; 5 | public static unsafe class IpcUtils 6 | { 7 | public static bool InitiateTravelFromCharaSelectScreenInternal(string charaName, string charaHomeWorld, string? destination, bool noLogin) 8 | { 9 | var homeWorldData = ExcelWorldHelper.Get(charaHomeWorld) ?? throw new NullReferenceException($"Invalid home world specified: {charaHomeWorld}"); 10 | if(CharaSelectOverlay.TryGetValidCharaSelectListMenu(out var m)) 11 | { 12 | var chara = m.Characters.FirstOrDefault(x => x.Name == charaName && x.HomeWorld == homeWorldData.RowId); 13 | if(chara == null) 14 | { 15 | //PluginLog.Error($"Character not found: {charaName}@{charaHomeWorld}"); 16 | return false; 17 | } 18 | 19 | var worlds = Utils.GetVisitableWorldsFrom(homeWorldData).ToArray(); 20 | var destinationWorldData = worlds.FirstOrNull(x => x.Name.ToString() == destination); 21 | if(chara.IsVisitingAnotherDC) 22 | { 23 | PluginLog.Information($"Reconnecting to valid DC with: {chara.Name}, world={ExcelWorldHelper.GetName(chara.CurrentWorld)} (2)"); 24 | CharaSelectOverlay.ReconnectToValidDC(chara.Name, chara.CurrentWorld, chara.HomeWorld, destinationWorldData, noLogin); 25 | return true; 26 | } 27 | else 28 | { 29 | CharaSelectOverlay.Command(chara.Name, chara.CurrentWorld, chara.HomeWorld, destinationWorldData, noLogin); 30 | return true; 31 | } 32 | } 33 | else 34 | { 35 | //PluginLog.Error($"Can not initiate travel from current state"); 36 | return false; 37 | } 38 | } 39 | 40 | public static bool InitiateLoginFromCharaSelectScreenInternal(string charaName, string charaHomeWorld) 41 | { 42 | var homeWorldData = ExcelWorldHelper.Get(charaHomeWorld) ?? throw new NullReferenceException($"Invalid home world specified: {charaHomeWorld}"); 43 | if(CharaSelectOverlay.TryGetValidCharaSelectListMenu(out var m)) 44 | { 45 | var chara = m.Characters.FirstOrDefault(x => x.Name == charaName && x.HomeWorld == homeWorldData.RowId); 46 | if(chara == null) 47 | { 48 | //PluginLog.Error($"Character not found: {charaName}@{charaHomeWorld}"); 49 | return false; 50 | } 51 | 52 | var worlds = Utils.GetVisitableWorldsFrom(homeWorldData).ToArray(); 53 | if(chara.IsVisitingAnotherDC) 54 | { 55 | PluginLog.Information($"Reconnecting to valid DC with: {chara.Name}, world={ExcelWorldHelper.GetName(chara.CurrentWorld)} (3)"); 56 | CharaSelectOverlay.ReconnectToValidDC(chara.Name, chara.CurrentWorld, chara.HomeWorld, null, false); 57 | return true; 58 | } 59 | else 60 | { 61 | CharaSelectOverlay.Command(chara.Name, chara.CurrentWorld, chara.HomeWorld, null, false); 62 | return true; 63 | } 64 | } 65 | else 66 | { 67 | //PluginLog.Error($"Can not initiate travel from current state"); 68 | return false; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Lifestream/IPC/SplatoonManager.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using ECommons.SplatoonAPI; 3 | 4 | namespace Lifestream.IPC; 5 | public class SplatoonManager 6 | { 7 | private ulong Frame = 0; 8 | private SplatoonCache Cache = new(); 9 | 10 | private SplatoonManager() 11 | { 12 | Splatoon.SetOnConnect(Reset); 13 | if(Splatoon.IsConnected()) Reset(); 14 | } 15 | 16 | private void Reset() 17 | { 18 | Cache = new(); 19 | } 20 | 21 | private unsafe void ResetOnFrameChange() 22 | { 23 | var frame = CSFramework.Instance()->FrameCounter; 24 | if(frame != Frame) 25 | { 26 | Frame = frame; 27 | Reset(); 28 | } 29 | } 30 | 31 | public void RenderPath(IReadOnlyList path, bool addPlayer = true, bool addNumbers = false) 32 | { 33 | if(!Splatoon.IsConnected()) return; 34 | Vector3? prev = null; 35 | if(path != null && path.Count > 0) 36 | { 37 | for(var i = 0; i < path.Count; i++) 38 | { 39 | var point = GetNextPoint(addNumbers ? (i + 1).ToString() : ""); 40 | point.SetRefCoord(path[i]); 41 | var line = GetNextLine(EColor.Red, 1); 42 | line.SetRefCoord(path[i]); 43 | line.SetOffCoord(prev ?? Player.Object.Position); 44 | line.color = (prev != null ? ImGuiColors.DalamudYellow : ImGuiColors.HealerGreen).ToUint(); 45 | Splatoon.DisplayOnce(point); 46 | if(prev != null || addPlayer) 47 | { 48 | Splatoon.DisplayOnce(line); 49 | } 50 | prev = path[i]; 51 | } 52 | } 53 | } 54 | 55 | public Element GetNextLine(Vector4 color, float thickness) 56 | { 57 | ResetOnFrameChange(); 58 | Element ret; 59 | if(Cache.WaymarkLineCache.Count < Cache.WaymarkLinePos) 60 | { 61 | ret = Cache.WaymarkLineCache[Cache.WaymarkLinePos]; 62 | } 63 | else 64 | { 65 | ret = new Element(ElementType.LineBetweenTwoFixedCoordinates) 66 | { 67 | radius = 0f, 68 | thicc = 1f, 69 | }; 70 | Cache.WaymarkLineCache.Add(ret); 71 | } 72 | Cache.WaymarkLinePos++; 73 | ret.color = color.ToUint(); 74 | ret.thicc = thickness; 75 | return ret; 76 | } 77 | 78 | public Element GetNextPoint(string overlay = "") 79 | { 80 | ResetOnFrameChange(); 81 | Element ret; 82 | if(Cache.WaymarkPointCache.Count < Cache.WaymarkPointPos) 83 | { 84 | ret = Cache.WaymarkPointCache[Cache.WaymarkPointPos]; 85 | } 86 | else 87 | { 88 | ret = new Element(ElementType.CircleAtFixedCoordinates) 89 | { 90 | radius = 0f, 91 | thicc = 3f, 92 | color = ImGuiColors.DalamudRed.ToUint(), 93 | overlayVOffset = 1f, 94 | overlayText = overlay, 95 | Filled = false, 96 | }; 97 | Cache.WaymarkPointCache.Add(ret); 98 | } 99 | Cache.WaymarkPointPos++; 100 | return ret; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossWorld/TaskChangeWorld.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using ECommons.Throttlers; 3 | using Lifestream.Schedulers; 4 | 5 | namespace Lifestream.Tasks.CrossWorld; 6 | 7 | internal static unsafe class TaskChangeWorld 8 | { 9 | internal static void Enqueue(string world) 10 | { 11 | try 12 | { 13 | Utils.AssertCanTravel(Player.Name, Player.Object.HomeWorld.RowId, Player.Object.CurrentWorld.RowId, world); 14 | } 15 | catch(Exception e) { e.Log(); return; } 16 | if(C.WaitForScreenReady) P.TaskManager.Enqueue(Utils.WaitForScreen); 17 | if(C.LeavePartyBeforeWorldChange) 18 | { 19 | if(Svc.Condition[ConditionFlag.RecruitingWorldOnly]) 20 | { 21 | P.TaskManager.Enqueue(WorldChange.ClosePF); 22 | P.TaskManager.Enqueue(WorldChange.OpenSelfPF); 23 | P.TaskManager.Enqueue(WorldChange.EndPF); 24 | P.TaskManager.Enqueue(WorldChange.WaitUntilNotRecruiting); 25 | } 26 | P.TaskManager.Enqueue(WorldChange.LeaveParty); 27 | } 28 | P.TaskManager.Enqueue(WorldChange.TargetValidAetheryte); 29 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 30 | P.TaskManager.Enqueue(WorldChange.SelectVisitAnotherWorld); 31 | P.TaskManager.Enqueue(() => WorldChange.SelectWorldToVisit(world), $"{nameof(WorldChange.SelectWorldToVisit)}, {world}"); 32 | P.TaskManager.Enqueue(() => WorldChange.ConfirmWorldVisit(world), $"{nameof(WorldChange.ConfirmWorldVisit)}, {world}"); 33 | P.TaskManager.Enqueue((Action)(() => EzThrottler.Throttle("RetryWorldVisit", Math.Max(10000, C.RetryWorldVisitInterval * 1000), true))); 34 | P.TaskManager.Enqueue(() => RetryWorldVisit(world), TaskSettings.Timeout5M); 35 | } 36 | 37 | private static int WorldVisitRand = 0; 38 | private static bool RetryWorldVisit(string targetWorld) 39 | { 40 | if(Player.Available && Player.CurrentWorld == targetWorld) 41 | { 42 | return true; 43 | } 44 | if(C.RetryWorldVisit) 45 | { 46 | if(!IsScreenReady() || Svc.Condition[ConditionFlag.WaitingToVisitOtherWorld] || Svc.Condition[ConditionFlag.ReadyingVisitOtherWorld] || Svc.Condition[ConditionFlag.OccupiedInQuestEvent]) 47 | { 48 | EzThrottler.Throttle("RetryWorldVisit", Math.Max(1000, C.RetryWorldVisitInterval * 1000 + WorldVisitRand), true); 49 | return false; 50 | } 51 | if(Svc.Targets.Target == null && Player.Interactable) 52 | { 53 | var aetheryte = Svc.Objects.FirstOrDefault(x => x.IsTargetable && x.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Aetheryte && Player.DistanceTo(x) < 30f); 54 | if(aetheryte != null && EzThrottler.Throttle("Target")) 55 | { 56 | Svc.Targets.Target = aetheryte; 57 | } 58 | } 59 | if(EzThrottler.Check("RetryWorldVisit")) 60 | { 61 | WorldVisitRand = Random.Shared.Next(0, C.RetryWorldVisitIntervalDelta * 1000); 62 | P.TaskManager.BeginStack(); 63 | TaskChangeWorld.Enqueue(targetWorld); 64 | P.TaskManager.InsertStack(); 65 | return true; 66 | } 67 | } 68 | return false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lifestream 2 | Lifestream is a plugin that helps with navigation between data centers, worlds, instances, residential areas. Visit another world, data center or house with just a single command. 3 | ## Become a Supporter! 4 | If you like Lifestream, please consider becoming a supporter on Patreon or via other means! This will help me to continue updating Lifestream and work on new plugins and features and you will receive benefits such as early progress updates, priority support, prioritized feature requests, early testing builds and private tools. 5 | - [Subscribe on Patreon (Starts from $1)](https://subscribe.nightmarexiv.com/) - eligible for Discord role 6 | - [Donate Litecoin, Bitcoin, Tether or other crypto](https://crypto.nightmarexiv.com/) - eligible for Discord role 7 | - [One-time donation on Ko-Fi](https://ko-fi.com/nightmarexiv) 8 | 9 | ### Also: 10 | - [Explore other plugins I maintain or contributed to](https://explore.nightmarexiv.com/) 11 | - [Join NightmareXIV Discord server to receive fast support and pings about plugin updates](https://discord.gg/m8NRt4X8Gf) 12 | ## Key features 13 | - Visit another world with one command, even if on another data center 14 | - One-click to travel between aetherytes 15 | - Quickly switch between area instances 16 | - Use shortcuts to go to your home, fc house, apartment, grand company, market board 17 | - Create address books where you can store residential addresses of your friends or venues and go there at any time with one click 18 | 19 | ## Optional dependencies 20 | - [vnavmesh](https://github.com/awgil/ffxiv_navmesh) from `https://puni.sh/api/repository/veyn` for pathing to your grand company automatically 21 | ## This plugin is in development 22 | This means that there are still features that I would like to implement in future or features that I would like to enhance, as well as that I'm accepting suggestions and feature requests. 23 | ## Installation 24 | 1. Install [FFXIVQuickLauncher](https://github.com/goatcorp/FFXIVQuickLauncher?tab=readme-ov-file#xivlauncher-----) and enable Dalamud in it's settings. You have to run the game through FFXIVQuickLauncher in order for any of these plugins to work. 25 | 2. Open Dalamud settings by typing `/xlsettings` in game chat. 26 | 3. Go to "Experimental" tab. 27 | 4. Find "Custom Plugin Repositories" section, agree with listed terms if needed and paste the following link into text input field: `https://github.com/NightmareXIV/MyDalamudPlugins/raw/main/pluginmaster.json` 28 | 5. Click "Save" button. 29 | 30 | You should now have NightmareXIV plugins available in your plugin installer.
31 | Open plugin installer by typing `/xlplugins` in game chat, go to "Available plugins" section and search for a plugin you would like to install. 32 | 33 | ![image](https://github.com/NightmareXIV/MyDalamudPlugins/blob/main/meta/install/installer.png?raw=true) 34 | 35 | ## Support 36 | Join NightmareXIV Discord server to receive support for this plugin: https://discord.gg/m8NRt4X8Gf 37 | [![](https://dcbadge.vercel.app/api/server/m8NRt4X8Gf)](https://discord.gg/m8NRt4X8Gf) 38 | 39 | The server operates on a ticket-based system. Please create a ticket and describe your issue. 40 | Additionally, you may create an issue in the repository. Reply time for tickets may be significantly longer than on Discord, however, the issue does not have any risks to be lost. 41 | (Basically, if you want to report a critical bug or receive help, prefer Discord, if you want to suggest feature or report non-critical bug, prefer Github) 42 | -------------------------------------------------------------------------------- /Lifestream/GUI/TabTravelBan.cs: -------------------------------------------------------------------------------- 1 | using ECommons.GameHelpers; 2 | using Lifestream.Data; 3 | using NightmareUI.ImGuiElements; 4 | 5 | namespace Lifestream.GUI; 6 | public static class TabTravelBan 7 | { 8 | public static void Draw() 9 | { 10 | WorldSelector.Instance.DisplayCurrent = true; 11 | ImGui.PushFont(UiBuilder.IconFont); 12 | ImGuiEx.Text(EColor.RedBright, FontAwesomeIcon.ExclamationTriangle.ToIconString()); 13 | ImGui.PopFont(); 14 | ImGui.SameLine(); 15 | ImGuiEx.TextWrapped(EColor.RedBright, "Be mindful that this function is meant to be the last chance to avoid unrecoverable mistakes. Using this function may break other plugins that rely on Lifestream. Blocking travel in a specific direction will block it only via Lifestream. You can still travel manually."); 16 | 17 | ImGuiEx.LineCentered(() => 18 | { 19 | if(ImGuiEx.IconButtonWithText(FontAwesomeIcon.Plus, "Add new entry")) 20 | { 21 | var entry = new TravelBanInfo(); 22 | if(Player.Available) 23 | { 24 | entry.CharaName = Player.Name; 25 | entry.CharaHomeWorld = (int)Player.Object.HomeWorld.RowId; 26 | } 27 | C.TravelBans.Add(entry); 28 | } 29 | }); 30 | if(ImGui.BeginTable("Bantable", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Borders)) 31 | { 32 | ImGui.TableSetupColumn("##enabled"); 33 | ImGui.TableSetupColumn("Character name and world", ImGuiTableColumnFlags.WidthStretch); 34 | ImGui.TableSetupColumn("Travel source"); 35 | ImGui.TableSetupColumn("Travel destination"); 36 | ImGui.TableSetupColumn("##control"); 37 | 38 | ImGui.TableHeadersRow(); 39 | for(var i = 0; i < C.TravelBans.Count; i++) 40 | { 41 | var entry = C.TravelBans[i]; 42 | ImGui.PushID(entry.ID); 43 | ImGui.TableNextRow(); 44 | ImGui.TableNextColumn(); 45 | ImGui.Checkbox("##en", ref entry.IsEnabled); 46 | ImGui.TableNextColumn(); 47 | ImGuiEx.InputWithRightButtonsArea(() => 48 | { 49 | ImGui.InputTextWithHint("##chara", "Character name", ref entry.CharaName, 30); 50 | }, () => 51 | { 52 | ImGuiEx.Text("@"); 53 | ImGui.SameLine(); 54 | ImGui.SetNextItemWidth(100f.Scale()); 55 | WorldSelector.Instance.Draw(ref entry.CharaHomeWorld); 56 | }); 57 | ImGui.TableNextColumn(); 58 | 59 | ImGui.SetNextItemWidth(100f.Scale()); 60 | if(ImGui.BeginCombo("##from", $"{entry.BannedFrom.Count} worlds", ImGuiComboFlags.HeightLarge)) 61 | { 62 | Utils.DrawWorldSelector(entry.BannedFrom); 63 | ImGui.EndCombo(); 64 | } 65 | ImGui.TableNextColumn(); 66 | 67 | ImGui.SetNextItemWidth(100f.Scale()); 68 | if(ImGui.BeginCombo("##to", $"{entry.BannedTo.Count} worlds", ImGuiComboFlags.HeightLarge)) 69 | { 70 | Utils.DrawWorldSelector(entry.BannedTo); 71 | ImGui.EndCombo(); 72 | } 73 | ImGui.TableNextColumn(); 74 | 75 | if(ImGuiEx.IconButton(FontAwesomeIcon.Trash)) 76 | { 77 | new TickScheduler(() => C.TravelBans.Remove(entry)); 78 | } 79 | 80 | ImGui.PopID(); 81 | } 82 | 83 | ImGui.EndTable(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Lifestream/IPC/TextAdvanceIPC.cs: -------------------------------------------------------------------------------- 1 | using ECommons.EzIpcManager; 2 | 3 | namespace Lifestream.IPC; 4 | public class TextAdvanceIPC 5 | { 6 | [EzIPC("EnqueueMoveTo2DPoint")] public Action EnqueueMoveTo2DPoint; 7 | [EzIPC("EnqueueMoveTo3DPoint")] public Action EnqueueMoveTo3DPoint; 8 | [EzIPC("Stop")] public Action Stop; 9 | [EzIPC("IsBusy")] public Func IsBusy; 10 | /// 11 | /// Enables external control of TextAdvance. 12 | /// First argument = your plugin's name. 13 | /// Second argument is options. Copy ExternalTerritoryConfig to your plugin. Configure it as you wish: set "null" values to features that you want to keep as configured by user. Set "true" or "false" to forcefully enable or disable feature. 14 | /// Returns whether external control successfully enabled or not. When already in external control, it will succeed if called again if plugin name matches with one that already has control and new settings will take effect, otherwise it will fail. 15 | /// External control completely disables territory-specific settings. 16 | /// 17 | [EzIPC] public Func EnableExternalControl; 18 | /// 19 | /// Disables external control. Will fail if external control is obtained from other plugin. 20 | /// 21 | [EzIPC] public Func DisableExternalControl; 22 | /// 23 | /// Indicates whether external control is enabled. 24 | /// 25 | [EzIPC] public Func IsInExternalControl; 26 | 27 | /// 28 | /// Indicates whether user has plugin enabled. Respects territory configuration. If in external control, will return true. 29 | /// 30 | [EzIPC] public Func IsEnabled; 31 | /// 32 | /// Indicates whether plugin is paused by other plugin. 33 | /// 34 | [EzIPC] public Func IsPaused; 35 | 36 | /// 37 | /// All the functions below return currently configured plugin state with respect for territory config and external control. 38 | /// However, it does not includes IsEnabled or IsPaused check. A complete check whether TextAdvance is currently ready to process appropriate event will look like:

39 | /// IsEnabled() && !IsPaused() && GetEnableQuestAccept() 40 | ///
41 | [EzIPC] public Func GetEnableQuestAccept; 42 | [EzIPC] public Func GetEnableQuestComplete; 43 | [EzIPC] public Func GetEnableRewardPick; 44 | [EzIPC] public Func GetEnableCutsceneEsc; 45 | [EzIPC] public Func GetEnableCutsceneSkipConfirm; 46 | [EzIPC] public Func GetEnableRequestHandin; 47 | [EzIPC] public Func GetEnableRequestFill; 48 | [EzIPC] public Func GetEnableTalkSkip; 49 | [EzIPC] public Func GetEnableAutoInteract; 50 | 51 | private TextAdvanceIPC() 52 | { 53 | ECommonsMain.ReducedLogging = true; 54 | EzIPC.Init(this, "TextAdvance", SafeWrapper.AnyException); 55 | ECommonsMain.ReducedLogging = false; 56 | } 57 | 58 | public class MoveData 59 | { 60 | public Vector3 Position; 61 | public uint DataID; 62 | public bool NoInteract; 63 | public bool? Mount = null; 64 | public bool? Fly = null; 65 | } 66 | 67 | public class ExternalTerritoryConfig 68 | { 69 | public bool? EnableQuestAccept = null; 70 | public bool? EnableQuestComplete = null; 71 | public bool? EnableRewardPick = null; 72 | public bool? EnableRequestHandin = null; 73 | public bool? EnableCutsceneEsc = null; 74 | public bool? EnableCutsceneSkipConfirm = null; 75 | public bool? EnableTalkSkip = null; 76 | public bool? EnableRequestFill = null; 77 | public bool? EnableAutoInteract = null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Lifestream/Services/TerritoryWatcher.cs: -------------------------------------------------------------------------------- 1 | using ECommons.ExcelServices; 2 | using ECommons.ExcelServices.TerritoryEnumeration; 3 | using ECommons.GameHelpers; 4 | using ECommons.LazyDataHelpers; 5 | 6 | namespace Lifestream.Services; 7 | public static class TerritoryWatcher 8 | { 9 | public static uint LastHousingOutdoorTerritory = 0; 10 | public static void Initialize() 11 | { 12 | Svc.ClientState.TerritoryChanged += ClientState_TerritoryChanged; 13 | if(Player.Available) 14 | { 15 | ClientState_TerritoryChanged(Svc.ClientState.TerritoryType); 16 | if(Utils.IsInsideHouse() || Utils.IsInsideWorkshop() || Utils.IsInsidePrivateChambers()) 17 | { 18 | DuoLog.Warning($"Lifestream was loaded or updated while being inside house. Please re-enter house to ensure data reliability."); 19 | } 20 | } 21 | Purgatory.Add(() => 22 | { 23 | Svc.ClientState.TerritoryChanged -= ClientState_TerritoryChanged; 24 | }); 25 | } 26 | 27 | public static bool IsDataReliable() => LastHousingOutdoorTerritory != 0; 28 | 29 | private static void ClientState_TerritoryChanged(ushort obj) 30 | { 31 | if(Utils.IsTerritoryResidentialDistrict(obj)) 32 | { 33 | LastHousingOutdoorTerritory = obj; 34 | PluginLog.Debug($"Last residential territory: {ExcelTerritoryHelper.GetName(obj)}"); 35 | } 36 | } 37 | 38 | public static ushort GetRealTerritoryType() 39 | { 40 | if(Svc.ClientState.TerritoryType.EqualsAny(Houses.Private_Cottage_Empyreum, Houses.Private_Cottage_Mist, Houses.Private_Cottage_Shirogane, Houses.Private_Cottage_The_Goblet, Houses.Private_Cottage_The_Lavender_Beds, 1249)) 41 | { 42 | return LastHousingOutdoorTerritory switch 43 | { 44 | ResidentalAreas.Mist => Houses.Private_Cottage_Mist, 45 | ResidentalAreas.The_Lavender_Beds => Houses.Private_Cottage_The_Lavender_Beds, 46 | ResidentalAreas.The_Goblet => Houses.Private_Cottage_The_Goblet, 47 | ResidentalAreas.Shirogane => Houses.Private_Cottage_Shirogane, 48 | ResidentalAreas.Empyreum => Houses.Private_Cottage_Empyreum, 49 | _ => Svc.ClientState.TerritoryType 50 | }; 51 | } 52 | if(Svc.ClientState.TerritoryType.EqualsAny(Houses.Private_House_Empyreum, Houses.Private_House_Mist, Houses.Private_House_Shirogane, Houses.Private_House_The_Goblet, Houses.Private_House_The_Lavender_Beds, 1250)) 53 | { 54 | return LastHousingOutdoorTerritory switch 55 | { 56 | ResidentalAreas.Mist => Houses.Private_House_Mist, 57 | ResidentalAreas.The_Lavender_Beds => Houses.Private_House_The_Lavender_Beds, 58 | ResidentalAreas.The_Goblet => Houses.Private_House_The_Goblet, 59 | ResidentalAreas.Shirogane => Houses.Private_House_Shirogane, 60 | ResidentalAreas.Empyreum => Houses.Private_House_Empyreum, 61 | _ => Svc.ClientState.TerritoryType 62 | }; 63 | } 64 | if(Svc.ClientState.TerritoryType.EqualsAny(Houses.Private_Mansion_Empyreum, Houses.Private_Mansion_Mist, Houses.Private_Mansion_Shirogane, Houses.Private_Mansion_The_Goblet, Houses.Private_Mansion_The_Lavender_Beds, 1251)) 65 | { 66 | return LastHousingOutdoorTerritory switch 67 | { 68 | ResidentalAreas.Mist => Houses.Private_Mansion_Mist, 69 | ResidentalAreas.The_Lavender_Beds => Houses.Private_Mansion_The_Lavender_Beds, 70 | ResidentalAreas.The_Goblet => Houses.Private_Mansion_The_Goblet, 71 | ResidentalAreas.Shirogane => Houses.Private_Mansion_Shirogane, 72 | ResidentalAreas.Empyreum => Houses.Private_Mansion_Empyreum, 73 | _ => Svc.ClientState.TerritoryType 74 | }; 75 | } 76 | return Svc.ClientState.TerritoryType; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Lifestream/Tasks/CrossDC/TaskChangeDatacenter.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Throttlers; 2 | using ECommons.UIHelpers.AddonMasterImplementations; 3 | using Lifestream.Schedulers; 4 | using Lifestream.Tasks.Login; 5 | 6 | namespace Lifestream.Tasks.CrossDC; 7 | 8 | internal static class TaskChangeDatacenter 9 | { 10 | internal static int NumRetries = 0; 11 | internal static long RetryAt = 0; 12 | 13 | internal static void Enqueue(string destination, string charaName, uint charaHomeWorld, uint currentLoginWorld) 14 | { 15 | NumRetries = 0; 16 | void tasks() 17 | { 18 | EnqueueVisitTasks(destination, charaName, charaHomeWorld, currentLoginWorld); 19 | P.TaskManager.Enqueue(DCChange.ConfirmDcVisit, TaskSettings.Timeout2M); 20 | P.TaskManager.Enqueue(() => DCChange.ConfirmDcVisit2(destination, charaName, charaHomeWorld, currentLoginWorld, tasks), "ConfirmDCVisit2", TaskSettings.Timeout2M); 21 | } 22 | tasks(); 23 | P.TaskManager.Enqueue(DCChange.SelectOk, TaskSettings.TimeoutInfinite); 24 | P.TaskManager.Enqueue(() => DCChange.SelectServiceAccount(Utils.GetServiceAccount(charaName, charaHomeWorld)), $"SelectServiceAccount_{charaName}@{charaHomeWorld}", TaskSettings.Timeout1M); 25 | } 26 | 27 | private static void EnqueueVisitTasks(string destination, string charaName, uint charaHomeWorld, uint currentLoginWorld) 28 | { 29 | var dc = Utils.GetDataCenterName(destination); 30 | PluginLog.Debug($"Beginning data center changing process. Destination: {dc}, {destination}"); 31 | P.TaskManager.Enqueue(TaskChangeCharacter.ResetWorldIndex); 32 | P.TaskManager.Enqueue(() => DCChange.OpenContextMenuForChara(charaName, charaHomeWorld, currentLoginWorld), nameof(DCChange.OpenContextMenuForChara), TaskSettings.Timeout5M); 33 | P.TaskManager.Enqueue(DCChange.SelectVisitAnotherDC); 34 | P.TaskManager.Enqueue(DCChange.ConfirmDcVisitIntention); 35 | P.TaskManager.Enqueue(() => DCChange.SelectTargetDataCenter(dc), nameof(DCChange.SelectTargetDataCenter), TaskSettings.Timeout2M); 36 | P.TaskManager.Enqueue(() => RetryAt = Environment.TickCount64 + C.DcvRetryInterval * 1000); 37 | P.TaskManager.Enqueue(() => DCChange.SelectTargetWorld(destination, () => RetryVisit(destination, charaName, charaHomeWorld, currentLoginWorld)), nameof(DCChange.SelectTargetWorld), TaskSettings.Timeout60M); 38 | } 39 | 40 | internal static void ProcessUnableDialogue(string destination, string charaName, uint charaWorld, uint currentLoginWorld) 41 | { 42 | if(TryGetAddonMaster(out var m) && m.IsAddonReady) 43 | { 44 | if(m.Text.ContainsAny(Lang.UnableToSelectWorldForDcv) && EzThrottler.Throttle("RetryVisitOnFaulire")) 45 | { 46 | m.Ok(); 47 | P.TaskManager.Abort(); 48 | EnqueueVisitTasks(destination, charaName, charaWorld, currentLoginWorld); 49 | } 50 | } 51 | } 52 | 53 | private static bool RetryVisit(string destination, string charaName, uint charaWorld, uint currentLoginWorld) 54 | { 55 | if(!C.EnableDvcRetry) return false; 56 | //PluginLog.Information($"Retrying DC visit"); 57 | if(RetryAt == 0) 58 | { 59 | RetryAt = Environment.TickCount64 + C.DcvRetryInterval * 1000; 60 | } 61 | else if(Environment.TickCount64 > RetryAt) 62 | { 63 | RetryAt = 0; 64 | NumRetries++; 65 | if(NumRetries > C.MaxDcvRetries) 66 | { 67 | PluginLog.Warning($"DC visit retry limit exceeded"); 68 | P.TaskManager.Abort(); 69 | return true; 70 | } 71 | P.TaskManager.BeginStack(); 72 | P.TaskManager.Enqueue(DCChange.CancelDcVisit); 73 | EnqueueVisitTasks(destination, charaName, charaWorld, currentLoginWorld); 74 | P.TaskManager.InsertStack(); 75 | return true; 76 | } 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Lifestream/Data/AddressBookEntry.cs: -------------------------------------------------------------------------------- 1 | global using AddressBookEntryTuple = (string Name, int World, int City, int Ward, int PropertyType, int Plot, int Apartment, bool ApartmentSubdivision, bool AliasEnabled, string Alias); 2 | using ECommons.ExcelServices; 3 | using Lifestream.Enums; 4 | using Lifestream.GUI; 5 | using Newtonsoft.Json; 6 | using System.ComponentModel; 7 | using System.Diagnostics.CodeAnalysis; 8 | 9 | namespace Lifestream.Data; 10 | [Serializable] 11 | public class AddressBookEntry 12 | { 13 | private static JsonSerializerSettings JsonSerializerSettings = new() 14 | { 15 | DefaultValueHandling = DefaultValueHandling.Ignore, 16 | }; 17 | 18 | internal Guid GUID = Guid.NewGuid(); 19 | [DefaultValue("")] public string Name = ""; 20 | public int World = 21; 21 | public ResidentialAetheryteKind City = ResidentialAetheryteKind.Uldah; 22 | public int Ward = 1; 23 | public PropertyType PropertyType; 24 | public int Plot = 1; 25 | public int Apartment = 1; 26 | public bool ApartmentSubdivision = false; 27 | [DefaultValue(false)] public bool AliasEnabled = false; 28 | [DefaultValue("")] public string Alias = ""; 29 | 30 | public AddressBookEntryTuple AsTuple() 31 | { 32 | return (Name, World, (int)City, Ward, (int)PropertyType, Plot, Apartment, ApartmentSubdivision, AliasEnabled, Alias); 33 | } 34 | 35 | public static AddressBookEntry FromTuple(AddressBookEntryTuple tuple) 36 | { 37 | return new() 38 | { 39 | Name = tuple.Name, 40 | World = tuple.World, 41 | City = (ResidentialAetheryteKind)tuple.City, 42 | Alias = tuple.Alias, 43 | AliasEnabled = tuple.AliasEnabled, 44 | Apartment = tuple.Apartment, 45 | ApartmentSubdivision = tuple.ApartmentSubdivision, 46 | Plot = tuple.Plot, 47 | PropertyType = (PropertyType)tuple.PropertyType, 48 | Ward = tuple.Ward, 49 | }; 50 | } 51 | 52 | public bool ShouldSerializeApartment() => PropertyType == PropertyType.Apartment; 53 | public bool ShouldSerializeApartmentSubdivision() => PropertyType == PropertyType.Apartment; 54 | public bool ShouldSerializePlot() => PropertyType == PropertyType.House; 55 | 56 | public string GetAddressString() 57 | { 58 | if(PropertyType == PropertyType.House) 59 | { 60 | return $"{ExcelWorldHelper.GetName(World)}, {TabAddressBook.ResidentialNames.SafeSelect(City)}, W{Ward}, P{Plot}"; 61 | } 62 | if(PropertyType == PropertyType.Apartment) 63 | { 64 | return $"{ExcelWorldHelper.GetName(World)}, {TabAddressBook.ResidentialNames.SafeSelect(City)}, W{Ward}{(ApartmentSubdivision ? " subdivision" : "")}, Apartment {Apartment}"; 65 | } 66 | return ""; 67 | } 68 | 69 | public bool IsValid([NotNullWhen(false)] out string error) 70 | { 71 | if(Name == null) 72 | { 73 | error = "Name is not a valid string"; 74 | return false; 75 | } 76 | if(!ExcelWorldHelper.GetPublicWorlds().Any(x => x.RowId == World)) 77 | { 78 | error = "World identifier is not valid"; 79 | return false; 80 | } 81 | if(!Enum.GetValues().Contains(City)) 82 | { 83 | error = "Residential aetheryte is not valid"; 84 | return false; 85 | } 86 | if(Ward < 1 || Ward > 30) 87 | { 88 | error = "Ward number is out of range"; 89 | return false; 90 | } 91 | if(Plot < 1 || Plot > 60) 92 | { 93 | error = "Plot number is out of range"; 94 | return false; 95 | } 96 | if(Apartment < 1) 97 | { 98 | error = "Apartment number is out of range"; 99 | return false; 100 | } 101 | if(Name == null) 102 | { 103 | error = "Alias is not a valid string"; 104 | return false; 105 | } 106 | error = null; 107 | return true; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Lifestream/Paissa/PaissaData.cs: -------------------------------------------------------------------------------- 1 | using Lifestream.Data; 2 | using Lifestream.Enums; 3 | using Newtonsoft.Json; 4 | using NightmareUI.OtterGuiWrapper.FileSystems.Generic; 5 | 6 | namespace Lifestream.Paissa; 7 | 8 | public static class PaissaData 9 | { 10 | public class PaissaResponse 11 | { 12 | [JsonProperty("id")] 13 | public int Id; 14 | [JsonProperty("name")] 15 | public string Name; 16 | [JsonProperty("districts")] 17 | public List Districts; 18 | [JsonProperty("num_open_plots")] 19 | public int NumOpenPlots; 20 | [JsonProperty("oldest_plot_time")] 21 | public float OldestPlotTime; 22 | } 23 | 24 | public class PaissaDistrict 25 | { 26 | [JsonProperty("id")] 27 | public int Id; 28 | [JsonProperty("name")] 29 | public string Name; 30 | [JsonProperty("num_open_plots")] 31 | public int NumOpenPlots; 32 | [JsonProperty("oldest_plot_time")] 33 | public float OldestPlotTime; 34 | [JsonProperty("open_plots")] 35 | public List OpenPlots; 36 | } 37 | 38 | public class PaissaPlot 39 | { 40 | [JsonProperty("world_id")] 41 | public int WorldId; 42 | [JsonProperty("district_id")] 43 | public int DistrictId; 44 | [JsonProperty("ward_number")] 45 | public int WardNumber; 46 | [JsonProperty("plot_number")] 47 | public int PlotNumber; 48 | [JsonProperty("size")] 49 | public int Size; 50 | [JsonProperty("price")] 51 | public int Price; 52 | [JsonProperty("last_updated_time")] 53 | public float LastUpdatedTime; 54 | [JsonProperty("first_seen_time")] 55 | public float FirstSeenTime; 56 | [JsonProperty("est_time_open_min")] 57 | public float ESTTimeOpenMin; 58 | [JsonProperty("est_time_open_max")] 59 | public float ESTTimeOpenMax; 60 | [JsonProperty("purchase_system")] 61 | public int PurchaseSystem; 62 | [JsonProperty("lotto_entries")] 63 | public int? LottoEntries; 64 | [JsonProperty("lotto_phase")] 65 | public int? LottoPhase; 66 | [JsonProperty("lotto_phase_until")] 67 | public float? LottoPhaseUntil; 68 | } 69 | 70 | public class PaissaResult 71 | { 72 | public PaissaStatus Status { get; set; } 73 | public string FolderText { get; set; } 74 | } 75 | 76 | public enum PaissaStatus 77 | { 78 | Idle, Progress, Success, Error 79 | } 80 | 81 | public class PaissaAddressBookEntry : AddressBookEntry 82 | { 83 | public int Size = 1; 84 | public int Bids = 1; 85 | public int AllowedTenants = 1; 86 | } 87 | 88 | public static PaissaAddressBookEntry ToPaissa(this AddressBookEntry entry) 89 | { 90 | var tuple = entry.AsTuple(); 91 | return new PaissaAddressBookEntry 92 | { 93 | Name = tuple.Name, 94 | World = tuple.World, 95 | City = (ResidentialAetheryteKind)tuple.City, 96 | Ward = tuple.Ward, 97 | PropertyType = (PropertyType)tuple.PropertyType, 98 | Plot = tuple.Plot, 99 | Apartment = tuple.Apartment, 100 | ApartmentSubdivision = tuple.ApartmentSubdivision, 101 | AliasEnabled = tuple.AliasEnabled, 102 | Alias = tuple.Alias, 103 | Size = 1, 104 | Bids = 1, 105 | AllowedTenants = 1, 106 | }; 107 | } 108 | 109 | [Serializable] 110 | public class PaissaAddressBookFolder : IFileSystemStorage 111 | { 112 | internal bool IsCopy = false; 113 | public string ExportedName = ""; 114 | public Guid GUID { get; set; } = Guid.NewGuid(); 115 | public List Entries = []; 116 | public bool IsDefault = false; 117 | public SortMode SortMode = SortMode.Manual; 118 | 119 | public bool ShouldSerializeGUID() => !IsCopy; 120 | public bool ShouldSerializeIsDefault() => !IsCopy; 121 | public bool ShouldSerializeExportedName() => IsCopy; 122 | 123 | public string GetCustomName() => null; 124 | public void SetCustomName(string s) { } 125 | } 126 | } -------------------------------------------------------------------------------- /Lifestream/Systems/Residential/ResidentialAethernet.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Types; 2 | using ECommons.Configuration; 3 | using ECommons.ExcelServices.TerritoryEnumeration; 4 | using ECommons.MathHelpers; 5 | using Lifestream.Data; 6 | using Lumina.Excel.Sheets; 7 | using Path = System.IO.Path; 8 | 9 | namespace Lifestream.Systems.Residential; 10 | public sealed class ResidentialAethernet : IDisposable 11 | { 12 | private const string FileName = "HousingData.json"; 13 | public HousingData HousingData; 14 | 15 | public readonly Dictionary ZoneInfo = new() 16 | { 17 | [ResidentalAreas.The_Goblet] = new() { SubdivisionModifier = new(-700f, -700f) }, 18 | [ResidentalAreas.Mist] = new() { SubdivisionModifier = new(-700f, -700f) }, 19 | [ResidentalAreas.The_Lavender_Beds] = new() { SubdivisionModifier = new(-700f, -700f) }, 20 | [ResidentalAreas.Empyreum] = new() { SubdivisionModifier = new(-704f, -654f) }, 21 | [ResidentalAreas.Shirogane] = new() { SubdivisionModifier = new(-700f, -700f) }, 22 | }; 23 | 24 | //1966103 4660263 f1h1 Amethyst Shallows 6 25 | //1966081 4573387 s1h1 Mistgate Square 1 26 | //1966118 4656726 w1h1 Goblet North 5 27 | //1966145 8791382 r1h1 Highmorn's Horizon 1 28 | //1966129 6794232 e1h1 Akanegumo Bridge 0 29 | public static readonly uint[] StartingAetherytes = [1966103, 1966081, 1966118, 1966145, 1966129]; 30 | 31 | //1966132 6794243 e1h1 Kobai Goten 3 32 | //1966088 6472423 s1h1 The Topmast 7 33 | //1966120 6472443 w1h1 The Sultana's Breath 7 34 | //1966104 6472421 f1h1 Lily Hills 7 35 | //1966149 8791386 r1h1 Ingleside 5 36 | public static readonly uint[] ApartmentAetherytes = [1966132, 1966088, 1966120, 1966104, 1966149]; 37 | 38 | //1966142 6794277 e1h1 Kobai Goten Subdivision 11 39 | //1966096 6472426 s1h1 The Topmast Subdivision 15 40 | //1966128 6472444 w1h1 The Sultana's Breath Subdivision 15 41 | //1966112 6472422 f1h1 Lily Hills Subdivision 15 42 | //1966157 8791394 r1h1 Ingleside Subdivision 14 43 | public static readonly uint[] ApartmentSubdivisionAetherytes = [1966142, 1966096, 1966128, 1966112, 1966157]; 44 | 45 | public ResidentialAetheryte? ActiveAetheryte = null; 46 | 47 | public bool IsInResidentialZone() => ZoneInfo.ContainsKey(P.Territory); 48 | 49 | public ResidentialAethernet() 50 | { 51 | try 52 | { 53 | HousingData = EzConfig.LoadConfiguration(Path.Combine(Svc.PluginInterface.AssemblyLocation.DirectoryName, FileName), false); 54 | foreach(var zone in ZoneInfo) 55 | { 56 | var values = Svc.Data.GetExcelSheet().Where(a => a.TerritoryType.RowId == zone.Key).OrderBy(x => x.Order); 57 | foreach(var a in values) 58 | { 59 | var aetheryte = new ResidentialAetheryte(a, a.Order >= values.Count() / 2, zone.Value.SubdivisionModifier); 60 | zone.Value.Aetherytes.Add(aetheryte); 61 | } 62 | } 63 | } 64 | catch(Exception e) 65 | { 66 | e.Log(); 67 | } 68 | } 69 | 70 | public void Dispose() 71 | { 72 | 73 | } 74 | 75 | public void Tick() 76 | { 77 | if(Svc.ClientState.LocalPlayer != null && ZoneInfo.ContainsKey(P.Territory)) 78 | { 79 | UpdateActiveAetheryte(); 80 | } 81 | else 82 | { 83 | ActiveAetheryte = null; 84 | } 85 | } 86 | 87 | private void UpdateActiveAetheryte() 88 | { 89 | var a = Utils.GetValidAetheryte(); 90 | if(a != null) 91 | { 92 | var aetheryte = GetFromIGameObject(a); 93 | if(aetheryte != null) 94 | { 95 | if(ActiveAetheryte == null) 96 | { 97 | S.Gui.Overlay.IsOpen = true; 98 | } 99 | ActiveAetheryte = aetheryte; 100 | } 101 | } 102 | else 103 | { 104 | ActiveAetheryte = null; 105 | } 106 | } 107 | 108 | public ResidentialAetheryte? GetFromIGameObject(IGameObject obj) 109 | { 110 | if(obj == null) return null; 111 | var pos2 = obj.Position.ToVector2(); 112 | if(ZoneInfo.TryGetValue(P.Territory, out var result)) 113 | { 114 | foreach(var l in result.Aetherytes) 115 | { 116 | if(Vector2.Distance(l.Position, pos2) < 10) 117 | { 118 | return l; 119 | } 120 | } 121 | } 122 | return null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskChangeInstance.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Enums; 2 | using Dalamud.Game.ClientState.Objects.Types; 3 | using ECommons.Automation; 4 | using ECommons.Automation.NeoTaskManager; 5 | using ECommons.GameFunctions; 6 | using ECommons.GameHelpers; 7 | using ECommons.Throttlers; 8 | using ECommons.UIHelpers.AddonMasterImplementations; 9 | using FFXIVClientStructs.FFXIV.Client.Game.Control; 10 | using Lumina.Excel.Sheets; 11 | 12 | namespace Lifestream.Tasks.SameWorld; 13 | public static unsafe class TaskChangeInstance 14 | { 15 | public static readonly char[] InstanceNumbers = "\0".ToCharArray(); 16 | 17 | public static void Enqueue(int number) 18 | { 19 | var tasks = new TaskManagerTask[] 20 | { 21 | new(InteractWithAetheryte), 22 | new(SelectTravel), 23 | new(() => SelectInstance(number), $"SelectInstance({number})"), 24 | new(() => !IsOccupied()), 25 | new(() => 26 | { 27 | if(C.InstanceSwitcherRepeat && number != S.InstanceHandler.GetInstance()) 28 | { 29 | Enqueue(number); 30 | } 31 | }) 32 | }; 33 | if(C.EnableFlydownInstance) 34 | { 35 | P.TaskManager.Enqueue(() => 36 | { 37 | if(!Svc.Condition[ConditionFlag.InFlight]) 38 | { 39 | return true; 40 | } 41 | if(EzThrottler.Throttle("DropFlight", 1000)) 42 | { 43 | Chat.ExecuteCommand($"/generalaction {Svc.Data.GetExcelSheet().GetRow(23).Name}"); 44 | } 45 | return false; 46 | }); 47 | } 48 | P.TaskManager.EnqueueMulti(tasks); 49 | } 50 | 51 | public static bool SelectInstance(int num) 52 | { 53 | if(TryGetAddonMaster(out var m) && m.IsAddonReady) 54 | { 55 | foreach(var x in m.Entries) 56 | { 57 | if(x.Text.Contains(InstanceNumbers[num])) 58 | { 59 | if(EzThrottler.Throttle("SelectTravelToInstance")) 60 | { 61 | x.Select(); 62 | return true; 63 | } 64 | return false; 65 | } 66 | } 67 | } 68 | return false; 69 | } 70 | 71 | public static bool SelectTravel() 72 | { 73 | if(TryGetAddonMaster(out var m) && m.IsAddonReady) 74 | { 75 | foreach(var x in m.Entries) 76 | { 77 | if(x.Text.ContainsAny(Lang.TravelToInstancedArea)) 78 | { 79 | if(EzThrottler.Throttle("SelectTravelToInstancedArea")) 80 | { 81 | x.Select(); 82 | return true; 83 | } 84 | } 85 | } 86 | } 87 | return false; 88 | } 89 | 90 | public static bool InteractWithAetheryte() 91 | { 92 | if(Svc.Condition[ConditionFlag.OccupiedInQuestEvent]) return true; 93 | if(!Utils.DismountIfNeeded()) return false; 94 | var aetheryte = GetAetheryte() ?? throw new NullReferenceException(); 95 | if(aetheryte.IsTarget()) 96 | { 97 | if(EzThrottler.Throttle("InteractWithAetheryte")) 98 | { 99 | TargetSystem.Instance()->InteractWithObject(aetheryte.Struct(), false); 100 | return false; 101 | } 102 | } 103 | else 104 | { 105 | if(EzThrottler.Throttle("AetheryteSetTarget")) 106 | { 107 | Svc.Targets.Target = aetheryte; 108 | return false; 109 | } 110 | } 111 | return false; 112 | } 113 | 114 | public static IGameObject GetAetheryte() 115 | { 116 | if(S.InstanceHandler.ExtraInstanceChangers.TryGetValue(Player.Territory, out var npcData)) 117 | { 118 | foreach(var x in Svc.Objects) 119 | { 120 | if(x.DataId == npcData.DataID && x.IsTargetable) 121 | { 122 | if(Vector3.Distance(x.Position, Player.Position) < npcData.Distance) 123 | { 124 | return x; 125 | } 126 | } 127 | } 128 | } 129 | foreach(var x in Svc.Objects) 130 | { 131 | if(x.ObjectKind == ObjectKind.Aetheryte && x.IsTargetable) 132 | { 133 | if(Vector3.Distance(x.Position, Player.Position) < 11f) 134 | { 135 | return x; 136 | } 137 | } 138 | } 139 | return null; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Lifestream/Tasks/SameWorld/TaskAetheryteAethernetTeleport.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Automation.NeoTaskManager.Tasks; 2 | using ECommons.GameHelpers; 3 | using ECommons.Throttlers; 4 | using Lifestream.Schedulers; 5 | 6 | namespace Lifestream.Tasks.SameWorld; 7 | 8 | internal static class TaskAetheryteAethernetTeleport 9 | { 10 | // Special values for the firmament. 11 | private const uint FirmamentRootAetheryteId = 70; 12 | internal const uint FirmamentAethernetId = uint.MaxValue; 13 | private const uint FirmamentRootAetheryteTerritoryId = 418; 14 | private const string Firmament = "The Firmament"; 15 | 16 | internal static void Enqueue(uint rootAetheryteId, uint aethernetId) 17 | { 18 | if(aethernetId == FirmamentAethernetId) 19 | { 20 | if(rootAetheryteId != FirmamentRootAetheryteId) 21 | { 22 | throw new Exception($"Special firmament aethernet {FirmamentAethernetId} must be teleported from root aetheryte {FirmamentRootAetheryteId}"); 23 | } 24 | EnqueueInner(FirmamentRootAetheryteId, FirmamentRootAetheryteTerritoryId, Firmament); 25 | return; 26 | } 27 | 28 | if(!S.Data.DataStore.Aetherytes.Keys.TryGetFirst(a => a.ID == rootAetheryteId, out var rootAetheryte)) 29 | { 30 | throw new Exception($"Root aetheryte {rootAetheryteId} not found"); 31 | } 32 | if(S.Data.DataStore.Aetherytes[rootAetheryte].TryGetFirst(a => a.ID == aethernetId, out var aethernet)) 33 | { 34 | EnqueueInner(rootAetheryte.ID, rootAetheryte.TerritoryType, aethernet.Name); 35 | } 36 | else 37 | { 38 | if(Svc.ClientState.TerritoryType != rootAetheryte.TerritoryType || Utils.GetReachableAetheryte(x => Utils.TryGetTinyAetheryteFromIGameObject(x, out var ae) && ae.HasValue && ae.Value.ID == rootAetheryteId) == null) 39 | { 40 | P.TaskManager.InsertMulti( 41 | new(() => S.TeleportService.TeleportToAetheryte(rootAetheryteId), "TeleportToRootAetheryte"), 42 | new(Utils.WaitForScreenFalse), 43 | new(Utils.WaitForScreen) 44 | ); 45 | } 46 | else 47 | { 48 | throw new Exception($"Could not find aetheryte {aethernetId} under root aetheryte {rootAetheryteId}"); 49 | } 50 | } 51 | } 52 | 53 | private static void EnqueueInner(uint rootAetheryteId, uint territoryId, string aethernetName) 54 | { 55 | if(!Player.Available) 56 | { 57 | return; 58 | } 59 | 60 | //DuoLog.Information($"Teleporting to {aethernetName}"); 61 | TaskRemoveAfkStatus.Enqueue(); 62 | 63 | // Teleport to the root aetheryte unless we're already close to it. 64 | P.TaskManager.Enqueue(() => 65 | { 66 | if(Svc.ClientState.TerritoryType != territoryId || Utils.GetReachableAetheryte(x => Utils.TryGetTinyAetheryteFromIGameObject(x, out var ae) && ae.HasValue && ae.Value.ID == rootAetheryteId) == null) 67 | { 68 | P.TaskManager.InsertMulti( 69 | new(() => S.TeleportService.TeleportToAetheryte(rootAetheryteId), "TeleportToRootAetheryte"), 70 | new(Utils.WaitForScreenFalse), 71 | new(Utils.WaitForScreen) 72 | ); 73 | } 74 | }, "ConditionalTeleportToRootAetheryte"); 75 | 76 | // Target and ensure we're in range to interact. 77 | P.TaskManager.EnqueueDelay(10, true); 78 | P.TaskManager.Enqueue(WorldChange.TargetReachableMasterAetheryte); 79 | P.TaskManager.Enqueue(() => 80 | { 81 | if(P.ActiveAetheryte == null) 82 | { 83 | P.TaskManager.InsertMulti( 84 | new(WorldChange.LockOn), 85 | new(WorldChange.EnableAutomove), 86 | new(WorldChange.WaitUntilMasterAetheryteExists), 87 | new(WorldChange.DisableAutomove), 88 | new FrameDelayTask(10) 89 | ); 90 | } 91 | }, "ConditionalLockonTask"); 92 | P.TaskManager.Enqueue(WorldChange.InteractWithTargetedAetheryte); 93 | 94 | // If we're going to the firmament, select the firmament option. 95 | if(aethernetName == Firmament) 96 | { 97 | P.TaskManager.Enqueue(() => Utils.TrySelectSpecificEntry(Lang.TravelToFirmament, () => EzThrottler.Throttle("SelectString")), 98 | "SelectTravelToFirmament"); 99 | return; 100 | } 101 | 102 | // Otherwise, open the aethernet menu and select the destination. 103 | P.TaskManager.Enqueue(WorldChange.SelectAethernet); 104 | P.TaskManager.EnqueueDelay(C.SlowTeleport ? C.SlowTeleportThrottle : 0); 105 | P.TaskManager.Enqueue(() => WorldChange.TeleportToAethernetDestination(aethernetName), 106 | nameof(WorldChange.TeleportToAethernetDestination)); 107 | } 108 | } -------------------------------------------------------------------------------- /Lifestream/Data/Config.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | using Lifestream.Enums; 3 | using Lifestream.Tasks.Shortcuts; 4 | 5 | namespace Lifestream.Data; 6 | 7 | public class Config : IEzConfig 8 | { 9 | public bool Enable = true; 10 | internal bool AllowClosingESC2 = false; 11 | public int ButtonWidth = 10; 12 | public int[] ButtonWidthArray = null; 13 | public int ButtonHeightAetheryte = 1; 14 | public int ButtonHeightWorld = 5; 15 | public bool FixedPosition = false; 16 | public Vector2 Offset = Vector2.Zero; 17 | public bool UseMapTeleport = true; 18 | public bool HideAddon = true; 19 | public HashSet HideAddonList = [.. Utils.DefaultAddons]; 20 | public BasePositionHorizontal PosHorizontal = BasePositionHorizontal.Middle; 21 | public BasePositionVertical PosVertical = BasePositionVertical.Middle; 22 | public bool ShowAethernet = true; 23 | public bool ShowWorldVisit = true; 24 | public HashSet Favorites = []; 25 | public HashSet Hidden = []; 26 | public Dictionary Renames = []; 27 | public WorldChangeAetheryte WorldChangeAetheryte = WorldChangeAetheryte.Uldah; 28 | public bool Firmament = true; 29 | public bool WalkToAetheryte = true; 30 | public bool LeavePartyBeforeWorldChange = true; 31 | public bool AllowDcTransfer = true; 32 | public bool LeavePartyBeforeLogout = true; 33 | public bool TeleportToGatewayBeforeLogout = true; 34 | public bool NoProgressBar = false; 35 | public Dictionary ServiceAccounts = []; 36 | public bool DCReturnToGateway = false; 37 | public bool WorldVisitTPToAethernet = false; 38 | public string WorldVisitTPTarget = ""; 39 | public bool WorldVisitTPOnlyCmd = true; 40 | public bool UseAutoRetainerAccounts = true; 41 | public bool SlowTeleport = false; 42 | public int SlowTeleportThrottle = 0; 43 | public bool WaitForScreenReady = true; 44 | public bool ShowWards = true; 45 | internal bool ShowPlots = false; 46 | public List AddressBookFolders = []; 47 | public bool AddressNoPathing = false; 48 | public bool AddressApartmentNoEntry = false; 49 | public bool SingleBookMode = false; 50 | public List MultiPathes = []; 51 | public string GameVersion = ""; 52 | public Dictionary PublicInstances = []; 53 | public bool ShowInstanceSwitcher = true; 54 | public bool InstanceSwitcherRepeat = true; 55 | public int InstanceButtonHeight = 10; 56 | public bool UseSprintPeloton = true; 57 | public bool UsePeloton = true; 58 | public bool EnableFlydownInstance = true; 59 | public bool DisplayChatTeleport = false; 60 | public bool DisplayPopupNotifications = true; 61 | public List HousePathDatas = []; 62 | public List CustomHousePathDatas = []; 63 | public bool EnterMyApartment = true; 64 | public HouseEnterMode HouseEnterMode = HouseEnterMode.None; 65 | public bool UseReturn = true; 66 | public uint PreferredInn = 0; 67 | public List PropertyPrio = [new(true, TaskPropertyShortcut.PropertyType.Home), new(true, TaskPropertyShortcut.PropertyType.FC), new(true, TaskPropertyShortcut.PropertyType.Apartment), new(true, TaskPropertyShortcut.PropertyType.Inn), new(false, TaskPropertyShortcut.PropertyType.Shared_Estate)]; 68 | public Dictionary> PropertyPrioOverrides = []; 69 | public bool EnableDvcRetry = true; 70 | public int MaxDcvRetries = 3000; 71 | public bool DcvUseAlternativeWorld = true; 72 | public int DcvRetryInterval = 30; 73 | public bool RetryWorldVisit = true; 74 | public int RetryWorldVisitInterval = 30; 75 | public int RetryWorldVisitIntervalDelta = 10; 76 | public List CustomAliases = []; 77 | public bool UseGuestWorldTravel = false; 78 | public bool AllowDCTravelFromCharaSelect = true; 79 | public List TravelBans = []; 80 | public bool TerminateSelfPartyFinder = false; 81 | public Dictionary CharaMap = []; 82 | public bool UseMount = true; 83 | public int Mount = 0; 84 | public bool WotsitIntegrationEnabled = true; 85 | public WotsitIntegrationIncludedItems WotsitIntegrationIncludes = new(); 86 | public bool EnableDtrBar = false; 87 | public Dictionary PreferredSharedEstates = []; 88 | public bool LeftAlignButtons = false; 89 | public int LeftAlignPadding = 0; 90 | public LiCommandBehavior LiCommandBehavior = LiCommandBehavior.Return_to_Home_World; 91 | public bool EnableNotifications = true; 92 | public bool ProgressOverlayToTop = false; 93 | public bool AllowCustomOverrides = false; 94 | public bool DisableMapClickOtherTerritory = false; 95 | public bool EnableAutoCompletion = false; 96 | public bool AutoCompletionFixedWindow = false; 97 | public bool AutoCompletionWindowBottom = false; 98 | public bool AutoCompletionWindowRight = false; 99 | public Vector2 AutoCompletionWindowOffset = Vector2.Zero; 100 | public bool AutoDismount = true; 101 | } 102 | -------------------------------------------------------------------------------- /Lifestream/Game/Memory.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Hooking; 2 | using Dalamud.Utility.Signatures; 3 | using ECommons.CSExtensions; 4 | using ECommons.EzHookManager; 5 | using ECommons.MathHelpers; 6 | using FFXIVClientStructs.FFXIV.Component.GUI; 7 | using Lifestream.Enums; 8 | using Lifestream.Tasks.SameWorld; 9 | using System.Windows.Forms; 10 | 11 | namespace Lifestream.Game; 12 | 13 | public unsafe class Memory : IDisposable 14 | { 15 | internal delegate void AddonDKTWorldList_ReceiveEventDelegate(nint a1, short a2, nint a3, AtkEvent* a4, InputData* a5); 16 | [Signature("48 89 74 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 44 24 ?? F6 81", DetourName = nameof(AddonDKTWorldList_ReceiveEventDetour), Fallibility = Fallibility.Fallible)] 17 | internal Hook AddonDKTWorldList_ReceiveEventHook; 18 | 19 | internal delegate void AtkComponentTreeList_vf31(nint a1, uint a2, byte a3); 20 | [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 41 0F B6 F0", DetourName = nameof(AtkComponentTreeList_vf31Detour), Fallibility = Fallibility.Fallible)] 21 | internal Hook AtkComponentTreeList_vf31Hook; 22 | 23 | [Signature("4C 8D 0D ?? ?? ?? ?? 44 0F B7 41", ScanType = ScanType.StaticAddress, Fallibility = Fallibility.Fallible)] 24 | internal int* MaxInstances; 25 | 26 | internal delegate byte OpenPartyFinderInfoDelegate(void* agentLfg, ulong contentId); 27 | [EzHook("40 53 48 83 EC 20 48 8B D9 E8 ?? ?? ?? ?? 84 C0 74 07 C6 83 ?? ?? ?? ?? ?? 48 83 C4 20 5B C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 40 53", false)] 28 | internal EzHook OpenPartyFinderInfoHook; 29 | 30 | internal byte OpenPartyFinderInfoDetour(void* agentLfg, ulong contentId) 31 | { 32 | PluginLog.Information($"{((nint)agentLfg):X16}, {contentId:X16}"); 33 | return OpenPartyFinderInfoHook.Original(agentLfg, contentId); 34 | } 35 | 36 | private void AtkComponentTreeList_vf31Detour(nint a1, uint a2, byte a3) 37 | { 38 | PluginLog.Debug($"AtkComponentTreeList_vf31Detour: {a1:X16}, {a2}, {a3}"); 39 | AtkComponentTreeList_vf31Hook.Original(a1, a2, a3); 40 | } 41 | 42 | private void AddonDKTWorldList_ReceiveEventDetour(nint a1, short a2, nint a3, AtkEvent* a4, InputData* a5) 43 | { 44 | PluginLog.Debug($"AddonDKTWorldCheck_ReceiveEventDetour: {a1:X16}, {a2}, {a3:X16}, {(nint)a4:X16}, {(nint)a5:X16}"); 45 | PluginLog.Debug($" Event: {(nint)a4->Node:X16}, {(nint)a4->Target:X16}, {(nint)a4->Listener:X16}, {a4->Param}, {(nint)a4->NextEvent:X16}, {a4->State.EventType}, {a4->State.ReturnFlags}, {a4->State.StateFlags}"); 46 | PluginLog.Debug($" Data: {(nint)a5->unk_8:X16}({*a5->unk_8:X16}/{*a5->unk_8:X16}), [{a5->unk_8s->unk_4}/{a5->unk_8s->SelectedItem}] {a5->unk_16}, {a5->unk_24} | "); //{a5->RawDumpSpan.ToArray().Print()} 47 | //var span = new Span((void*)*a5->unk_8, 0x40).ToArray().Select(x => $"{x:X2}"); 48 | //PluginLog.Debug($" Data 2, {a5->unk_8s->unk_4}, {MemoryHelper.ReadRaw((nint)a5->unk_8s->CategorySelection, 4).Print(",")}, :{string.Join(" ", span)}"); 49 | AddonDKTWorldList_ReceiveEventHook.Original(a1, a2, a3, a4, a5); 50 | } 51 | 52 | internal void ConstructEvent(AtkUnitBase* addon, int category, int which, int nodeIndex, int itemToSelect, int itemToHighlight) 53 | { 54 | if(itemToSelect == 0) throw new Exception("Enumeration starts with 1"); 55 | var Event = stackalloc AtkEvent[1] 56 | { 57 | new AtkEvent() 58 | { 59 | Node = null, 60 | Target = (AtkEventTarget*)addon->UldManager.NodeList[nodeIndex], 61 | Listener = &addon->UldManager.NodeList[nodeIndex]->GetAsAtkComponentNode()->Component->AtkEventListener, 62 | Param = 1, 63 | NextEvent = null, 64 | State = new() 65 | { 66 | EventType = AtkEventType.ListItemClick, 67 | ReturnFlags = 0, 68 | StateFlags = 0, 69 | UnkFlags3 = 0, 70 | } 71 | } 72 | }; 73 | var Unk = stackalloc UnknownStruct[1] 74 | { 75 | new() 76 | { 77 | unk_4 = 1, 78 | SelectedItem = itemToSelect - 1 + (category << 8) 79 | } 80 | }; 81 | var ptr = stackalloc nint[1] 82 | { 83 | (nint)Unk 84 | }; 85 | var Data = stackalloc InputData[1] 86 | { 87 | new InputData() 88 | { 89 | unk_8 = ptr, 90 | unk_16 = itemToSelect, 91 | unk_24 = 0, 92 | } 93 | }; 94 | AddonDKTWorldList_ReceiveEventDetour((nint)addon, 35, which, Event, Data); 95 | AtkComponentTreeList_vf31Detour((nint)addon->UldManager.NodeList[nodeIndex]->GetAsAtkComponentList(), (uint)itemToHighlight, 0); 96 | } 97 | 98 | internal Memory() 99 | { 100 | SignatureHelper.Initialise(this); 101 | EzSignatureHelper.Initialize(this); 102 | //AddonDKTWorldList_ReceiveEventHook.Enable(); 103 | } 104 | 105 | public void Dispose() 106 | { 107 | AddonDKTWorldList_ReceiveEventHook?.Disable(); 108 | AddonDKTWorldList_ReceiveEventHook?.Dispose(); 109 | AtkComponentTreeList_vf31Hook?.Disable(); 110 | AtkComponentTreeList_vf31Hook?.Dispose(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Lifestream/GUI/InputWardDetailDialog.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | using Lifestream.Data; 3 | using NightmareUI.ImGuiElements; 4 | 5 | namespace Lifestream.GUI; 6 | public static class InputWardDetailDialog 7 | { 8 | public static AddressBookEntry Entry = null; 9 | public static bool Open = false; 10 | public static void Draw() 11 | { 12 | if(Entry != null) 13 | { 14 | if(!ImGui.IsPopupOpen($"###ABEEditModal")) 15 | { 16 | Open = true; 17 | ImGui.OpenPopup($"###ABEEditModal"); 18 | } 19 | if(ImGui.BeginPopupModal($"Editing {Entry.Name}###ABEEditModal", ref Open, ImGuiWindowFlags.AlwaysAutoResize)) 20 | { 21 | if(ImGui.BeginTable($"ABEEditTable", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit)) 22 | { 23 | ImGui.TableSetupColumn("Edit1", ImGuiTableColumnFlags.WidthFixed, 150); 24 | ImGui.TableSetupColumn("Edit2", ImGuiTableColumnFlags.WidthFixed, 250); 25 | 26 | ImGui.TableNextRow(); 27 | 28 | ImGui.TableNextColumn(); 29 | ImGuiEx.TextV($"Name:"); 30 | ImGui.TableNextColumn(); 31 | ImGuiEx.SetNextItemFullWidth(); 32 | ImGui.InputTextWithHint($"##name", Entry.GetAutoName(), ref Entry.Name, 150); 33 | 34 | ImGui.TableNextColumn(); 35 | ImGuiEx.TextV($"Alias:"); 36 | ImGuiEx.HelpMarker($"If you enable and set alias, you will be able to use it in a \"li\" command: \"/li alias\". Aliases are case-insensitive."); 37 | ImGui.TableNextColumn(); 38 | ImGui.Checkbox($"##alias", ref Entry.AliasEnabled); 39 | if(Entry.AliasEnabled) 40 | { 41 | ImGui.SameLine(); 42 | ImGuiEx.InputWithRightButtonsArea(() => ImGui.InputText($"##aliasname", ref Entry.Alias, 150), () => 43 | { 44 | AddressBookEntry existing = null; 45 | if(Entry.Alias != "" && C.AddressBookFolders.Any(b => b.Entries.TryGetFirst(a => a != Entry && a.AliasEnabled && a.Alias.EqualsIgnoreCase(Entry.Alias), out existing))) 46 | { 47 | ImGuiEx.HelpMarker($"Alias conflict found: this alias already set for {existing?.Name.NullWhenEmpty() ?? existing?.GetAutoName()}", EColor.RedBright, FontAwesomeIcon.ExclamationTriangle.ToIconString()); 48 | } 49 | }); 50 | } 51 | 52 | ImGui.TableNextColumn(); 53 | ImGuiEx.TextV($"World:"); 54 | ImGui.TableNextColumn(); 55 | ImGuiEx.SetNextItemFullWidth(); 56 | WorldSelector.Instance.Draw(ref Entry.World); 57 | 58 | ImGui.TableNextColumn(); 59 | ImGuiEx.TextV($"Residential District:"); 60 | ImGui.TableNextColumn(); 61 | if(Entry.City.RenderIcon()) ImGui.SameLine(0, 1); 62 | ImGuiEx.SetNextItemFullWidth(); 63 | Utils.ResidentialAetheryteEnumSelector($"##resdis", ref Entry.City); 64 | 65 | ImGui.TableNextColumn(); 66 | ImGuiEx.TextV($"Ward:"); 67 | ImGui.TableNextColumn(); 68 | ImGuiEx.SetNextItemFullWidth(); 69 | ImGui.InputInt($"##ward", ref Entry.Ward.ValidateRange(1, 30)); 70 | 71 | ImGui.TableNextColumn(); 72 | ImGuiEx.TextV($"Property Type:"); 73 | ImGui.TableNextColumn(); 74 | ImGuiEx.SetNextItemFullWidth(); 75 | ImGuiEx.EnumRadio(ref Entry.PropertyType, true); 76 | 77 | if(Entry.PropertyType == Enums.PropertyType.Apartment) 78 | { 79 | ImGui.TableNextColumn(); 80 | ImGuiEx.TextV($""); 81 | ImGui.TableNextColumn(); 82 | ImGui.Checkbox("Subdivision", ref Entry.ApartmentSubdivision); 83 | 84 | ImGui.TableNextColumn(); 85 | ImGuiEx.TextV($"Room:"); 86 | ImGui.TableNextColumn(); 87 | ImGuiEx.SetNextItemFullWidth(); 88 | ImGui.InputInt($"##room", ref Entry.Apartment.ValidateRange(1, 99999)); 89 | } 90 | 91 | if(Entry.PropertyType == Enums.PropertyType.House) 92 | { 93 | ImGui.TableNextColumn(); 94 | ImGuiEx.TextV($"Plot:"); 95 | ImGui.TableNextColumn(); 96 | ImGuiEx.SetNextItemFullWidth(); 97 | ImGui.InputInt($"##plot", ref Entry.Plot.ValidateRange(1, 60)); 98 | } 99 | 100 | ImGui.EndTable(); 101 | } 102 | ImGuiEx.LineCentered(() => 103 | { 104 | if(ImGuiEx.IconButtonWithText(FontAwesomeIcon.Save, "Save and close")) 105 | { 106 | Open = false; 107 | EzConfig.Save(); 108 | } 109 | }); 110 | ImGui.EndPopup(); 111 | } 112 | } 113 | if(!Open) Entry = null; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Lifestream/Tasks/Shortcuts/TaskISShortcut.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Game.ClientState.Objects.Types; 2 | using ECommons.Automation.NeoTaskManager.Tasks; 3 | using ECommons.GameHelpers; 4 | using ECommons.Throttlers; 5 | using ECommons.UIHelpers.AddonMasterImplementations; 6 | using FFXIVClientStructs.FFXIV.Client.UI; 7 | using Lifestream.Data; 8 | using Lifestream.Tasks.Utility; 9 | 10 | namespace Lifestream.Tasks.Shortcuts; 11 | public static unsafe class TaskISShortcut 12 | { 13 | public enum IslandNPC : uint 14 | { 15 | Baldin = 1043621, 16 | TactfulTaskmaster = 1043078, 17 | ExcitableExplorer = 1043081, 18 | EnterprisingExporter = 1043464, 19 | HorrendousHoarder = 1043463, 20 | FelicitousFurball = 1043473, 21 | CreatureComforter = 1043466, 22 | ProduceProducer = 1043465, 23 | } 24 | 25 | public static class IslandTerritories 26 | { 27 | public const uint Moraby = 135; 28 | public const uint Island = 1055; 29 | } 30 | 31 | public static readonly Dictionary NPCPoints = new() 32 | { 33 | [IslandNPC.Baldin] = new(173.05f, 14.10f, 668.42f), 34 | [IslandNPC.TactfulTaskmaster] = new(-278.37f, 40.00f, 229.82f), 35 | [IslandNPC.ExcitableExplorer] = new(-265.44f, 40.00f, 234.52f), 36 | [IslandNPC.EnterprisingExporter] = new(-265.72f, 41.01f, 210.44f), 37 | [IslandNPC.HorrendousHoarder] = new(-265.72f, 41.01f, 210.44f), 38 | [IslandNPC.FelicitousFurball] = new(-272.06f, 41.00f, 212.32f), 39 | [IslandNPC.CreatureComforter] = new(-268.60f, 55.20f, 134.44f), 40 | [IslandNPC.ProduceProducer] = new(-258.16f, 55.20f, 135.01f), 41 | }; 42 | 43 | public static void Enqueue(IslandNPC? npcNullable = null, bool returnHome = true) 44 | { 45 | if(P.TaskManager.IsBusy) 46 | { 47 | DuoLog.Error($"Lifestream is busy, could not process request"); 48 | return; 49 | } 50 | if(!Player.Available) 51 | { 52 | DuoLog.Error("Player not available"); 53 | return; 54 | } 55 | if(returnHome) 56 | { 57 | if(!Player.IsInHomeWorld) 58 | { 59 | P.TPAndChangeWorld(Player.HomeWorld, !Player.IsInHomeDC, null, true, null, false, false); 60 | } 61 | P.TaskManager.Enqueue(() => Player.Interactable && Player.IsInHomeWorld && IsScreenReady()); 62 | } 63 | var point = npcNullable == null ? NPCPoints[IslandNPC.TactfulTaskmaster] : NPCPoints[npcNullable.Value]; 64 | P.TaskManager.Enqueue(() => 65 | { 66 | if(P.Territory == IslandTerritories.Island) 67 | { 68 | EnqueueNavToNPC(point); 69 | } 70 | else 71 | { 72 | TravelToIsland(); 73 | } 74 | }); 75 | 76 | IGameObject baldin() => Svc.Objects.FirstOrDefault(x => x.DataId == (uint)IslandNPC.Baldin); 77 | 78 | void TravelToIsland() 79 | { 80 | StaticAlias.IslandSanctuary.Enqueue(true); 81 | P.TaskManager.EnqueueTask(NeoTasks.ApproachObjectViaAutomove(baldin, 6.5f)); 82 | P.TaskManager.EnqueueTask(NeoTasks.InteractWithObject(baldin)); 83 | P.TaskManager.Enqueue(TalkWithBaldin); 84 | P.TaskManager.Enqueue(ConfirmIslandTravel); 85 | P.TaskManager.Enqueue(() => Player.Interactable && IsScreenReady() && P.Territory == IslandTerritories.Island, "WaitUntilPlayerInteractableOnIsland", TaskSettings.Timeout2M); 86 | P.TaskManager.Enqueue(() => EnqueueNavToNPC(point)); 87 | } 88 | 89 | bool TalkWithBaldin() 90 | { 91 | if(TryGetAddonMaster(out var talk)) 92 | talk.Click(); 93 | return Utils.TrySelectSpecificEntry(Lang.TravelToMyIsland, () => EzThrottler.Throttle(nameof(TalkWithBaldin))); 94 | } 95 | 96 | bool ConfirmIslandTravel() 97 | { 98 | var addon = (AddonSelectYesno*)Utils.GetSpecificYesno(true, Lang.TravelToYourIsland); 99 | if(addon != null && addon->YesButton->IsEnabled) 100 | { 101 | if(EzThrottler.Throttle(nameof(ConfirmIslandTravel), 5000)) 102 | { 103 | new AddonMaster.SelectYesno(addon).Yes(); 104 | return true; 105 | } 106 | } 107 | return false; 108 | } 109 | 110 | void EnqueueNavToNPC(Vector3 point) 111 | { 112 | P.TaskManager.Enqueue(Utils.WaitForScreen); 113 | P.TaskManager.Enqueue(() => 114 | { 115 | if(!(Svc.PluginInterface.InstalledPlugins.Any(x => x.InternalName == "vnavmesh" && x.IsLoaded))) 116 | { 117 | PluginLog.Warning($"Navmesh not found, will not continue navigation"); 118 | return null; 119 | } 120 | else 121 | { 122 | return true; 123 | } 124 | }); 125 | P.TaskManager.Enqueue(S.Ipc.VnavmeshIPC.IsReady); 126 | P.TaskManager.Enqueue(() => 127 | { 128 | var task = S.Ipc.VnavmeshIPC.Pathfind(Player.Position, point, false); 129 | P.TaskManager.Enqueue(() => 130 | { 131 | if(!task.IsCompleted) return false; 132 | var path = task.Result; 133 | P.TaskManager.Enqueue(() => TaskMoveToHouse.UseSprint(false)); 134 | P.TaskManager.Enqueue(() => P.FollowPath.Move([.. path], true)); 135 | return true; 136 | }, "Build path"); 137 | }, "Master navmesh task"); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Lifestream/StaticData.json: -------------------------------------------------------------------------------- 1 | { 2 | "CustomPositions": { 3 | "127": { 4 | "X": 42.648926, 5 | "Y": 1.4190674, 6 | "Z": -14.8776245 7 | }, 8 | "129": { 9 | "X": 8.987488, 10 | "Y": 0.8086548, 11 | "Z": -105.85187 12 | }, 13 | "130": { 14 | "X": -61.57019, 15 | "Y": 0.77819824, 16 | "Z": 90.684326 17 | }, 18 | "162": { 19 | "X": 96.269165, 20 | "Y": -3.4332886, 21 | "Z": 81.01013 22 | }, 23 | "216": { 24 | "X": -24.093994, 25 | "Y": 0.77819824, 26 | "Z": 7.583679 27 | }, 28 | "218": { 29 | "X": -413.68738, 30 | "Y": 2.9754639, 31 | "Z": -45.975464 32 | }, 33 | "219": { 34 | "X": -187.1214, 35 | "Y": 39.93274, 36 | "Z": 6.088318 37 | }, 38 | "220": { 39 | "X": -149.73682, 40 | "Y": -15.030151, 41 | "Z": 198.90125 42 | }, 43 | "221": { 44 | "X": -14.999634, 45 | "Y": -10.025269, 46 | "Z": 135.57642 47 | }, 48 | "222": { 49 | "X": -99.13794, 50 | "Y": 100.72473, 51 | "Z": -222.03406 52 | }, 53 | "223": { 54 | "X": 166.27747, 55 | "Y": -17.990417, 56 | "Z": 38.742676 57 | }, 58 | "224": { 59 | "X": 71.7937, 60 | "Y": 47.074097, 61 | "Z": -333.21124 62 | }, 63 | "217": { 64 | "X": -0.015319824, 65 | "Y": 8.987488, 66 | "Z": -0.015319824 67 | }, 68 | "230": { 69 | "X": -30.441833, 70 | "Y": -6.0579224, 71 | "Z": 209.3385 72 | }, 73 | "231": { 74 | "X": 382.6809, 75 | "Y": 59.983154, 76 | "Z": 76.67651 77 | }, 78 | "232": { 79 | "X": 258.28943, 80 | "Y": 50.736206, 81 | "Z": 148.72961 82 | }, 83 | "233": { 84 | "X": 374.77686, 85 | "Y": 60.01367, 86 | "Z": 325.67322 87 | }, 88 | "234": { 89 | "X": -32.059265, 90 | "Y": 38.04065, 91 | "Z": -345.2354 92 | }, 93 | "235": { 94 | "X": -160.05188, 95 | "Y": -0.015319824, 96 | "Z": 21.591492 97 | }, 98 | "236": { 99 | "X": -378.13385, 100 | "Y": 13.992493, 101 | "Z": 136.49194 102 | } 103 | }, 104 | "SortOrder": { 105 | "2": 0, 106 | "25": 1, 107 | "26": 2, 108 | "27": 3, 109 | "28": 4, 110 | "29": 5, 111 | "30": 6, 112 | "31": 7, 113 | "32": 8, 114 | "54": 9, 115 | "94": 10, 116 | "8": 0, 117 | "41": 4, 118 | "42": 5, 119 | "43": 1, 120 | "44": 2, 121 | "45": 7, 122 | "46": 8, 123 | "48": 6, 124 | "49": 3, 125 | "93": 9, 126 | "9": 0, 127 | "33": 1, 128 | "34": 2, 129 | "35": 3, 130 | "36": 4, 131 | "37": 9, 132 | "38": 11, 133 | "39": 12, 134 | "40": 13, 135 | "47": 6, 136 | "50": 7, 137 | "51": 10, 138 | "95": 14, 139 | "125": 8, 140 | "62": 0, 141 | "63": 0, 142 | "64": 0, 143 | "65": 0, 144 | "66": 0, 145 | "67": 0, 146 | "68": 0, 147 | "69": 0, 148 | "89": 0, 149 | "70": 0, 150 | "80": 0, 151 | "81": 0, 152 | "82": 0, 153 | "83": 0, 154 | "84": 0, 155 | "85": 0, 156 | "86": 0, 157 | "87": 0, 158 | "88": 0, 159 | "75": 0, 160 | "90": 0, 161 | "91": 0, 162 | "92": 0, 163 | "104": 0, 164 | "121": 0, 165 | "122": 0, 166 | "123": 0, 167 | "124": 0, 168 | "111": 0, 169 | "112": 0, 170 | "113": 0, 171 | "114": 0, 172 | "115": 0, 173 | "116": 0, 174 | "117": 0, 175 | "118": 0, 176 | "119": 0, 177 | "120": 0, 178 | "126": 0, 179 | "127": 0, 180 | "129": 1, 181 | "130": 2, 182 | "131": 4, 183 | "162": 3, 184 | "163": 5, 185 | "133": 0, 186 | "149": 0, 187 | "150": 0, 188 | "151": 0, 189 | "152": 0, 190 | "153": 0, 191 | "154": 0, 192 | "155": 0, 193 | "156": 0, 194 | "134": 0, 195 | "135": 1, 196 | "157": 4, 197 | "158": 2, 198 | "159": 3, 199 | "160": 5, 200 | "182": 0, 201 | "184": 0, 202 | "185": 0, 203 | "186": 0, 204 | "187": 0, 205 | "188": 0, 206 | "189": 0, 207 | "190": 0, 208 | "183": 0, 209 | "191": 1, 210 | "192": 2, 211 | "193": 3, 212 | "194": 4, 213 | "195": 5, 214 | "196": 6, 215 | "197": 9, 216 | "198": 7, 217 | "199": 8, 218 | "216": 0, 219 | "218": 0, 220 | "219": 0, 221 | "220": 0, 222 | "221": 0, 223 | "222": 0, 224 | "223": 0, 225 | "224": 0, 226 | "225": 0, 227 | "226": 0, 228 | "227": 0, 229 | "228": 0, 230 | "229": 0, 231 | "217": 0, 232 | "230": 0, 233 | "231": 0, 234 | "232": 0, 235 | "233": 0, 236 | "234": 0, 237 | "235": 0, 238 | "236": 0, 239 | "237": 0 240 | } 241 | } -------------------------------------------------------------------------------- /Lifestream/Systems/Legacy/DataStore.cs: -------------------------------------------------------------------------------- 1 | using ECommons.Configuration; 2 | using ECommons.Events; 3 | using ECommons.ExcelServices; 4 | using ECommons.GameHelpers; 5 | using Lifestream.Data; 6 | using Lifestream.Tasks.Shortcuts; 7 | using Lumina.Excel.Sheets; 8 | using static ECommons.Singletons.SingletonServiceManager; 9 | using Map = Lumina.Excel.Sheets.Map; 10 | using Path = System.IO.Path; 11 | 12 | namespace Lifestream.Systems.Legacy; 13 | 14 | public class DataStore 15 | { 16 | internal string FileName = "StaticData.json"; 17 | internal uint[] Territories; 18 | internal Dictionary> Aetherytes = []; 19 | internal string[] Worlds = Array.Empty(); 20 | internal string[] DCWorlds = Array.Empty(); 21 | internal Dictionary IslandNPCs = []; 22 | internal StaticData StaticData; 23 | 24 | internal TinyAetheryte GetMaster(TinyAetheryte aetheryte) 25 | { 26 | foreach(var x in Aetherytes.Keys) 27 | { 28 | if(x.Group == aetheryte.Group) return x; 29 | } 30 | return default; 31 | } 32 | 33 | public DataStore() 34 | { 35 | var terr = new List(); 36 | StaticData = EzConfig.LoadConfiguration(Path.Combine(Svc.PluginInterface.AssemblyLocation.DirectoryName, FileName), false); 37 | Svc.Data.GetExcelSheet().Each(x => 38 | { 39 | if(x.AethernetGroup != 0) 40 | { 41 | if(x.IsAetheryte) 42 | { 43 | Aetherytes[GetTinyAetheryte(x)] = []; 44 | terr.Add(x.Territory.Value.RowId); 45 | } 46 | } 47 | }); 48 | Svc.Data.GetExcelSheet().Each(x => 49 | { 50 | if(x.AethernetGroup != 0) 51 | { 52 | if(!x.IsAetheryte) 53 | { 54 | var a = GetTinyAetheryte(x); 55 | Aetherytes[GetMaster(a)].Add(a); 56 | terr.Add(x.Territory.Value.RowId); 57 | } 58 | } 59 | }); 60 | foreach(var x in Aetherytes.Keys.ToArray()) 61 | { 62 | Aetherytes[x] = [.. Aetherytes[x].OrderBy(x => GetAetheryteSortOrder(x.ID))]; 63 | } 64 | Territories = [.. terr]; 65 | if(ProperOnLogin.PlayerPresent) 66 | { 67 | BuildWorlds(); 68 | } 69 | 70 | foreach(TaskISShortcut.IslandNPC npc in Enum.GetValues(typeof(TaskISShortcut.IslandNPC))) 71 | { 72 | if(Svc.Data.GetExcelSheet().TryGetRow((uint)npc, out var row)) 73 | { 74 | IslandNPCs.Add(npc, [row.Singular.ToString(), row.Title.ToString()]); 75 | } 76 | } 77 | 78 | ProperOnLogin.RegisterAvailable(BuildWorlds); 79 | } 80 | 81 | internal uint GetAetheryteSortOrder(uint id) 82 | { 83 | var ret = 10000u; 84 | if(StaticData.SortOrder.TryGetValue(id, out var x)) 85 | { 86 | ret += x; 87 | } 88 | if(C.Favorites.Contains(id)) 89 | { 90 | ret -= 10000u; 91 | } 92 | return ret; 93 | } 94 | 95 | internal void BuildWorlds() 96 | { 97 | BuildWorlds(Svc.ClientState.LocalPlayer.CurrentWorld.Value.DataCenter.Value.RowId); 98 | if(Player.Available) 99 | { 100 | if(P.AutoRetainerApi?.Ready == true && C.UseAutoRetainerAccounts) 101 | { 102 | var data = P.AutoRetainerApi.GetOfflineCharacterData(Player.CID); 103 | if(data != null) 104 | { 105 | C.ServiceAccounts[Player.NameWithWorld] = data.ServiceAccount; 106 | } 107 | } 108 | else if(!C.ServiceAccounts.ContainsKey(Player.NameWithWorld)) 109 | { 110 | C.ServiceAccounts[Player.NameWithWorld] = -1; 111 | } 112 | } 113 | } 114 | 115 | internal void BuildWorlds(uint dc) 116 | { 117 | Worlds = [.. Svc.Data.GetExcelSheet().Where(x => x.DataCenter.Value.RowId == dc && x.IsPublic()).Select(x => x.Name.ToString()).Order()]; 118 | PluginLog.Debug($"Built worlds: {Worlds.Print()}"); 119 | DCWorlds = Svc.Data.GetExcelSheet().Where(x => x.DataCenter.Value.RowId != dc && x.IsPublic() && (x.DataCenter.Value.Region == Player.Object.HomeWorld.Value.DataCenter.Value.Region || x.DataCenter.Value.Region == 4)).Select(x => x.Name.ToString()).ToArray(); 120 | PluginLog.Debug($"Built DCworlds: {DCWorlds.Print()}"); 121 | } 122 | 123 | internal TinyAetheryte GetTinyAetheryte(Aetheryte aetheryte) 124 | { 125 | var AethersX = 0f; 126 | var AethersY = 0f; 127 | if(StaticData.CustomPositions.TryGetValue(aetheryte.RowId, out var pos)) 128 | { 129 | AethersX = pos.X; 130 | AethersY = pos.Z; 131 | } 132 | else 133 | { 134 | var map = Svc.Data.GetExcelSheet().FirstOrNull(m => m.TerritoryType.RowId == aetheryte.Territory.Value.RowId); 135 | if(map == null) 136 | { 137 | PluginLog.Error($"Error, map is null for {aetheryte.Territory.Value.RowId}"); 138 | } 139 | var scale = map?.SizeFactor ?? 1; 140 | if(Svc.Data.GetSubrowExcelSheet().AllRows().TryGetFirst(m => m.DataType == (aetheryte.IsAetheryte ? 3 : 4) && m.DataKey.RowId == (aetheryte.IsAetheryte ? aetheryte.RowId : aetheryte.AethernetName.RowId), out var mapMarker)) 141 | { 142 | AethersX = Utils.ConvertMapMarkerToRawPosition(mapMarker.X, scale); 143 | AethersY = Utils.ConvertMapMarkerToRawPosition(mapMarker.Y, scale); 144 | } 145 | } 146 | return new(new(AethersX, AethersY), aetheryte.Territory.Value.RowId, aetheryte.RowId, aetheryte.AethernetGroup); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Lifestream/Lifestream.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NightmareXIV 5 | 2.5.3.11 6 | 7 | 8 | 9 | net10.0-windows7.0 10 | x64 11 | preview 12 | true 13 | false 14 | true 15 | false 16 | bin\$(Configuration)\ 17 | CS1591;IDE0052 18 | true 19 | true 20 | false 21 | 22 | 23 | 24 | 25 | true 26 | 27 | 28 | 29 | $(appdata)\XIVLauncher\addon\Hooks\dev\ 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | $(DalamudLibPath)FFXIVClientStructs.dll 49 | False 50 | 51 | 52 | $(DalamudLibPath)InteropGenerator.Runtime.dll 53 | False 54 | 55 | 56 | 57 | 58 | CUSTOMCS 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | $(DalamudLibPath)Newtonsoft.Json.dll 70 | False 71 | 72 | 73 | $(DalamudLibPath)Dalamud.dll 74 | False 75 | 76 | 77 | $(DalamudLibPath)Dalamud.Bindings.ImGui.dll 78 | False 79 | 80 | 81 | $(DalamudLibPath)Dalamud.Bindings.ImPlot.dll 82 | False 83 | 84 | 85 | $(DalamudLibPath)Dalamud.Bindings.ImGuizmo.dll 86 | False 87 | 88 | 89 | $(DalamudLibPath)TerraFX.Interop.Windows.dll 90 | False 91 | 92 | 93 | $(DalamudLibPath)Lumina.dll 94 | False 95 | 96 | 97 | $(DalamudLibPath)TerraFX.Interop.Windows.dll 98 | False 99 | 100 | 101 | $(DalamudLibPath)Lumina.Excel.dll 102 | False 103 | 104 | 105 | $(DalamudLibPath)Dalamud.Common.dll 106 | False 107 | 108 | 109 | $(DalamudLibPath)ImGuiScene.dll 110 | False 111 | 112 | 113 | $(DalamudLibPath)SharpDX.dll 114 | False 115 | 116 | 117 | 118 | 119 | 120 | Always 121 | 122 | 123 | Always 124 | 125 | 126 | Always 127 | 128 | 129 | PreserveNewest 130 | 131 | 132 | -------------------------------------------------------------------------------- /Lifestream/IPC/Wotsit/WotsitManager.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Plugin.Ipc; 2 | using ECommons.Configuration; 3 | using ECommons.Events; 4 | using ECommons.GameHelpers; 5 | 6 | namespace Lifestream.IPC; 7 | 8 | public class WotsitManager : IDisposable 9 | { 10 | // Map from registered Guid string to the entry. 11 | private readonly Dictionary _registered = []; 12 | private HashSet _lastEntries = []; 13 | 14 | private readonly ICallGateSubscriber _faAvailable; 15 | private readonly ICallGateSubscriber _faInvoke; 16 | private ICallGateSubscriber? _faRegisterWithSearch; 17 | 18 | private WotsitManager() 19 | { 20 | _faAvailable = Svc.PluginInterface.GetIpcSubscriber("FA.Available"); 21 | _faAvailable.Subscribe(OnAvailable); 22 | _faInvoke = Svc.PluginInterface.GetIpcSubscriber("FA.Invoke"); 23 | _faInvoke.Subscribe(HandleInvoke); 24 | // To handle (re)logins (and plugin reloads). 25 | ProperOnLogin.RegisterAvailable(OnLogin, true); 26 | // To handle logouts and clear all entries. 27 | Svc.ClientState.Logout += OnLogout; 28 | // To catch new config options, address book entries, aliases, etc. 29 | EzConfig.OnSave += ConfigSaved; 30 | // To periodically reload generated entries. 31 | Svc.ClientState.TerritoryChanged += TerritoryChanged; 32 | } 33 | 34 | public void Dispose() 35 | { 36 | ClearWotsit(); 37 | _faAvailable?.Unsubscribe(OnAvailable); 38 | _faInvoke?.Unsubscribe(HandleInvoke); 39 | ProperOnLogin.Unregister(OnLogin); 40 | Svc.ClientState.Logout -= OnLogout; 41 | EzConfig.OnSave -= ConfigSaved; 42 | Svc.ClientState.TerritoryChanged -= TerritoryChanged; 43 | GC.SuppressFinalize(this); 44 | } 45 | 46 | private void HandleInvoke(string id) 47 | { 48 | if(!_registered.TryGetValue(id, out var entry)) 49 | { 50 | return; 51 | } 52 | 53 | PluginLog.Debug($"WotsitManager: Received FA.Invoke(\"{id}\") => {entry.DisplayName}"); 54 | try 55 | { 56 | entry.Callback.DynamicInvoke(); 57 | } 58 | catch(Exception e) 59 | { 60 | DuoLog.Error($"WotsitManager: Could not handle FA.Invoke(\"{id}\") ({entry.DisplayName}): {e}"); 61 | } 62 | } 63 | 64 | public void TryClearWotsit() 65 | { 66 | try 67 | { 68 | ClearWotsit(); 69 | } 70 | catch(Exception e) 71 | { 72 | PluginLog.Warning($"WotsitManager: Failed to clear wotsit: {e}"); 73 | } 74 | } 75 | 76 | private void ClearWotsit() 77 | { 78 | _lastEntries = []; 79 | if(Utils.WotsitInstalled()) 80 | { 81 | var faUnregisterAll = Svc.PluginInterface.GetIpcSubscriber("FA.UnregisterAll"); 82 | faUnregisterAll!.InvokeFunc(P.Name); 83 | PluginLog.Debug($"WotsitManager: Invoked FA.UnregisterAll(\"{P.Name}\")"); 84 | } 85 | _registered.Clear(); 86 | } 87 | 88 | private void OnAvailable() 89 | { 90 | PluginLog.Debug("WotsitManager: FA.Available triggered, forcing re-registration"); 91 | MaybeTryInit(true); 92 | } 93 | 94 | private void OnLogin() 95 | { 96 | PluginLog.Debug("WotsitManager: ProperOnLogin.Available triggered, forcing re-registration"); 97 | MaybeTryInit(true); 98 | } 99 | 100 | private void OnLogout(int type, int code) 101 | { 102 | PluginLog.Debug("WotsitManager: ClientState.Logout triggered, clearing wotsit"); 103 | TryClearWotsit(); 104 | } 105 | 106 | private void ConfigSaved() 107 | { 108 | if(!Player.Available) 109 | { 110 | return; 111 | } 112 | PluginLog.Debug("WotsitManager: Config saved, attempting re-registration"); 113 | MaybeTryInit(false); 114 | } 115 | 116 | private void TerritoryChanged(ushort territory) 117 | { 118 | PluginLog.Debug($"WotsitManager: Territory changed to {territory}, attempting re-registration"); 119 | MaybeTryInit(false); 120 | } 121 | 122 | public void MaybeTryInit(bool force = false) 123 | { 124 | if(!C.WotsitIntegrationEnabled || !Utils.WotsitInstalled()) 125 | { 126 | return; 127 | } 128 | 129 | try 130 | { 131 | Init(force); 132 | } 133 | catch(Exception e) 134 | { 135 | PluginLog.Verbose($"WotsitManager: Failed to initialize: {e.ToStringFull()}"); 136 | } 137 | } 138 | 139 | private void Init(bool force = false) 140 | { 141 | var newEntries = WotsitEntryGenerator.Generate().ToHashSet(); 142 | if(!force && _lastEntries.Count != 0 && newEntries.SetEquals(_lastEntries)) 143 | { 144 | PluginLog.Debug("WotsitManager: Entries have not changed, skipping re-registration"); 145 | return; 146 | } 147 | #if DEBUG 148 | // Log the actual differences for debugging. 149 | if (_lastEntries.Count > 0) 150 | { 151 | foreach (var added in newEntries.Except(_lastEntries).ToArray()) 152 | { 153 | PluginLog.Debug($"WotsitManager: New entry: {added}"); 154 | } 155 | foreach (var removed in _lastEntries.Except(newEntries).ToArray()) 156 | { 157 | PluginLog.Debug($"WotsitManager: Removed entry: {removed}"); 158 | } 159 | } 160 | #endif 161 | ClearWotsit(); 162 | _lastEntries = newEntries; 163 | 164 | _faRegisterWithSearch = Svc.PluginInterface.GetIpcSubscriber("FA.RegisterWithSearch"); 165 | 166 | foreach(var entry in newEntries) 167 | { 168 | var id = _faRegisterWithSearch!.InvokeFunc(P.Name, entry.DisplayName, $"{P.Name} {entry.SearchString}", entry.IconId); 169 | _registered.Add(id, entry); 170 | PluginLog.Debug($"WotsitManager: Invoked FA.RegisterWithSearch(\"{P.Name}\", \"{entry.DisplayName}\", \"{entry.SearchString}\", {entry.IconId}) => {id}"); 171 | } 172 | } 173 | } 174 | --------------------------------------------------------------------------------