This commit is contained in:
2025-12-17 17:19:46 +09:00
9 changed files with 378 additions and 145 deletions

View File

@@ -1,11 +1,80 @@
tagline: "Lightless Sync v1.12.4" tagline: "Lightless Sync v2.0.0"
subline: "Bugfixes and various improvements across Lightless" subline: "LIGHTLESS IS EVOLVING!!"
changelog: changelog:
- name: "v2.0.0"
tagline: "Thank you for 4 months!"
date: "December 2025"
# be sure to set this every new version
isCurrent: true
versions:
- number: "Lightless Chat"
icon: ""
items:
- "Chat has been added to the top of the main UI. It will work in certain Zones or in Syncshells!"
- "You will only be able to use the chat feature after enabling it and accepting the rules. If you're not interested, don't use it!"
- "Breaking the rules may result in a mute or ban from chat. Serious offenses may result in a ban from the Lightless service altogether."
- "You can right click the offender in the chat and report them within the chat, reports will be reviewed asap."
- "Syncshells can enforce their own chat rules and moderate their own chat. This however does not apply to serious offenses."
- "Your name in chat will not be shown unless you are paired with the person OR you are in the same syncshell. Otherwise, you will be anonymous."
- "Refer to #release-notes in the Discord for more information. Feel free to ask questions in the Discord as well."
- number: "Changes to LightFinder"
icon: ""
items:
- "We have recieve quite a bit of reports of users crashing due to how Nameplates are handled across various plugins. As a result, we have moved the LightFinder icon and text to Imgui."
- "This should resolve the crashing issues, however, it may not look as nice as before. We are looking into ways to improve the Imgui experience in the future."
- "We will always prioritize stability and safety over visuals."
- "Refer to #release-notes in the Discord for an example of the error."
- number: "User Profiles, ShellFinder, Syncshells, Syncshell Profiles"
icon: ""
items:
- "Both User Profiles and Syncshell Profiles have been revamped for 2.0.0."
- "We have added profile tags to both Users and Syncshells that will show when a profile is being viewed"
- "Syncshell Admin Panel has been reworked to make it a friendlier experience"
- "Syncshell Moderators can now also broadcast on ShellFinder"
- "ShellFinder has been revamped to be more visually friends and also show more information (Tags) about the Syncshell"
- "Syncshells has an auto-prune feature now that will remove inactive members after a set amount of time, options available are 1, 3, 7, and 14 days that runs in 1 hour intervals"
- "IF YOUR SYNCSHELL IS NSFW, PLEASE MARK IT AS NSFW!"
- "Refer to #release-notes in the Discord for pretty pictures or try it yourself!."
- number: "Texture Optimization"
icon: ""
items:
- "In 2.0.0, we've added the option for Texture Optimization to improve the performance of scenarios such as overwhelmingly big "
- "NOTE: ALL OF THESE ARE OPTIONAL AND DISABLED BY DEFAULT"
- "Within Texture Optimization, you will be able to safely downscale all textures of new downloads around you."
- "This downscale DOES NOT APPLY to DIRECT PAIRS or those who've updated their preferred settings to not be downscaled"
- "The first time this is enabled, you may experience some lag or frame drops, but in the long run, it will help performance."
- "This can be found in Lightless Settings > Performance > Texture Optimization"
- "Like a broken record, please refer to #release-notes in the Discord for more information."
- number: "Character Analysis - The big scary UI no one knew about"
icon: ""
items:
- "We have made the Character Analysis UI more user friendly. This includes a revamp of the look and functionality"
- "You can now see more information about your character and how it affects performance"
- "It will show you the Textures tab by default with an option for \"Other file types\""
- "You can now choose if you want to BC7/BC5/BC4/BC3/BC1 compress a certain texture."
- "The UI will give you a recommendation on what BC compression to use based on the file."
- "Shows a small preview of what the texture looks like with some general info about it."
- "Shows you how much VRAM you would take up."
- "This can be found in Lightless Settings > Performance > Character Analysis"
- number: "Performance"
icon: ""
items:
- "Moved to the internal object table to have improved overall plugin performance."
- "Compactor is now running on a multi-threaded level instead of single-threaded; This should increase the speed of compacting files."
- "Penumbra Collections are now only made when people are visible, reducing the load on boot-up when having many Syncshells in your list."
- "Pairing system has been revamped to make pausing and unpausing faster, and loading people should be faster as well."
- number: "Miscellaneous Changes and Bugfixes"
icon: ""
items:
- "UI has been updated to look more modern"
- "We have started on file compression for Linux with the option for BTRFS or ZFS but it's not very great yet and will release later."
- "Nameplate colours now use sigs to client structs as an alternative to the Nameplate Handler, also preventing crashes on that from our end."
- "Notifications now work with the \"Enable multi-monitor windows\" settings of Dalamud."
- "Fixed a bug where nothing above the notifications was clickable in certain cases."
- "Added a check that prevents small messages from going below 0 resulting in an ArgumentOutOfRangeException."
- name: "v1.12.4" - name: "v1.12.4"
tagline: "Preparation for future features" tagline: "Preparation for future features"
date: "November 11th 2025" date: "November 11th 2025"
# be sure to set this every new version
isCurrent: true
versions: versions:
- number: "Syncshells" - number: "Syncshells"
icon: "" icon: ""

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>1.12.4</Version> <Version>2.0.0</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl> <PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>

