From 90bf84f8eb0cab3692f771f97f3d97575511b737 Mon Sep 17 00:00:00 2001 From: defnotken Date: Mon, 19 Jan 2026 10:58:37 -0600 Subject: [PATCH] Implement Lifestream With Location Sharing. --- .../Enums/TerritoryTypeIdHousing.cs | 51 +++++++ LightlessSync/Services/DalamudUtilService.cs | 16 ++ .../Services/LocationShareService.cs | 139 ++++++++++++++++++ LightlessSync/UI/Components/DrawUserPair.cs | 72 ++++++++- LightlessSync/UI/DrawEntityFactory.cs | 9 +- 5 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 LightlessSync/Interop/InteropModel/Enums/TerritoryTypeIdHousing.cs diff --git a/LightlessSync/Interop/InteropModel/Enums/TerritoryTypeIdHousing.cs b/LightlessSync/Interop/InteropModel/Enums/TerritoryTypeIdHousing.cs new file mode 100644 index 0000000..699f6b6 --- /dev/null +++ b/LightlessSync/Interop/InteropModel/Enums/TerritoryTypeIdHousing.cs @@ -0,0 +1,51 @@ +namespace Lifestream.Enums; + +public enum TerritoryTypeIdHousing +{ + None = -1, + + // Mist (Limsa Lominsa) + Mist = 339, + MistSmall = 282, + MistMedium = 283, + MistLarge = 284, + MistFCRoom = 384, + MistFCWorkshop = 423, + MistApartment = 608, + + // Lavender Beds (Gridania) + Lavender = 340, + LavenderSmall = 342, + LavenderMedium = 343, + LavenderLarge = 344, + LavenderFCRoom = 385, + LavenderFCWorkshop = 425, + LavenderApartment = 609, + + // Goblet (Ul'dah) + Goblet = 341, + GobletSmall = 345, + GobletMedium = 346, + GobletLarge = 347, + GobletFCRoom = 386, + GobletFCWorkshop = 424, + GobletApartment = 610, + + // Shirogane (Kugane) + Shirogane = 641, + ShiroganeSmall = 649, + ShiroganeMedium = 650, + ShiroganeLarge = 651, + ShiroganeFCRoom = 652, + ShiroganeFCWorkshop = 653, + ShiroganeApartment = 655, + + // Empyreum (Ishgard) + Empyream = 979, + EmpyreamSmall = 980, + EmpyreamMedium = 981, + EmpyreamLarge = 982, + EmpyreamFCRoom = 983, + EmpyreamFCWorkshop = 984, + EmpyreamApartment = 999, +} \ No newline at end of file diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index d7a814a..500db53 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -701,7 +701,23 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber str += $" Room #{location.RoomId}"; } } + return str; + } + public string LocationToLifestream(LocationInfo location) + { + if (location.ServerId is 0 || location.TerritoryId is 0 || ContentFinderData.Value.ContainsKey(location.TerritoryId)) return String.Empty; + var str = WorldData.Value[(ushort)location.ServerId]; + if (location.HouseId is 0 && location.MapId is not 0) + { + var mapName = MapData.Value[(ushort)location.MapId].MapName; + var parts = mapName.Split(" - ", StringSplitOptions.RemoveEmptyEntries); + var locationName = parts.Length > 0 ? parts[^1] : mapName; + str += $", tp {locationName}"; + string message = $"LocationToLifestream: {str}"; + _logger.LogInformation(message); + + } return str; } diff --git a/LightlessSync/Services/LocationShareService.cs b/LightlessSync/Services/LocationShareService.cs index 38b2834..4a8249c 100644 --- a/LightlessSync/Services/LocationShareService.cs +++ b/LightlessSync/Services/LocationShareService.cs @@ -1,3 +1,4 @@ +using Lifestream.Enums; using LightlessSync.API.Data; using LightlessSync.API.Dto.CharaData; using LightlessSync.API.Dto.User; @@ -108,6 +109,144 @@ namespace LightlessSync.Services } } + public LocationInfo? GetLocationForLifestreamByUid(string uid) + { + try + { + if (_locations.TryGetValue(uid, out var location)) + { + return location; + } + return null; + } + catch (Exception e) + { + Logger.LogError(e,"GetLocationInfoByUid error : "); + throw; + } + } + + public AddressBookEntryTuple? GetAddressBookEntryByLocation(LocationInfo location) + { + if (location.ServerId is 0 || location.TerritoryId is 0) + { + return null; + } + + var territoryHousing = (TerritoryTypeIdHousing)location.TerritoryId; + + if (territoryHousing == TerritoryTypeIdHousing.None || !Enum.IsDefined(typeof(TerritoryTypeIdHousing), territoryHousing)) + { + return null; + } + + var city = GetResidentialAetheryteKind(territoryHousing); + + if (city == ResidentialAetheryteKind.None) + { + return null; + } + + if (location.HouseId is not 0 and not 100) + { + AddressBookEntryTuple addressEntry = ( + Name: "", + World: (int)location.ServerId, + City: (int)city, + Ward: (int)location.WardId, + PropertyType: 0, + Plot: (int)location.HouseId, + Apartment: 0, + ApartmentSubdivision: location.DivisionId == 2, + AliasEnabled: false, + Alias: "" + ); + return addressEntry; + } + else if (location.HouseId is 100) + { + AddressBookEntryTuple addressEntry = ( + Name: "", + World: (int)location.ServerId, + City: (int)city, + Ward: (int)location.WardId, + PropertyType: 1, + Plot: 0, + Apartment: (int)location.RoomId, + ApartmentSubdivision: location.DivisionId == 2, + AliasEnabled: false, + Alias: "" + ); + return addressEntry; + } + + return null; + } + + private ResidentialAetheryteKind GetResidentialAetheryteKind(TerritoryTypeIdHousing territoryHousing) + { + return territoryHousing switch + { + TerritoryTypeIdHousing.Shirogane or + TerritoryTypeIdHousing.ShiroganeApartment or + TerritoryTypeIdHousing.ShiroganeSmall or + TerritoryTypeIdHousing.ShiroganeMedium or + TerritoryTypeIdHousing.ShiroganeLarge or + TerritoryTypeIdHousing.ShiroganeFCRoom or + TerritoryTypeIdHousing.ShiroganeFCWorkshop + => ResidentialAetheryteKind.Kugane, + + TerritoryTypeIdHousing.Lavender or + TerritoryTypeIdHousing.LavenderSmall or + TerritoryTypeIdHousing.LavenderMedium or + TerritoryTypeIdHousing.LavenderLarge or + TerritoryTypeIdHousing.LavenderApartment or + TerritoryTypeIdHousing.LavenderFCRoom or + TerritoryTypeIdHousing.LavenderFCWorkshop + => ResidentialAetheryteKind.Gridania, + + TerritoryTypeIdHousing.Mist or + TerritoryTypeIdHousing.MistSmall or + TerritoryTypeIdHousing.MistMedium or + TerritoryTypeIdHousing.MistLarge or + TerritoryTypeIdHousing.MistApartment or + TerritoryTypeIdHousing.MistFCRoom or + TerritoryTypeIdHousing.MistFCWorkshop + => ResidentialAetheryteKind.Limsa, + + TerritoryTypeIdHousing.Goblet or + TerritoryTypeIdHousing.GobletSmall or + TerritoryTypeIdHousing.GobletMedium or + TerritoryTypeIdHousing.GobletLarge or + TerritoryTypeIdHousing.GobletApartment or + TerritoryTypeIdHousing.GobletFCRoom or + TerritoryTypeIdHousing.GobletFCWorkshop + => ResidentialAetheryteKind.Uldah, + + TerritoryTypeIdHousing.Empyream or + TerritoryTypeIdHousing.EmpyreamSmall or + TerritoryTypeIdHousing.EmpyreamMedium or + TerritoryTypeIdHousing.EmpyreamLarge or + TerritoryTypeIdHousing.EmpyreamApartment or + TerritoryTypeIdHousing.EmpyreamFCRoom or + TerritoryTypeIdHousing.EmpyreamFCWorkshop + => ResidentialAetheryteKind.Foundation, + + _ => ResidentialAetheryteKind.None + }; + } + + public string? GetMapAddressByLocation(LocationInfo location) + { + string? liString = null; + var territoryHousing = (TerritoryTypeIdHousing)location.TerritoryId; + if (GetResidentialAetheryteKind(territoryHousing) == ResidentialAetheryteKind.None) + { + liString = _dalamudUtilService.LocationToLifestream(location); + } + return liString; + } + public DateTimeOffset GetSharingStatus(string uid) { try diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index 3ee10ad..09a2517 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -4,8 +4,10 @@ using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; +using LightlessSync.API.Dto.CharaData; using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.User; +using LightlessSync.Interop.Ipc; using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; @@ -40,6 +42,7 @@ public class DrawUserPair private readonly LocationShareService _locationShareService; private readonly CharaDataManager _charaDataManager; private readonly PairLedger _pairLedger; + private readonly IpcCallerLifestream _lifestreamIpc; private float _menuWidth = -1; private bool _wasHovered = false; private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty; @@ -60,7 +63,8 @@ public class DrawUserPair LightlessConfigService configService, LocationShareService locationShareService, CharaDataManager charaDataManager, - PairLedger pairLedger) + PairLedger pairLedger, + IpcCallerLifestream lifestreamIpc) { _id = id; _uiEntry = uiEntry; @@ -79,6 +83,7 @@ public class DrawUserPair _locationShareService = locationShareService; _charaDataManager = charaDataManager; _pairLedger = pairLedger; + _lifestreamIpc = lifestreamIpc; } public PairDisplayEntry DisplayEntry => _displayEntry; @@ -656,6 +661,13 @@ public class DrawUserPair using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther)) _uiSharedService.IconText(shareLocationIcon); + var popupId = $"LocationPopup_{_pair.UserData.UID}"; + + if (ImGui.IsItemClicked(ImGuiMouseButton.Left) && shareLocation && !string.IsNullOrEmpty(location)) + { + ImGui.OpenPopup(popupId); + } + if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); @@ -669,6 +681,8 @@ public class DrawUserPair _uiSharedService.IconText(FontAwesomeIcon.LocationArrow); ImGui.SameLine(); ImGui.TextUnformatted(location); + ImGui.Separator(); + ImGui.TextUnformatted("Click to teleport to this location"); } else { @@ -700,6 +714,62 @@ public class DrawUserPair } ImGui.EndTooltip(); } + + if (ImGui.BeginPopup(popupId)) + { + + var locationInfo = _locationShareService.GetLocationForLifestreamByUid(_pair.UserData.UID); + if (locationInfo != null) + { + var locationLi = locationInfo.Value; + var housingAddress = _locationShareService.GetAddressBookEntryByLocation(locationLi); + var mapAddress = _locationShareService.GetMapAddressByLocation(locationLi); + ImGui.TextUnformatted("Teleport to user?"); + ImGui.Separator(); + if (!_lifestreamIpc.APIAvailable) + { + ImGui.TextUnformatted("Lifestream IPC is not available. Please ensure Lifestream is enabled"); + } + else if (housingAddress != null || mapAddress != null) + { + ImGui.TextUnformatted($"Go to {location}?"); + ImGui.TextUnformatted($"NOTE: Teleporting to maps with multiple aetherytes or instances may not be accurate currently. (ie. Thavnair, Yanxia)"); + } + else + { + ImGui.TextUnformatted("Lifestream cannot teleport here. If you are in a residential area, please make sure you're inside a plot."); + } + ImGui.Separator(); + if (_lifestreamIpc.APIAvailable && (housingAddress != null || mapAddress != null)) + { + if (locationLi.HouseId is not 0 && housingAddress != null) + { + if (ImGui.Button("Navigate")) + { + _lifestreamIpc.GoToHousingAddress(housingAddress.Value); + + ImGui.CloseCurrentPopup(); + } + } + else if (mapAddress != null && locationLi.HouseId is 0) + { + if (ImGui.Button("Navigate")) + { + _lifestreamIpc.ExecuteLifestreamCommand(mapAddress); + + ImGui.CloseCurrentPopup(); + } + } + + ImGui.SameLine(); + } + if (ImGui.Button("Close")) + { + ImGui.CloseCurrentPopup(); + } + ImGui.EndPopup(); + } + } } if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky) diff --git a/LightlessSync/UI/DrawEntityFactory.cs b/LightlessSync/UI/DrawEntityFactory.cs index 08f81b6..b9613a5 100644 --- a/LightlessSync/UI/DrawEntityFactory.cs +++ b/LightlessSync/UI/DrawEntityFactory.cs @@ -16,6 +16,7 @@ using LightlessSync.UI.Handlers; using LightlessSync.UI.Models; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; +using LightlessSync.Interop.Ipc; namespace LightlessSync.UI; @@ -40,6 +41,7 @@ public class DrawEntityFactory private readonly IdDisplayHandler _uidDisplayHandler; private readonly PairLedger _pairLedger; private readonly PairFactory _pairFactory; + private readonly IpcCallerLifestream _lifestreamIpc; public DrawEntityFactory( ILogger logger, @@ -60,7 +62,8 @@ public class DrawEntityFactory RenameSyncshellTagUi renameSyncshellTagUi, SelectSyncshellForTagUi selectSyncshellForTagUi, PairLedger pairLedger, - PairFactory pairFactory) + PairFactory pairFactory, + IpcCallerLifestream lifestreamIpc) { _logger = logger; _apiController = apiController; @@ -81,6 +84,7 @@ public class DrawEntityFactory _selectSyncshellForTagUi = selectSyncshellForTagUi; _pairLedger = pairLedger; _pairFactory = pairFactory; + _lifestreamIpc = lifestreamIpc; } public DrawFolderGroup CreateGroupFolder( @@ -167,7 +171,8 @@ public class DrawEntityFactory _configService, _locationShareService, _charaDataManager, - _pairLedger); + _pairLedger, + _lifestreamIpc); } public IReadOnlyList GetAllEntries()