From 76520878bfbac8d112058053e5786eadfb0f5f3e Mon Sep 17 00:00:00 2001 From: azyges Date: Wed, 1 Oct 2025 03:29:56 +0900 Subject: [PATCH 1/9] bump submodule --- LightlessAPI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LightlessAPI b/LightlessAPI index 5bfd21a..69f0e31 160000 --- a/LightlessAPI +++ b/LightlessAPI @@ -1 +1 @@ -Subproject commit 5bfd21aaa90817f14c9e2931e77b20f4276f16ed +Subproject commit 69f0e310bd78e0c56eab298199e6e2ca15bf56bd From dea4ef4832d8d591c8b4cedb27e7ea5fe76400e6 Mon Sep 17 00:00:00 2001 From: azyges Date: Wed, 1 Oct 2025 08:59:08 +0900 Subject: [PATCH 2/9] rich text, updated lightfinder description and bug fixes --- LightlessSync/UI/BroadcastUI.cs | 68 ++++++++++++++++++----- LightlessSync/UI/CompactUI.cs | 23 +++++--- LightlessSync/UI/EditProfileUi.cs | 31 ++++------- LightlessSync/UI/UISharedService.cs | 49 ++++++++++++++++- LightlessSync/Utils/SeStringUtils.cs | 82 +++++++++++++++++++++++++--- 5 files changed, 205 insertions(+), 48 deletions(-) diff --git a/LightlessSync/UI/BroadcastUI.cs b/LightlessSync/UI/BroadcastUI.cs index eda5a53..dc0d740 100644 --- a/LightlessSync/UI/BroadcastUI.cs +++ b/LightlessSync/UI/BroadcastUI.cs @@ -1,10 +1,12 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; +using Dalamud.Interface.Colors; using Dalamud.Interface.Utility; using Dalamud.Utility; using LightlessSync.API.Dto.Group; using LightlessSync.LightlessConfiguration; using LightlessSync.Services; using LightlessSync.Services.Mediator; +using LightlessSync.Utils; using LightlessSync.WebAPI; using Microsoft.Extensions.Logging; using System.Numerics; @@ -44,8 +46,8 @@ namespace LightlessSync.UI IsOpen = false; this.SizeConstraints = new() { - MinimumSize = new(600, 340), - MaximumSize = new(750, 400) + MinimumSize = new(600, 450), + MaximumSize = new(750, 510) }; mediator.Subscribe(this, async _ => await RefreshSyncshells().ConfigureAwait(false)); @@ -137,19 +139,59 @@ namespace LightlessSync.UI { _uiSharedService.MediumText("Lightfinder", UIColors.Get("PairBlue")); - ImGui.PushTextWrapPos(); - ImGui.Text("This lets other Lightless users know you use Lightless."); - ImGui.Text("By enabling this, the server will allow other people to see that you are using Lightless."); - ImGui.Text("When disabled, pairing is still possible but both parties need to mutually send each other requests, receiving party will not be notified about the request unless the pairing is complete."); - ImGui.Text("At no point ever, even when Lightfinder is active that any Lightless data is getting sent to other people (including ID's), the server keeps this to itself."); - ImGui.Text("You can request to pair by right-clicking any (not yourself) character and using 'Send Pair Request'."); - ImGui.PopTextWrapPos(); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, -2)); + + _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "This lets other Lightless users know you use Lightless."); + _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "While enabled, you and other people using Lightfinder can see each other identified as Lightless users."); + ImGui.Indent(5f); + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); + ImGui.Text("- This is done using a 'Lightless' label above player nameplates."); + ImGui.PopStyleColor(); + ImGui.Unindent(5f); + + ImGuiHelpers.ScaledDummy(3f); + + _uiSharedService.MediumText("Pairing", UIColors.Get("PairBlue")); + _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Pairing may be initiated via the right-click context menu on another player." + + " The process requires mutual confirmation: the sender initiates the request, and the recipient completes it by responding with a request in return."); + + _uiSharedService.DrawNoteLine( + "! ", + UIColors.Get("LightlessYellow"), + new SeStringUtils.RichTextEntry("If Lightfinder is "), + new SeStringUtils.RichTextEntry("ENABLED", UIColors.Get("LightlessGreen"), true), + new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will get notified about it.")); + + _uiSharedService.DrawNoteLine( + "! ", + UIColors.Get("LightlessYellow"), + new SeStringUtils.RichTextEntry("If Lightfinder is "), + new SeStringUtils.RichTextEntry("DISABLED", UIColors.Get("DimRed"), true), + new SeStringUtils.RichTextEntry(" when a pair request is made, the receiving user will "), + new SeStringUtils.RichTextEntry("NOT", UIColors.Get("DimRed"), true), + new SeStringUtils.RichTextEntry(" get a notification, and the request will not be visible to them in any way.")); + + ImGuiHelpers.ScaledDummy(3f); + + _uiSharedService.MediumText("Privacy", UIColors.Get("PairBlue")); + + _uiSharedService.DrawNoteLine( + "! ", + UIColors.Get("DimRed"), + new SeStringUtils.RichTextEntry("Lightfinder is entirely "), + new SeStringUtils.RichTextEntry("opt-in", UIColors.Get("LightlessYellow"), true), + new SeStringUtils.RichTextEntry(" and does not share any data with other users. All identifying information remains private to the server.")); + + _uiSharedService.DrawNoteLine("! ", UIColors.Get("DimRed"), "Pairing is intended as a mutual agreement between both parties. A pair request will not be visible to the recipient unless Lightfinder is enabled."); + ImGuiHelpers.ScaledDummy(3f); ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed")); - ImGui.Text("Use it only when you want to be visible."); + ImGui.Text("Use Lightfinder only when you want to be visible."); ImGui.PopStyleColor(); - ImGuiHelpers.ScaledDummy(0.2f); + ImGui.PopStyleVar(); + + ImGuiHelpers.ScaledDummy(2.2f); _uiSharedService.ColoredSeparator(UIColors.Get("LightlessPurple"), 2f); if (_configService.Current.BroadcastEnabled) @@ -168,7 +210,7 @@ namespace LightlessSync.UI else { ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed")); - ImGui.Text("The Lightfinder’s light wanes, but not in vain."); // cringe.. + ImGui.Text("The Lightfinder’s light wanes, but not in vain."); // cringe.. ImGui.PopStyleColor(); } } diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index 5390c2f..170af20 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -109,6 +109,21 @@ public class CompactUi : WindowMediatorSubscriberBase AllowClickthrough = false; TitleBarButtons = new() { + new TitleBarButton() + { + Icon = FontAwesomeIcon.Cog, + Click = (msg) => + { + Mediator.Publish(new UiToggleMessage(typeof(SettingsUi))); + }, + IconOffset = new(2,1), + ShowTooltip = () => + { + ImGui.BeginTooltip(); + ImGui.Text("Open Lightless Settings"); + ImGui.EndTooltip(); + } + }, new TitleBarButton() { Icon = FontAwesomeIcon.Book, @@ -431,7 +446,7 @@ public class CompactUi : WindowMediatorSubscriberBase float uidStartX = (contentWidth - uidTextSize.X) / 2f; float cursorY = ImGui.GetCursorPosY(); - if (_configService.Current.BroadcastEnabled) + if (_configService.Current.BroadcastEnabled && _apiController.IsConnected) { float iconYOffset = (uidTextSize.Y - iconSize.Y) * 0.5f; var buttonSize = new Vector2(iconSize.X, uidTextSize.Y); @@ -452,12 +467,6 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.Text("Lightfinder"); ImGui.PopStyleColor(); - ImGui.Text("This lets other Lightless users know you use Lightless."); - ImGui.Text("By enabling this, the server will allow other people to see that you are using Lightless."); - ImGui.Text("When disabled, pairing is still possible but both parties need to mutually send each other requests, receiving party will not be notified about the request unless the pairing is complete."); - ImGui.Text("At no point ever, even when Lightfinder is active that any Lightless data is getting sent to other people (including ID's), the server keeps this to itself."); - ImGui.Text("You can request to pair by right-clicking any (not yourself) character and using 'Send Pair Request'."); - ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("DimRed")); ImGui.Text("Use it only when you want to be visible."); ImGui.PopStyleColor(); diff --git a/LightlessSync/UI/EditProfileUi.cs b/LightlessSync/UI/EditProfileUi.cs index 6c787b3..50c4dbe 100644 --- a/LightlessSync/UI/EditProfileUi.cs +++ b/LightlessSync/UI/EditProfileUi.cs @@ -75,15 +75,6 @@ public class EditProfileUi : WindowMediatorSubscriberBase }); } - void DrawNoteLine(string icon, Vector4 color, string text) - { - _uiSharedService.MediumText(icon, color); - ImGui.SameLine(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); - ImGui.TextWrapped(text); - } - private void LoadVanity() { textEnabled = !string.IsNullOrEmpty(_apiController.TextColorHex); @@ -101,15 +92,15 @@ public class EditProfileUi : WindowMediatorSubscriberBase _uiSharedService.UnderlinedBigText("Notes and Rules for Profiles", UIColors.Get("LightlessYellow")); ImGui.Dummy(new Vector2(5)); - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2, 2)); + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(1, 1)); - DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description."); - DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules."); - DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)"); - DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive"); - DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely."); - DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent."); - DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings."); + _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessBlue"), "All users that are paired and unpaused with you will be able to see your profile picture and description."); + _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Other users have the possibility to report your profile for breaking the rules."); + _uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Anything as profile image that can be considered highly illegal or obscene (bestiality, anything that could be considered a sexual act with a minor (that includes Lalafells), etc.)"); + _uiSharedService.DrawNoteLine("!!! ", UIColors.Get("DimRed"), "AVOID: Slurs of any kind in the description that can be considered highly offensive"); + _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "In case of valid reports from other users this can lead to disabling your profile forever or terminating your Lightless account indefinitely."); + _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), "Judgement of your profile validity from reports through staff is not up to debate and the decisions to disable your profile/account permanent."); + _uiSharedService.DrawNoteLine("! ", UIColors.Get("LightlessBlue"), "If your profile picture or profile description could be considered NSFW, enable the toggle in profile settings."); ImGui.PopStyleVar(); @@ -286,7 +277,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase { _uiSharedService.MediumText("Supporter Vanity Settings", UIColors.Get("LightlessPurple")); ImGui.Dummy(new Vector2(4)); - DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings."); + _uiSharedService.DrawNoteLine("# ", UIColors.Get("LightlessPurple"), "Must be a supporter through Patreon/Ko-fi to access these settings."); var hasVanity = _apiController.HasVanity; @@ -332,7 +323,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase const float colorPickAlign = 90f; - DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color"); + _uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Text Color"); ImGui.SameLine(colorPickAlign); ImGui.Checkbox("##toggleTextColor", ref textEnabled); ImGui.SameLine(); @@ -340,7 +331,7 @@ public class EditProfileUi : WindowMediatorSubscriberBase ImGui.ColorEdit4($"##color_text", ref textColor, ImGuiColorEditFlags.NoInputs | ImGuiColorEditFlags.AlphaPreviewHalf); ImGui.EndDisabled(); - DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color"); + _uiSharedService.DrawNoteLine("- ", UIColors.Get("LightlessPurple"), "Glow Color"); ImGui.SameLine(colorPickAlign); ImGui.Checkbox("##toggleGlowColor", ref glowEnabled); ImGui.SameLine(); diff --git a/LightlessSync/UI/UISharedService.cs b/LightlessSync/UI/UISharedService.cs index 24899ee..eb3acce 100644 --- a/LightlessSync/UI/UISharedService.cs +++ b/LightlessSync/UI/UISharedService.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.GameFonts; @@ -7,6 +7,7 @@ using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using System; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Dalamud.Utility; @@ -531,6 +532,52 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase { FontText(text, MediumFont, color); } + public void DrawNoteLine(string icon, Vector4 color, string text) + { + MediumText(icon, color); + var iconHeight = ImGui.GetItemRectSize().Y; + + ImGui.SameLine(); + + float textHeight = ImGui.GetTextLineHeight(); + float offset = (iconHeight - textHeight) * 0.5f; + if (offset > 0) + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + offset); + + ImGui.BeginGroup(); + ImGui.TextWrapped(text); + ImGui.EndGroup(); + } + + public void DrawNoteLine(string icon, Vector4 color, ReadOnlySpan fragments) + { + if (fragments.Length == 0) + { + DrawNoteLine(icon, color, string.Empty); + return; + } + + MediumText(icon, color); + var iconHeight = ImGui.GetItemRectSize().Y; + + ImGui.SameLine(); + + float textHeight = ImGui.GetTextLineHeight(); + float offset = (iconHeight - textHeight) * 0.5f; + if (offset > 0) + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + offset); + + var wrapWidth = ImGui.GetContentRegionAvail().X; + ImGui.BeginGroup(); + var richText = SeStringUtils.BuildRichText(fragments); + SeStringUtils.RenderSeStringWrapped(richText, wrapWidth); + ImGui.EndGroup(); + } + + public void DrawNoteLine(string icon, Vector4 color, params SeStringUtils.RichTextEntry[] fragments) + { + DrawNoteLine(icon, color, fragments.AsSpan()); + } public bool MediumTreeNode(string label, Vector4? textColor = null, float lineWidth = 2f, ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags.SpanAvailWidth) { diff --git a/LightlessSync/Utils/SeStringUtils.cs b/LightlessSync/Utils/SeStringUtils.cs index 837d13d..a19a343 100644 --- a/LightlessSync/Utils/SeStringUtils.cs +++ b/LightlessSync/Utils/SeStringUtils.cs @@ -1,17 +1,23 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Interface; using Dalamud.Interface.ImGuiSeStringRenderer; using Dalamud.Interface.Utility; +using Lumina.Text; +using System; using System.Numerics; +using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString; +using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; +using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; namespace LightlessSync.Utils; public static class SeStringUtils { - public static SeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor) + public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor) { - var b = new SeStringBuilder(); + var b = new DalamudSeStringBuilder(); if (glowColor is Vector4 glow) b.Add(new GlowPayload(glow)); @@ -30,14 +36,47 @@ public static class SeStringUtils return b.Build(); } - public static SeString BuildPlain(string text) + public static DalamudSeString BuildPlain(string text) { - var b = new SeStringBuilder(); + var b = new DalamudSeStringBuilder(); b.AddText(text ?? string.Empty); return b.Build(); } - public static void RenderSeString(SeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null) + public static DalamudSeString BuildRichText(ReadOnlySpan fragments) + { + var builder = new LuminaSeStringBuilder(); + + foreach (var fragment in fragments) + { + if (string.IsNullOrEmpty(fragment.Text)) + continue; + + var hasColor = fragment.Color.HasValue; + Vector4 color = default; + if (hasColor) + { + color = fragment.Color!.Value; + builder.PushColorRgba(color); + } + + if (fragment.Bold) + builder.AppendSetBold(true); + + builder.Append(fragment.Text.AsSpan()); + + if (fragment.Bold) + builder.AppendSetBold(false); + + if (hasColor) + builder.PopColor(); + } + + return DalamudSeString.Parse(builder.ToArray()); + } + + public static DalamudSeString BuildRichText(params RichTextEntry[] fragments) => BuildRichText(fragments.AsSpan()); + public static void RenderSeString(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, ImDrawListPtr? drawList = null) { drawList ??= ImGui.GetWindowDrawList(); @@ -51,9 +90,36 @@ public static class SeStringUtils ImGui.SetCursorScreenPos(position); ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams); + + var textSize = ImGui.CalcTextSize(seString.TextValue); + if (textSize.Y <= 0f) + textSize.Y = ImGui.GetTextLineHeight(); + + ImGui.Dummy(new Vector2(0f, textSize.Y)); } - public static Vector2 RenderSeStringWithHitbox(SeString seString, Vector2 position, ImFontPtr? font = null) + public static void RenderSeStringWrapped(DalamudSeString seString, float wrapWidth, ImFontPtr? font = null, ImDrawListPtr? drawList = null) + { + drawList ??= ImGui.GetWindowDrawList(); + + var drawParams = new SeStringDrawParams + { + Font = font ?? ImGui.GetFont(), + Color = ImGui.GetColorU32(ImGuiCol.Text), + WrapWidth = wrapWidth, + TargetDrawList = drawList + }; + + ImGuiHelpers.SeStringWrapped(seString.Encode(), drawParams); + + var calcWrapWidth = wrapWidth > 0f ? wrapWidth : -1f; + var textSize = ImGui.CalcTextSize(seString.TextValue, wrapWidth: calcWrapWidth); + if (textSize.Y <= 0f) + textSize.Y = ImGui.GetTextLineHeight(); + + ImGui.Dummy(new Vector2(0f, textSize.Y)); + } + public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null) { var drawList = ImGui.GetWindowDrawList(); @@ -99,6 +165,8 @@ public static class SeStringUtils #region Internal Payloads + public readonly record struct RichTextEntry(string Text, Vector4? Color = null, bool Bold = false); + private abstract class AbstractColorPayload : Payload { protected byte Red { get; init; } From e91d163763c9bf44b40192b3091b095bb89e3b65 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Wed, 1 Oct 2025 02:47:11 +0200 Subject: [PATCH 3/9] Disabled the pair request on already paired users --- LightlessSync/Plugin.cs | 2 +- LightlessSync/Services/NameplateService.cs | 2 -- LightlessSync/UI/ContextMenu.cs | 22 ++++++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 24f1745..93e666a 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -147,7 +147,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(addonLifecycle); - collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, p.GetRequiredService())); + collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, p.GetRequiredService(), p.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService>(), pluginInterface, diff --git a/LightlessSync/Services/NameplateService.cs b/LightlessSync/Services/NameplateService.cs index 9de944e..6a94a53 100644 --- a/LightlessSync/Services/NameplateService.cs +++ b/LightlessSync/Services/NameplateService.cs @@ -40,7 +40,6 @@ public class NameplateService : DisposableMediatorSubscriberBase private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList handlers) { - if (!_configService.Current.IsNameplateColorsEnabled || (_configService.Current.IsNameplateColorsEnabled && _clientState.IsPvPExcludingDen)) return; @@ -78,7 +77,6 @@ public class NameplateService : DisposableMediatorSubscriberBase public void RequestRedraw() { - _namePlateGui.RequestRedraw(); } diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index b828cdd..e300f16 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -3,13 +3,13 @@ using Dalamud.Game.Gui.ContextMenu; using Dalamud.Plugin; using Dalamud.Plugin.Services; using LightlessSync.LightlessConfiguration; +using LightlessSync.PlayerData.Pairs; using LightlessSync.Services; using LightlessSync.Utils; using LightlessSync.WebAPI; using Lumina.Excel.Sheets; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.Linq; namespace LightlessSync.UI; @@ -21,16 +21,17 @@ internal class ContextMenu : IHostedService private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtil; private readonly LightlessConfigService _configService; + private readonly PairManager _pairManager; private readonly ApiController _apiController; private readonly IObjectTable _objectTable; - private static readonly string[] ValidAddons = new[] - { + private static readonly string[] _validAddons = + [ null, "PartyMemberList", "FriendList", "FreeCompany", "LinkShell", "CrossWorldLinkshell", "_PartyList", "ChatLog", "LookingForGroup", "BlackList", "ContentMemberList", "SocialList", "ContactList", "BeginnerChatList", "MuteList" - }; + ]; public ContextMenu( IContextMenu contextMenu, @@ -40,7 +41,8 @@ internal class ContextMenu : IHostedService DalamudUtilService dalamudUtil, ApiController apiController, IObjectTable objectTable, - LightlessConfigService configService) + LightlessConfigService configService, + PairManager pairManager) { _contextMenu = contextMenu; _pluginInterface = pluginInterface; @@ -50,6 +52,7 @@ internal class ContextMenu : IHostedService _apiController = apiController; _objectTable = objectTable; _configService = configService; + _pairManager = pairManager; } public Task StartAsync(CancellationToken cancellationToken) @@ -81,7 +84,7 @@ internal class ContextMenu : IHostedService if (!_pluginInterface.UiBuilder.ShouldModifyUi) return; - if (!ValidAddons.Contains(args.AddonName, StringComparer.Ordinal)) + if (!_validAddons.Contains(args.AddonName, StringComparer.Ordinal)) return; if (args.Target is not MenuTargetDefault target) @@ -94,6 +97,9 @@ internal class ContextMenu : IHostedService if (targetData == null || targetData.Address == IntPtr.Zero) return; + if (VisibleUserIds.Any(u => u == target.TargetObjectId)) + return; + var world = GetWorld(target.TargetHomeWorld.RowId); if (!IsWorldValid(world)) return; @@ -138,6 +144,10 @@ internal class ContextMenu : IHostedService _logger.LogError(ex, "Error sending pair request."); } } + private HashSet VisibleUserIds => _pairManager.GetOnlineUserPairs() + .Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue) + .Select(u => (ulong)u.PlayerCharacterId) + .ToHashSet(); private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target) { From 49d138049e0b6ffcb3a03090d1a9241c68d540a5 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Wed, 1 Oct 2025 03:15:11 +0200 Subject: [PATCH 4/9] Added check if in PVP/Gpose and check if you are targetting your own. --- LightlessSync/Plugin.cs | 4 +++- LightlessSync/UI/ContextMenu.cs | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 93e666a..7490ebe 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -147,7 +147,9 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(addonLifecycle); - collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, p.GetRequiredService(), p.GetRequiredService())); + collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, + p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, + p.GetRequiredService(), p.GetRequiredService(), clientState)); collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); collection.AddSingleton((s) => new IpcCallerGlamourer(s.GetRequiredService>(), pluginInterface, diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/UI/ContextMenu.cs index e300f16..efa9997 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/UI/ContextMenu.cs @@ -21,6 +21,7 @@ internal class ContextMenu : IHostedService private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtil; private readonly LightlessConfigService _configService; + private readonly IClientState _clientState; private readonly PairManager _pairManager; private readonly ApiController _apiController; private readonly IObjectTable _objectTable; @@ -42,7 +43,8 @@ internal class ContextMenu : IHostedService ApiController apiController, IObjectTable objectTable, LightlessConfigService configService, - PairManager pairManager) + PairManager pairManager, + IClientState clientState) { _contextMenu = contextMenu; _pluginInterface = pluginInterface; @@ -53,6 +55,7 @@ internal class ContextMenu : IHostedService _objectTable = objectTable; _configService = configService; _pairManager = pairManager; + _clientState = clientState; } public Task StartAsync(CancellationToken cancellationToken) @@ -86,20 +89,29 @@ internal class ContextMenu : IHostedService if (!_validAddons.Contains(args.AddonName, StringComparer.Ordinal)) return; - + + //Check if target is not menutargetdefault. if (args.Target is not MenuTargetDefault target) return; + //Check if name or target id isnt null/zero if (string.IsNullOrEmpty(target.TargetName) || target.TargetObjectId == 0 || target.TargetHomeWorld.RowId == 0) return; + //Check if it is a real target. IPlayerCharacter? targetData = GetPlayerFromObjectTable(target); if (targetData == null || targetData.Address == IntPtr.Zero) return; - if (VisibleUserIds.Any(u => u == target.TargetObjectId)) + //Check if user is paired or is own. + if (VisibleUserIds.Any(u => u == target.TargetObjectId) || _clientState.LocalPlayer.GameObjectId == target.TargetObjectId) return; + //Check if in PVP or GPose + if (_clientState.IsPvPExcludingDen || _clientState.IsGPosing) + return; + + //Check for valid world. var world = GetWorld(target.TargetHomeWorld.RowId); if (!IsWorldValid(world)) return; From bf3770025b8db0b5fe25897af04d4cccaf2e7501 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Wed, 1 Oct 2025 03:20:31 +0200 Subject: [PATCH 5/9] Changed logger calls to debug/trace. --- LightlessSync/Services/BroadcastService.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/LightlessSync/Services/BroadcastService.cs b/LightlessSync/Services/BroadcastService.cs index 6d6409e..bf9eb05 100644 --- a/LightlessSync/Services/BroadcastService.cs +++ b/LightlessSync/Services/BroadcastService.cs @@ -141,7 +141,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber await _apiController.SetBroadcastStatus(msg.HashedCid, msg.Enabled, groupDto).ConfigureAwait(false); - _logger.LogInformation("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid); + _logger.LogDebug("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid); if (!msg.Enabled) { @@ -164,7 +164,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _config.Current.BroadcastEnabled = true; _config.Save(); - _logger.LogInformation("Fetched TTL from server: {TTL}", remaining); + _logger.LogDebug("Fetched TTL from server: {TTL}", remaining); _mediator.Publish(new BroadcastStatusChangedMessage(true, remaining)); Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(BroadcastService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}"))); } @@ -201,13 +201,13 @@ public class BroadcastService : IHostedService, IMediatorSubscriber { try { - _logger.LogInformation("[BroadcastCheck] Checking CID: {cid}", targetCid); + _logger.LogDebug("[BroadcastCheck] Checking CID: {cid}", targetCid); var info = await _apiController.IsUserBroadcasting(targetCid).ConfigureAwait(false); result = info?.TTL > TimeSpan.Zero; - _logger.LogInformation("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID); + _logger.LogDebug("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID); } catch (Exception ex) { @@ -251,7 +251,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber result[kv.Key] = kv.Value; } - _logger.LogInformation("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count); + _logger.LogTrace("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count); } catch (Exception ex) { @@ -291,10 +291,10 @@ public class BroadcastService : IHostedService, IMediatorSubscriber if (!newStatus) { _lastForcedDisableTime = DateTime.UtcNow; - _logger.LogInformation("Manual disable: cooldown timer started."); + _logger.LogDebug("Manual disable: cooldown timer started."); } - _logger.LogInformation("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus); + _logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus); _mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus)); } @@ -332,7 +332,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _config.Current.BroadcastTtl = DateTime.UtcNow + remaining; _config.Current.BroadcastEnabled = true; _config.Save(); - _logger.LogInformation("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining); + _logger.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", remaining); } else { @@ -361,7 +361,7 @@ public class BroadcastService : IHostedService, IMediatorSubscriber _remainingTtl = remaining > TimeSpan.Zero ? remaining : null; if (_remainingTtl == null) { - _logger.LogInformation("Broadcast TTL expired. Disabling broadcast locally."); + _logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally."); _config.Current.BroadcastEnabled = false; _config.Current.BroadcastTtl = DateTime.MinValue; _config.Save(); From 3a627f2d3edb790a0319ff2438170a0f849dd973 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Wed, 1 Oct 2025 04:11:01 +0200 Subject: [PATCH 6/9] Added refetch of syncshells after comfirmation. --- LightlessSync/UI/SyncshellFinderUI.cs | 61 +++++++++++++++------------ 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/LightlessSync/UI/SyncshellFinderUI.cs b/LightlessSync/UI/SyncshellFinderUI.cs index 4af72d5..c5dc2d2 100644 --- a/LightlessSync/UI/SyncshellFinderUI.cs +++ b/LightlessSync/UI/SyncshellFinderUI.cs @@ -103,6 +103,14 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return; } + DrawSyncshellTable(); + + if (_joinDto != null && _joinInfo != null && _joinInfo.Success) + DrawConfirmation(); + } + + private void DrawSyncshellTable() + { if (ImGui.BeginTable("##NearbySyncshellsTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg)) { ImGui.TableSetupColumn("Syncshell", ImGuiTableColumnFlags.WidthStretch); @@ -122,18 +130,18 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase var broadcasterName = "Unknown"; var broadcast = _broadcastScannerService.GetActiveSyncshellBroadcasts() .FirstOrDefault(b => string.Equals(b.GID, shell.Group.GID, StringComparison.Ordinal)); - + if (broadcast != null) { - var playerInfo = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); - if (!string.IsNullOrEmpty(playerInfo.Name)) + var (Name, Address) = _dalamudUtilService.FindPlayerByNameHash(broadcast.HashedCID); + if (!string.IsNullOrEmpty(Name)) { - var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(playerInfo.Address); - broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{playerInfo.Name} ({worldName})" : playerInfo.Name; + var worldName = _dalamudUtilService.GetWorldNameFromPlayerAddress(Address); + broadcasterName = !string.IsNullOrEmpty(worldName) ? $"{Name} ({worldName})" : Name; } } ImGui.TextUnformatted(broadcasterName); - + ImGui.TableNextColumn(); var label = $"Join##{shell.Group.GID}"; @@ -179,7 +187,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase } else { - using (ImRaii.Disabled()) { ImGui.Button(label); @@ -191,9 +198,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase ImGui.EndTable(); } - - if (_joinDto != null && _joinInfo != null && _joinInfo.Success) - DrawConfirmation(); } private void DrawConfirmation() @@ -222,6 +226,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase _ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions)); _joinDto = null; _joinInfo = null; + _ = RefreshSyncshellsAsync(); } } } @@ -255,7 +260,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase private async Task RefreshSyncshellsAsync() { var syncshellBroadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts(); - _currentSyncshells = _pairManager.GroupPairs.Select(g => g.Key).ToList(); + _currentSyncshells = [.. _pairManager.GroupPairs.Select(g => g.Key)]; if (syncshellBroadcasts.Count == 0) { @@ -263,7 +268,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase return; } - List updatedList = []; + List? updatedList = []; try { var groups = await _apiController.GetBroadcastedGroups(syncshellBroadcasts).ConfigureAwait(false); @@ -276,23 +281,27 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase } var currentGids = _nearbySyncshells.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal); - var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal); - if (currentGids.SetEquals(newGids)) - return; - - var previousGid = GetSelectedGid(); - - _nearbySyncshells.Clear(); - _nearbySyncshells.AddRange(updatedList); - - if (previousGid != null) + if (updatedList != null) { - var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal)); - if (newIndex >= 0) - { - _selectedNearbyIndex = newIndex; + var newGids = updatedList.Select(s => s.Group.GID).ToHashSet(StringComparer.Ordinal); + + if (currentGids.SetEquals(newGids)) return; + + var previousGid = GetSelectedGid(); + + _nearbySyncshells.Clear(); + _nearbySyncshells.AddRange(updatedList); + + if (previousGid != null) + { + var newIndex = _nearbySyncshells.FindIndex(s => string.Equals(s.Group.GID, previousGid, StringComparison.Ordinal)); + if (newIndex >= 0) + { + _selectedNearbyIndex = newIndex; + return; + } } } From 714aeef468dbff14448c54fc2f704272359db115 Mon Sep 17 00:00:00 2001 From: CakeAndBanana Date: Wed, 1 Oct 2025 04:16:19 +0200 Subject: [PATCH 7/9] Moved ContextMenu from UI to Service. --- LightlessSync/Plugin.cs | 6 ++--- .../ContextMenuService.cs} | 22 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) rename LightlessSync/{UI/ContextMenu.cs => Services/ContextMenuService.cs} (92%) diff --git a/LightlessSync/Plugin.cs b/LightlessSync/Plugin.cs index 7490ebe..bb66c5a 100644 --- a/LightlessSync/Plugin.cs +++ b/LightlessSync/Plugin.cs @@ -147,8 +147,8 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(addonLifecycle); - collection.AddSingleton(p => new ContextMenu(contextMenu, pluginInterface, gameData, - p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, + collection.AddSingleton(p => new ContextMenuService(contextMenu, pluginInterface, gameData, + p.GetRequiredService>(), p.GetRequiredService(), p.GetRequiredService(), objectTable, p.GetRequiredService(), p.GetRequiredService(), clientState)); collection.AddSingleton((s) => new IpcCallerPenumbra(s.GetRequiredService>(), pluginInterface, s.GetRequiredService(), s.GetRequiredService(), s.GetRequiredService())); @@ -263,7 +263,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); - collection.AddHostedService(p => p.GetRequiredService()); + collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); }) .Build(); diff --git a/LightlessSync/UI/ContextMenu.cs b/LightlessSync/Services/ContextMenuService.cs similarity index 92% rename from LightlessSync/UI/ContextMenu.cs rename to LightlessSync/Services/ContextMenuService.cs index efa9997..e941311 100644 --- a/LightlessSync/UI/ContextMenu.cs +++ b/LightlessSync/Services/ContextMenuService.cs @@ -4,21 +4,20 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; using LightlessSync.LightlessConfiguration; using LightlessSync.PlayerData.Pairs; -using LightlessSync.Services; using LightlessSync.Utils; using LightlessSync.WebAPI; using Lumina.Excel.Sheets; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace LightlessSync.UI; +namespace LightlessSync.Services; -internal class ContextMenu : IHostedService +internal class ContextMenuService : IHostedService { private readonly IContextMenu _contextMenu; private readonly IDalamudPluginInterface _pluginInterface; private readonly IDataManager _gameData; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly DalamudUtilService _dalamudUtil; private readonly LightlessConfigService _configService; private readonly IClientState _clientState; @@ -34,11 +33,11 @@ internal class ContextMenu : IHostedService "SocialList", "ContactList", "BeginnerChatList", "MuteList" ]; - public ContextMenu( + public ContextMenuService( IContextMenu contextMenu, IDalamudPluginInterface pluginInterface, IDataManager gameData, - ILogger logger, + ILogger logger, DalamudUtilService dalamudUtil, ApiController apiController, IObjectTable objectTable, @@ -100,7 +99,7 @@ internal class ContextMenu : IHostedService //Check if it is a real target. IPlayerCharacter? targetData = GetPlayerFromObjectTable(target); - if (targetData == null || targetData.Address == IntPtr.Zero) + if (targetData == null || targetData.Address == nint.Zero) return; //Check if user is paired or is own. @@ -139,7 +138,7 @@ internal class ContextMenu : IHostedService { IPlayerCharacter? targetData = GetPlayerFromObjectTable(target); - if (targetData == null || targetData.Address == IntPtr.Zero) + if (targetData == null || targetData.Address == nint.Zero) { _logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name); return; @@ -156,10 +155,9 @@ internal class ContextMenu : IHostedService _logger.LogError(ex, "Error sending pair request."); } } - private HashSet VisibleUserIds => _pairManager.GetOnlineUserPairs() + private HashSet VisibleUserIds => [.. _pairManager.GetOnlineUserPairs() .Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue) - .Select(u => (ulong)u.PlayerCharacterId) - .ToHashSet(); + .Select(u => (ulong)u.PlayerCharacterId)]; private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target) { @@ -196,7 +194,7 @@ internal class ContextMenu : IHostedService private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter); - private static bool IsChineseJapaneseKoreanCharacter(char c) => (c >= 0x4E00 && c <= 0x9FFF); + private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF; public bool IsWorldValid(uint worldId) => IsWorldValid(GetWorld(worldId)); From afc42d97d16e4eb2012645fae21ec6bff8636b4b Mon Sep 17 00:00:00 2001 From: azyges Date: Wed, 1 Oct 2025 23:25:34 +0900 Subject: [PATCH 8/9] highlight dark uid's --- .../Configurations/LightlessConfig.cs | 1 + LightlessSync/UI/Handlers/IdDisplayHandler.cs | 70 +++++++++++++++++-- LightlessSync/UI/SettingsUi.cs | 13 +++- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs index 235f0c5..7194c60 100644 --- a/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs +++ b/LightlessSync/LightlessConfiguration/Configurations/LightlessConfig.cs @@ -67,6 +67,7 @@ public class LightlessConfig : ILightlessConfiguration public bool UseFocusTarget { get; set; } = false; public bool overrideFriendColor { get; set; } = false; public bool overridePartyColor { get; set; } = false; + public bool useColoredUIDs { get; set; } = true; public bool BroadcastEnabled { get; set; } = false; public DateTime BroadcastTtl { get; set; } = DateTime.MinValue; public bool SyncshellFinderEnabled { get; set; } = false; diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs index 048efe9..1e3fee2 100644 --- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs +++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs @@ -1,5 +1,6 @@ -using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using LightlessSync.API.Dto.Group; using LightlessSync.LightlessConfiguration; @@ -7,6 +8,7 @@ using LightlessSync.PlayerData.Pairs; using LightlessSync.Services.Mediator; using LightlessSync.Services.ServerConfiguration; using LightlessSync.Utils; +using System; using System.Numerics; namespace LightlessSync.UI.Handlers; @@ -114,14 +116,74 @@ public class IdDisplayHandler } } - var seString = (textColor != null || glowColor != null) + var useVanityColors = _lightlessConfigService.Current.useColoredUIDs && (textColor != null || glowColor != null); + var seString = useVanityColors ? SeStringUtils.BuildFormattedPlayerName(playerText, textColor, glowColor) : SeStringUtils.BuildPlain(playerText); + var rowStart = ImGui.GetCursorScreenPos(); + var drawList = ImGui.GetWindowDrawList(); + bool useHighlight = false; + float highlightPadX = 0f; + float highlightPadY = 0f; + + if (useVanityColors && textColor is Vector4 contrastColor) + { + var brightness = (0.299f * contrastColor.X) + (0.587f * contrastColor.Y) + (0.114f * contrastColor.Z); + if (brightness < 0.35f) + { + var style = ImGui.GetStyle(); + useHighlight = true; + highlightPadX = MathF.Max(style.FramePadding.X * 0.6f, 2f * ImGuiHelpers.GlobalScale); + highlightPadY = MathF.Max(style.FramePadding.Y * 0.55f, 1.25f * ImGuiHelpers.GlobalScale); + drawList.ChannelsSplit(2); + drawList.ChannelsSetCurrent(1); + } + } + + Vector2 itemMin; + Vector2 itemMax; + Vector2 textSize; using (ImRaii.PushFont(font, textIsUid)) { - var pos = ImGui.GetCursorScreenPos(); - SeStringUtils.RenderSeStringWithHitbox(seString, pos, font); + SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font); + itemMin = ImGui.GetItemRectMin(); + itemMax = ImGui.GetItemRectMax(); + textSize = itemMax - itemMin; + } + + if (useHighlight) + { + var style = ImGui.GetStyle(); + var frameHeight = ImGui.GetFrameHeight(); + var rowTop = rowStart.Y - style.FramePadding.Y; + var rowBottom = rowTop + frameHeight; + + var highlightMin = new Vector2(itemMin.X - highlightPadX, rowTop - highlightPadY); + var highlightMax = new Vector2(itemMax.X + highlightPadX, rowBottom + highlightPadY); + + var windowPos = ImGui.GetWindowPos(); + var contentMin = windowPos + ImGui.GetWindowContentRegionMin(); + var contentMax = windowPos + ImGui.GetWindowContentRegionMax(); + highlightMin.X = MathF.Max(highlightMin.X, contentMin.X); + highlightMax.X = MathF.Min(highlightMax.X, contentMax.X); + highlightMin.Y = MathF.Max(highlightMin.Y, contentMin.Y); + highlightMax.Y = MathF.Min(highlightMax.Y, contentMax.Y); + + var highlightColor = style.Colors[(int)ImGuiCol.TableRowBgAlt]; + highlightColor.X = 0.25f; + highlightColor.Y = 0.25f; + highlightColor.Z = 0.25f; + highlightColor.W = 1f; + + float rounding = style.FrameRounding > 0f ? style.FrameRounding : 5f * ImGuiHelpers.GlobalScale; + drawList.ChannelsSetCurrent(0); + drawList.AddRectFilled(highlightMin, highlightMax, ImGui.GetColorU32(highlightColor), rounding); + + var borderColor = style.Colors[(int)ImGuiCol.Border]; + borderColor.W *= 0.25f; + drawList.AddRect(highlightMin, highlightMax, ImGui.GetColorU32(borderColor), rounding); + drawList.ChannelsMerge(); } if (ImGui.IsItemHovered()) diff --git a/LightlessSync/UI/SettingsUi.cs b/LightlessSync/UI/SettingsUi.cs index 95bb2af..2047fa0 100644 --- a/LightlessSync/UI/SettingsUi.cs +++ b/LightlessSync/UI/SettingsUi.cs @@ -1109,12 +1109,23 @@ public class SettingsUi : WindowMediatorSubscriberBase _uiShared.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"), 1.5f); - if (ImGui.Checkbox("Use the complete redesign of the UI for Lightless client.", ref useLightlessRedesign)) + ImGui.TextUnformatted("UI Theme"); + + if (ImGui.Checkbox("Use the redesign of the UI for Lightless client", ref useLightlessRedesign)) { _configService.Current.UseLightlessRedesign = useLightlessRedesign; _configService.Save(); } + var usePairColoredUIDs = _configService.Current.useColoredUIDs; + + if (ImGui.Checkbox("Toggle the colored UID's in pair list", ref usePairColoredUIDs)) + { + _configService.Current.useColoredUIDs = usePairColoredUIDs; + _configService.Save(); + } + _uiShared.DrawHelpText("This changes the vanity colored UID's in pair list."); + _uiShared.ColoredSeparator(UIColors.Get("LightlessPurple"), 1.5f); ImGui.TreePop(); } From 54ea1e9d4c290dd0bbd564377aafeec9f1a8bdc1 Mon Sep 17 00:00:00 2001 From: defnotken Date: Thu, 2 Oct 2025 15:38:04 -0500 Subject: [PATCH 9/9] cleanup workflows --- .../workflows/lightless-tag-and-release.yml | 97 +++++++++++- .../workflows/lightless-tag-and-release.yml | 140 ------------------ 2 files changed, 92 insertions(+), 145 deletions(-) delete mode 100644 .github/workflows/lightless-tag-and-release.yml diff --git a/.gitea/workflows/lightless-tag-and-release.yml b/.gitea/workflows/lightless-tag-and-release.yml index 63b4fb0..3401e21 100644 --- a/.gitea/workflows/lightless-tag-and-release.yml +++ b/.gitea/workflows/lightless-tag-and-release.yml @@ -2,7 +2,7 @@ name: Tag and Release Lightless on: push: - branches: [ master ] + branches: [ master, dev ] env: PLUGIN_NAME: LightlessSync @@ -62,7 +62,8 @@ jobs: mkdir -p output (cd /workspace/Lightless-Sync/LightlessClient/LightlessSync/bin/x64/Release/ && zip -r $OLDPWD/output/LightlessClient.zip *) - - name: Create Git tag if not exists + - name: Create Git tag if not exists (master) + if: github.ref == 'refs/heads/master' run: | tag="${{ steps.package_version.outputs.version }}" git fetch --tags @@ -76,7 +77,23 @@ jobs: echo "Tag $tag already exists. Skipping tag creation." fi - - name: Create Release + - name: Create Git tag if not exists (dev) + if: github.ref == 'refs/heads/dev' + run: | + tag="${{ steps.package_version.outputs.version }}-Dev" + git fetch --tags + if ! git tag -l "$tag" | grep -q "$tag"; then + echo "Tag $tag does not exist. Creating and pushing..." + git config user.name "GitHub Action" + git config user.email "action@github.com" + git tag "$tag" + git push origin "$tag" + else + echo "Tag $tag already exists. Skipping tag creation." + fi + + - name: Create Release (master) + if: github.ref == 'refs/heads/master' id: create_release run: | echo "=== Searching for existing release ${{ steps.package_version.outputs.version }}===" @@ -107,6 +124,35 @@ jobs: release_id=$(echo "$response" | jq -r .id) echo "release_id=$release_id" >> "$GITHUB_OUTPUT" + - name: Create Release (dev) + if: github.ref == 'refs/heads/dev' + id: create_release + run: | + version="${{ steps.package_version.outputs.version }}-Dev" + echo "=== Searching for existing release $version===" + release_id=$(curl -s -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/$version" | jq -r .id) + if [ "$release_id" != "null" ]; then + echo "=== Deleting existing release $version===" + curl -X DELETE -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases/$release_id" + fi + echo "=== Creating new release $version===" + response=$( + curl --fail-with-body -X POST \ + -H "Content-Type: application/json" \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -d '{ + "tag_name": "'"$version"'", + "name": "'"$version"'", + "draft": false, + "prerelease": false + }' \ + "https://git.lightless-sync.org/api/v1/repos/${GITHUB_REPOSITORY}/releases" + ) + release_id=$(echo "$response" | jq -r .id) + echo "release_id=$release_id" >> "$GITHUB_OUTPUT" + - name: Upload Assets to release run: | curl --fail-with-body -s -X POST \ @@ -122,7 +168,8 @@ jobs: env: GIT_TERMINAL_PROMPT: 0 - - name: Update plogonmaster.json with version + - name: Update plogonmaster.json with version (master) + if: github.ref == 'refs/heads/master' env: VERSION: ${{ steps.package_version.outputs.version }} run: | @@ -159,7 +206,6 @@ jobs: .DalamudApiLevel = $dalamudApiLevel | .AssemblyVersion = $version | .DownloadLinkInstall = $downloadUrl - | .DownloadLinkTesting = $downloadUrl | .DownloadLinkUpdate = $downloadUrl else . @@ -172,6 +218,47 @@ jobs: # Output the content of the file cat "$repoJsonPath" + - name: Update plogonmaster.json with version (dev) + if: github.ref == 'refs/heads/dev' + env: + VERSION: ${{ steps.package_version.outputs.version }} + run: | + set -e + pluginJsonPath="${PLUGIN_NAME}/bin/x64/Release/${PLUGIN_NAME}.json" + repoJsonPath="LightlessSyncRepo/LightlessSync/plogonmaster.json" + assemblyVersion="${VERSION}" + version="${VERSION}-Dev" + downloadUrl="https://git.lightless-sync.org/${{ gitea.repository_owner }}/LightlessClient/releases/download/$version/LightlessClient.zip" + pluginJson=$(cat "$pluginJsonPath") + internalName=$(jq -r '.InternalName' <<< "$pluginJson") + dalamudApiLevel=$(jq -r '.DalamudApiLevel' <<< "$pluginJson") + repoJsonRaw=$(cat "$repoJsonPath") + if echo "$repoJsonRaw" | jq 'type' | grep -q '"array"'; then + repoJson="$repoJsonRaw" + else + repoJson="[$repoJsonRaw]" + fi + updatedRepoJson=$(jq \ + --arg internalName "$internalName" \ + --arg dalamudApiLevel "$dalamudApiLevel" \ + --arg assemblyVersion "$assemblyVersion" \ + --arg version "$version" \ + --arg downloadUrl "$downloadUrl" \ + ' + map( + if .InternalName == $internalName + then + .DalamudApiLevel = $dalamudApiLevel + | .TestingAssemblyVersion = $assemblyVersion + | .DownloadLinkTesting = $downloadUrl + else + . + end + ) + ' <<< "$repoJson") + echo "$updatedRepoJson" > "$repoJsonPath" + cat "$repoJsonPath" + - name: Commit and push to LightlessSync run: | cd LightlessSyncRepo/LightlessSync diff --git a/.github/workflows/lightless-tag-and-release.yml b/.github/workflows/lightless-tag-and-release.yml deleted file mode 100644 index 9391829..0000000 --- a/.github/workflows/lightless-tag-and-release.yml +++ /dev/null @@ -1,140 +0,0 @@ -name: Tag and Release Lightless - -on: - push: - branches: [ master ] - -env: - PLUGIN_NAME: LightlessSync - DOTNET_VERSION: 9.x - -jobs: - tag-and-release: - runs-on: windows-2022 - permissions: - contents: write - - steps: - - name: Checkout Lightless - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: true - - - name: Setup .NET 9 SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 9.x - - - name: Download Dalamud - run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip - Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - - - name: Lets Build Lightless! - run: | - dotnet restore - dotnet build --configuration Release --no-restore - dotnet publish --configuration Release --no-build - - - name: Get version - id: package_version - uses: KageKirin/get-csproj-version@v0 - with: - file: LightlessSync/LightlessSync.csproj - - - name: Display version - run: | - echo "Version: ${{ steps.package_version.outputs.version }}" - - - name: Prepare Lightless Client - run: | - $publishPath = "${{ env.PLUGIN_NAME }}/bin/x64/Release/publish" - if (Test-Path $publishPath) { - Remove-Item -Recurse -Force $publishPath - Write-Host "Removed $publishPath" - } else { - Write-Host "$publishPath does not exist, nothing to remove." - } - mkdir output - Compress-Archive -Path ${{ env.PLUGIN_NAME }}/bin/x64/Release/* -DestinationPath output/LightlessClient.zip - - - name: Create Git tag if not exists - shell: pwsh - run: | - $tag = "${{ steps.package_version.outputs.version }}" - git fetch --tags - if (-not (git tag -l $tag)) { - Write-Host "Tag $tag does not exist. Creating and pushing..." - git config user.name "GitHub Action" - git config user.email "action@github.com" - git tag $tag - git push origin $tag - } else { - Write-Host "Tag $tag already exists. Skipping tag creation." - } - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ steps.package_version.outputs.version }} - name: ${{ steps.package_version.outputs.version }} - draft: false - prerelease: false - files: output/LightlessClient.zip - - - name: Clone plugin hosting repo - run: | - mkdir LightlessSyncRepo - cd LightlessSyncRepo - git clone https://github.com/${{ github.repository_owner }}/LightlessSync.git - env: - GIT_TERMINAL_PROMPT: 0 - - - name: Update plogonmaster.json with version - shell: pwsh - env: - VERSION: ${{ steps.package_version.outputs.version }} - run: | - $pluginJsonPath = "${{ env.PLUGIN_NAME }}/bin/x64/Release/${{ env.PLUGIN_NAME }}.json" - $pluginJson = Get-Content $pluginJsonPath | ConvertFrom-Json - $repoJsonPath = "LightlessSyncRepo/LightlessSync/plogonmaster.json" - $repoJsonRaw = Get-Content $repoJsonPath -Raw - $repoJson = $repoJsonRaw | ConvertFrom-Json - $version = $env:VERSION - $downloadUrl = "https://github.com/${{ github.repository_owner }}/LightlessClient/releases/download/$version/LightlessClient.zip" - - if (-not ($repoJson -is [System.Collections.IEnumerable])) { - $repoJson = @($repoJson) - } - - foreach ($plugin in $repoJson) { - if ($plugin.InternalName -eq $pluginJson.InternalName) { - $plugin.DalamudApiLevel = $pluginJson.DalamudApiLevel - $plugin.AssemblyVersion = $version - $plugin.DownloadLinkInstall = $downloadUrl - $plugin.DownloadLinkTesting = $downloadUrl - $plugin.DownloadLinkUpdate = $downloadUrl - } - } - - $repoJson | ConvertTo-Json -Depth 100 | Set-Content $repoJsonPath - - # Convert to JSON and force array brackets if necessary - $repoJsonString = $repoJson | ConvertTo-Json -Depth 100 - - # If the output is not an array, wrap it manually - if ($repoJsonString.Trim().StartsWith('{')) { - $repoJsonString = "[$repoJsonString]" - } - - $repoJsonString | Set-Content $repoJsonPath - - - name: Commit and push to LightlessSync - run: | - cd LightlessSyncRepo/LightlessSync - git config user.name "github-actions" - git config user.email "github-actions@github.com" - git add . - git commit -m "Update ${{ env.PLUGIN_NAME }} to ${{ steps.package_version.outputs.version }}" - git push https://x-access-token:${{ secrets.LIGHTLESS_TOKEN }}@github.com/${{ github.repository_owner }}/LightlessSync.git HEAD:main