View File

@@ -467,7 +467,8 @@ public sealed class Plugin : IDalamudPlugin
sp.GetRequiredService<ApiController>(), sp.GetRequiredService<ApiController>(),
sp.GetRequiredService<LightFinderScannerService>(), sp.GetRequiredService<LightFinderScannerService>(),
sp.GetRequiredService<PairUiService>(), sp.GetRequiredService<PairUiService>(),
sp.GetRequiredService<DalamudUtilService>())); sp.GetRequiredService<DalamudUtilService>(),
sp.GetRequiredService<LightlessProfileManager>()));
services.AddScoped<IPopupHandler, BanUserPopupHandler>(); services.AddScoped<IPopupHandler, BanUserPopupHandler>();
services.AddScoped<IPopupHandler, CensusPopupHandler>(); services.AddScoped<IPopupHandler, CensusPopupHandler>();

View File

@@ -12,11 +12,11 @@ namespace LightlessSync.Services.Chat;
public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedService public sealed class ZoneChatService : DisposableMediatorSubscriberBase, IHostedService
{ {
private const int MaxMessageHistory = 150; private const int MaxMessageHistory = 150;
internal const int MaxOutgoingLength = 400; internal const int MaxOutgoingLength = 200;
private const int MaxUnreadCount = 999; private const int MaxUnreadCount = 999;
private const string ZoneUnavailableMessage = "Zone chat is only available in major cities."; private const string ZoneUnavailableMessage = "Zone chat is only available in major cities.";
private const string ZoneChannelKey = "zone"; private const string ZoneChannelKey = "zone";
private const int MaxReportReasonLength = 500; private const int MaxReportReasonLength = 100;
private const int MaxReportContextLength = 1000; private const int MaxReportContextLength = 1000;
private readonly ApiController _apiController; private readonly ApiController _apiController;

View File

@@ -218,7 +218,7 @@ internal class ContextMenuService : IHostedService
return; return;
} }
var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetBlake3Hash(); var senderCid = (await _dalamudUtil.GetCIDAsync().ConfigureAwait(false)).ToString().GetHash256();
var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address); var receiverCid = DalamudUtilService.GetHashedCIDFromPlayerPointer(targetData.Address);
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid); _logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);

View File

@@ -562,7 +562,10 @@ public class DownloadUi : WindowMediatorSubscriberBase
{ {
boxHeight += lineHeight + spacingY; boxHeight += lineHeight + spacingY;
if (_configService.Current.ShowPlayerSpeedBarsTransferWindow && p.DlProg > 0) var showBar = _configService.Current.ShowPlayerSpeedBarsTransferWindow
&& p.TransferredBytes > 0;
if (showBar)
{ {
boxHeight += perPlayerBarHeight + spacingY; boxHeight += perPlayerBarHeight + spacingY;
} }
@@ -630,8 +633,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
); );
cursor.Y += lineHeight * 1.4f + spacingY; cursor.Y += lineHeight * 1.4f + spacingY;
if (_configService.Current.ShowPlayerLinesTransferWindow)
{
var orderedPlayers = perPlayer.OrderByDescending(p => p.TotalBytes).ToList(); var orderedPlayers = perPlayer.OrderByDescending(p => p.TotalBytes).ToList();
foreach (var p in orderedPlayers) foreach (var p in orderedPlayers)
@@ -641,19 +642,12 @@ public class DownloadUi : WindowMediatorSubscriberBase
? $"{UiSharedService.ByteToString((long)p.SpeedBytesPerSecond)}/s" ? $"{UiSharedService.ByteToString((long)p.SpeedBytesPerSecond)}/s"
: "-"; : "-";
// Label line for the player var showBar = _configService.Current.ShowPlayerSpeedBarsTransferWindow
&& p.TransferredBytes > 0;
var labelLine = var labelLine =
$"{p.Name} [W:{p.DlSlot}/Q:{p.DlQueue}/P:{p.DlProg}/D:{p.DlDecomp}] {p.TransferredFiles}/{p.TotalFiles}"; $"{p.Name} [W:{p.DlSlot}/Q:{p.DlQueue}/P:{p.DlProg}/D:{p.DlDecomp}] {p.TransferredFiles}/{p.TotalFiles}";
// State flags
var isDownloading = p.DlProg > 0;
var isDecompressing = p.DlDecomp > 0
|| (!isDownloading && p.TotalBytes > 0 && p.TransferredBytes >= p.TotalBytes);
var showBar = _configService.Current.ShowPlayerSpeedBarsTransferWindow
&& (isDownloading || isDecompressing);
if (!showBar) if (!showBar)
{ {
UiSharedService.DrawOutlinedFont( UiSharedService.DrawOutlinedFont(
@@ -669,7 +663,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
continue; continue;
} }
// Top label line (only name + W/Q/P/D + files)
UiSharedService.DrawOutlinedFont( UiSharedService.DrawOutlinedFont(
drawList, drawList,
labelLine, labelLine,
@@ -691,15 +684,10 @@ public class DownloadUi : WindowMediatorSubscriberBase
3f 3f
); );
// Fill based on Progress of download
float ratio = 0f; float ratio = 0f;
if (isDownloading && p.TotalBytes > 0) if (p.TotalBytes > 0)
{
ratio = (float)p.TransferredBytes / p.TotalBytes; ratio = (float)p.TransferredBytes / p.TotalBytes;
}
else if (isDecompressing)
{
ratio = 1f;
}
if (ratio < 0f) ratio = 0f; if (ratio < 0f) ratio = 0f;
if (ratio > 1f) ratio = 1f; if (ratio > 1f) ratio = 1f;
@@ -714,9 +702,17 @@ public class DownloadUi : WindowMediatorSubscriberBase
3f 3f
); );
// Text inside bar: downloading vs decompressing
string barText; string barText;
if (isDownloading) var isDecompressing = p.DlDecomp > 0 && p.TransferredBytes >= p.TotalBytes && p.TotalBytes > 0;
if (isDecompressing)
{
// Keep bar full, static text showing decompressing
barText = "Decompressing...";
}
else
{ {
var bytesInside = var bytesInside =
$"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}"; $"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}";
@@ -725,18 +721,11 @@ public class DownloadUi : WindowMediatorSubscriberBase
? $"{bytesInside} @ {playerSpeedText}" ? $"{bytesInside} @ {playerSpeedText}"
: bytesInside; : bytesInside;
} }
else if (isDecompressing)
{
barText = "Decompressing...";
}
else
{
barText = string.Empty;
}
if (!string.IsNullOrEmpty(barText)) if (!string.IsNullOrEmpty(barText))
{ {
var barTextSize = ImGui.CalcTextSize(barText); var barTextSize = ImGui.CalcTextSize(barText);
var barTextPos = new Vector2( var barTextPos = new Vector2(
barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1, barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1,
barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1 barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1
@@ -755,7 +744,6 @@ public class DownloadUi : WindowMediatorSubscriberBase
cursor.Y += perPlayerBarHeight + spacingY; cursor.Y += perPlayerBarHeight + spacingY;
} }
} }
}
public override bool DrawConditions() public override bool DrawConditions()
{ {

View File

@@ -2,6 +2,7 @@ using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Utility;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;

View File

@@ -1,21 +1,24 @@
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Colors; using Dalamud.Interface.Colors;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using LightlessSync.API.Data; using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto; using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using LightlessSync.UI.Tags;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using LightlessSync.UI.Services;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Numerics; using System.Numerics;
using LightlessSync.Services.LightFinder;
namespace LightlessSync.UI; namespace LightlessSync.UI;
@@ -28,6 +31,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private readonly PairUiService _pairUiService; private readonly PairUiService _pairUiService;
private readonly DalamudUtilService _dalamudUtilService; private readonly DalamudUtilService _dalamudUtilService;
private Vector4 _tagBackgroundColor = new(0.18f, 0.18f, 0.18f, 0.95f);
private Vector4 _tagBorderColor = new(0.35f, 0.35f, 0.35f, 0.4f);
private readonly List<SeStringUtils.SeStringSegment> _seResolvedSegments = new();
private readonly List<GroupJoinDto> _nearbySyncshells = []; private readonly List<GroupJoinDto> _nearbySyncshells = [];
private List<GroupFullInfoDto> _currentSyncshells = []; private List<GroupFullInfoDto> _currentSyncshells = [];
private int _selectedNearbyIndex = -1; private int _selectedNearbyIndex = -1;
@@ -40,6 +47,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private bool _useTestSyncshells = false; private bool _useTestSyncshells = false;
private bool _compactView = false; private bool _compactView = false;
private readonly LightlessProfileManager _lightlessProfileManager;
public SyncshellFinderUI( public SyncshellFinderUI(
ILogger<SyncshellFinderUI> logger, ILogger<SyncshellFinderUI> logger,
@@ -50,7 +58,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ApiController apiController, ApiController apiController,
LightFinderScannerService broadcastScannerService, LightFinderScannerService broadcastScannerService,
PairUiService pairUiService, PairUiService pairUiService,
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService) DalamudUtilService dalamudUtilService,
LightlessProfileManager lightlessProfileManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{ {
_broadcastService = broadcastService; _broadcastService = broadcastService;
_uiSharedService = uiShared; _uiSharedService = uiShared;
@@ -58,6 +67,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
_broadcastScannerService = broadcastScannerService; _broadcastScannerService = broadcastScannerService;
_pairUiService = pairUiService; _pairUiService = pairUiService;
_dalamudUtilService = dalamudUtilService; _dalamudUtilService = dalamudUtilService;
_lightlessProfileManager = lightlessProfileManager;
IsOpen = false; IsOpen = false;
WindowBuilder.For(this) WindowBuilder.For(this)
@@ -130,20 +140,34 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
return; return;
} }
string? myHashedCid = null;
try
{
var cid = _dalamudUtilService.GetCID();
myHashedCid = cid.ToString().GetHash256();
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to get CID, not excluding own broadcast.");
}
var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts().Where(b => !string.Equals(b.HashedCID, myHashedCid, StringComparison.Ordinal)).ToList() ?? [];
var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>(); var cardData = new List<(GroupJoinDto Shell, string BroadcasterName)>();
var broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
foreach (var shell in _nearbySyncshells) foreach (var shell in _nearbySyncshells)
{ {
string broadcasterName; string broadcasterName;
if (shell?.Group == null || string.IsNullOrEmpty(shell.Group.GID))
continue;
if (_useTestSyncshells) if (_useTestSyncshells)
{ {
var displayName = !string.IsNullOrEmpty(shell.Group.Alias) var displayName = !string.IsNullOrEmpty(shell.Group.Alias)
? shell.Group.Alias ? shell.Group.Alias
: shell.Group.GID; : shell.Group.GID;
broadcasterName = $"Tester of {displayName}"; broadcasterName = $"{displayName} (Tester of TestWorld)";
} }
else else
{ {
@@ -206,7 +230,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var (shell, broadcasterName) = listData[index]; var (shell, broadcasterName) = listData[index];
ImGui.PushID(shell.Group.GID); ImGui.PushID(shell.Group.GID);
float rowHeight = 90f * ImGuiHelpers.GlobalScale; float rowHeight = 74f * ImGuiHelpers.GlobalScale;
ImGui.BeginChild($"ShellRow##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true); ImGui.BeginChild($"ShellRow##{shell.Group.GID}", new Vector2(-1, rowHeight), border: true);
@@ -234,10 +258,48 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group);
IReadOnlyList<ProfileTagDefinition> groupTags =
groupProfile != null && groupProfile.Tags.Count > 0
? ProfileTagService.ResolveTags(groupProfile.Tags)
: [];
var limitedTags = groupTags.Count > 3
? [.. groupTags.Take(3)]
: groupTags;
float tagScale = ImGuiHelpers.GlobalScale * 0.9f;
Vector2 rowStartLocal = ImGui.GetCursorPos();
float tagsWidth = 0f;
float tagsHeight = 0f;
if (limitedTags.Count > 0)
{
(tagsWidth, tagsHeight) = RenderProfileTagsSingleRow(limitedTags, tagScale);
}
else
{
ImGui.SetCursorPosX(startX);
ImGui.TextDisabled("-- No tags set --");
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
}
float btnBaselineY = rowStartLocal.Y;
float joinX = rowStartLocal.X + (tagsWidth > 0 ? tagsWidth + style.ItemSpacing.X : 0f);
ImGui.SetCursorPos(new Vector2(joinX, btnBaselineY));
DrawJoinButton(shell); DrawJoinButton(shell);
float btnHeight = ImGui.GetFrameHeightWithSpacing();
float rowHeightUsed = MathF.Max(tagsHeight, btnHeight);
ImGui.SetCursorPos(new Vector2(
rowStartLocal.X,
rowStartLocal.Y + rowHeightUsed));
ImGui.EndChild(); ImGui.EndChild();
ImGui.PopID(); ImGui.PopID();
@@ -311,10 +373,39 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.SetTooltip("Broadcaster of the syncshell."); ImGui.SetTooltip("Broadcaster of the syncshell.");
ImGui.EndGroup(); ImGui.EndGroup();
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale)); ImGui.Dummy(new Vector2(0, 6 * ImGuiHelpers.GlobalScale));
var groupProfile = _lightlessProfileManager.GetLightlessGroupProfile(shell.Group);
IReadOnlyList<ProfileTagDefinition> groupTags =
groupProfile != null && groupProfile.Tags.Count > 0
? ProfileTagService.ResolveTags(groupProfile.Tags)
: [];
float tagScale = ImGuiHelpers.GlobalScale * 0.9f;
if (groupTags.Count > 0)
{
var limitedTags = groupTags.Count > 2
? [.. groupTags.Take(2)]
: groupTags;
ImGui.SetCursorPosX(startX);
var (_, tagsHeight) = RenderProfileTagsSingleRow(limitedTags, tagScale);
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
}
else
{
ImGui.SetCursorPosX(startX);
ImGui.TextDisabled("-- No tags set --");
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
}
var buttonHeight = ImGui.GetFrameHeightWithSpacing(); var buttonHeight = ImGui.GetFrameHeightWithSpacing();
var remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight; var remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight;
if (remainingY > 0) if (remainingY > 0)
@@ -338,7 +429,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
{ {
if (totalPages > 1) if (totalPages > 1)
{ {
UiSharedService.ColoredSeparator(UIColors.Get("PairBlue")); UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}"; string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}";
@@ -371,10 +462,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
const string visibleLabel = "Join"; const string visibleLabel = "Join";
var label = $"{visibleLabel}##{shell.Group.GID}"; var label = $"{visibleLabel}##{shell.Group.GID}";
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal)); var isAlreadyMember = _currentSyncshells.Exists(g => string.Equals(g.GID, shell.GID, StringComparison.Ordinal));
var isRecentlyJoined = _recentlyJoined.Contains(shell.GID); var isRecentlyJoined = _recentlyJoined.Contains(shell.GID);
@@ -386,7 +473,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var textSize = ImGui.CalcTextSize(visibleLabel); var textSize = ImGui.CalcTextSize(visibleLabel);
var width = textSize.X + style.FramePadding.X * 20f; var width = textSize.X + style.FramePadding.X * 20f;
buttonSize = new Vector2(width, 0); buttonSize = new Vector2(width, 30f);
float availX = ImGui.GetContentRegionAvail().X; float availX = ImGui.GetContentRegionAvail().X;
float curX = ImGui.GetCursorPosX(); float curX = ImGui.GetCursorPosX();
@@ -400,6 +487,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
if (!isAlreadyMember && !isRecentlyJoined) if (!isAlreadyMember && !isRecentlyJoined)
{ {
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("LightlessGreen"));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("LightlessGreen").WithAlpha(0.85f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("LightlessGreen").WithAlpha(0.75f));
if (ImGui.Button(label, buttonSize)) if (ImGui.Button(label, buttonSize))
{ {
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})"); _logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
@@ -436,6 +526,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
} }
else else
{ {
ImGui.PushStyleColor(ImGuiCol.Button, UIColors.Get("DimRed"));
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, UIColors.Get("DimRed").WithAlpha(0.85f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, UIColors.Get("DimRed").WithAlpha(0.75f));
using (ImRaii.Disabled()) using (ImRaii.Disabled())
{ {
ImGui.Button(label, buttonSize); ImGui.Button(label, buttonSize);
@@ -446,6 +540,72 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.PopStyleColor(3); ImGui.PopStyleColor(3);
} }
private (float widthUsed, float rowHeight) RenderProfileTagsSingleRow(IReadOnlyList<ProfileTagDefinition> tags, float scale)
{
if (tags == null || tags.Count == 0)
return (0f, 0f);
var drawList = ImGui.GetWindowDrawList();
var style = ImGui.GetStyle();
var defaultTextColorU32 = ImGui.GetColorU32(ImGuiCol.Text);
var baseLocal = ImGui.GetCursorPos();
var baseScreen = ImGui.GetCursorScreenPos();
float availableWidth = ImGui.GetContentRegionAvail().X;
if (availableWidth <= 0f)
availableWidth = 1f;
float cursorLocalX = baseLocal.X;
float cursorScreenX = baseScreen.X;
float rowHeight = 0f;
for (int i = 0; i < tags.Count; i++)
{
var tag = tags[i];
if (!tag.HasContent)
continue;
var tagSize = ProfileTagRenderer.MeasureTag(tag, scale, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger);
float tagWidth = tagSize.X;
float tagHeight = tagSize.Y;
if (cursorLocalX > baseLocal.X && cursorLocalX + tagWidth > baseLocal.X + availableWidth)
break;
var tagScreenPos = new Vector2(cursorScreenX, baseScreen.Y);
ImGui.SetCursorScreenPos(tagScreenPos);
ImGui.InvisibleButton($"##profileTagInline_{i}", tagSize);
ProfileTagRenderer.RenderTag(tag, tagScreenPos, scale, drawList, style, _tagBackgroundColor, _tagBorderColor, defaultTextColorU32, _seResolvedSegments, GetIconWrap, _logger);
cursorLocalX += tagWidth + style.ItemSpacing.X;
cursorScreenX += tagWidth + style.ItemSpacing.X;
rowHeight = MathF.Max(rowHeight, tagHeight);
}
ImGui.SetCursorPos(new Vector2(baseLocal.X, baseLocal.Y + rowHeight));
float widthUsed = cursorLocalX - baseLocal.X;
return (widthUsed, rowHeight);
}
private IDalamudTextureWrap? GetIconWrap(uint iconId)
{
try
{
if (_uiSharedService.TryGetIcon(iconId, out var wrap) && wrap != null)
return wrap;
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to resolve icon {IconId} for profile tags", iconId);
}
return null;
}
private void DrawConfirmation() private void DrawConfirmation()
{ {
if (_joinDto != null && _joinInfo != null) if (_joinDto != null && _joinInfo != null)

View File

@@ -1,19 +1,20 @@
using System;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions; using LightlessSync.API.Data.Extensions;
using LightlessSync.LightlessConfiguration.Models; using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.LightFinder; using LightlessSync.Services.LightFinder;
using LightlessSync.Utils; using LightlessSync.Services.Mediator;
using LightlessSync.UI.Models; using LightlessSync.UI.Models;
using LightlessSync.UI.Style; using LightlessSync.UI.Style;
using LightlessSync.Utils;
using LightlessSync.WebAPI; using LightlessSync.WebAPI;
using System;
using System.Numerics; using System.Numerics;
namespace LightlessSync.UI; namespace LightlessSync.UI;
@@ -799,9 +800,22 @@ public class TopTabMenu
if (!_lightFinderService.IsBroadcasting) if (!_lightFinderService.IsBroadcasting)
return "Syncshell Finder"; return "Syncshell Finder";
string? myHashedCid = null;
try
{
var cid = _dalamudUtilService.GetCID();
myHashedCid = cid.ToString().GetHash256();
}
catch (Exception)
{
// Couldnt get own CID, log and return default table
}
var nearbyCount = _lightFinderScannerService var nearbyCount = _lightFinderScannerService
.GetActiveSyncshellBroadcasts() .GetActiveSyncshellBroadcasts()
.Where(b => !string.IsNullOrEmpty(b.GID)) .Where(b =>
!string.IsNullOrEmpty(b.GID) &&
!string.Equals(b.HashedCID, myHashedCid, StringComparison.Ordinal))
.Select(b => b.GID!) .Select(b => b.GID!)
.Distinct(StringComparer.Ordinal) .Distinct(StringComparer.Ordinal)
.Count(); .Count();