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"
subline: "Bugfixes and various improvements across Lightless"
tagline: "Lightless Sync v2.0.0"
subline: "LIGHTLESS IS EVOLVING!!"
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"
tagline: "Preparation for future features"
date: "November 11th 2025"
# be sure to set this every new version
isCurrent: true
versions:
- number: "Syncshells"
icon: ""

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Authors></Authors>
<Company></Company>
<Version>1.12.4</Version>
<Version>2.0.0</Version>
<Description></Description>
<Copyright></Copyright>
<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<LightFinderScannerService>(),
sp.GetRequiredService<PairUiService>(),
sp.GetRequiredService<DalamudUtilService>()));
sp.GetRequiredService<DalamudUtilService>(),
sp.GetRequiredService<LightlessProfileManager>()));
services.AddScoped<IPopupHandler, BanUserPopupHandler>();
services.AddScoped<IPopupHandler, CensusPopupHandler>();

View File

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

View File

@@ -218,7 +218,7 @@ internal class ContextMenuService : IHostedService
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);
_logger.LogInformation("Sending pair request: sender {SenderCid}, receiver {ReceiverCid}", senderCid, receiverCid);

View File

@@ -560,9 +560,12 @@ public class DownloadUi : WindowMediatorSubscriberBase
{
foreach (var p in perPlayer)
{
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;
}
@@ -630,46 +633,23 @@ public class DownloadUi : WindowMediatorSubscriberBase
);
cursor.Y += lineHeight * 1.4f + spacingY;
if (_configService.Current.ShowPlayerLinesTransferWindow)
var orderedPlayers = perPlayer.OrderByDescending(p => p.TotalBytes).ToList();
foreach (var p in orderedPlayers)
{
var orderedPlayers = perPlayer.OrderByDescending(p => p.TotalBytes).ToList();
var hasSpeed = p.SpeedBytesPerSecond > 0;
var playerSpeedText = hasSpeed
? $"{UiSharedService.ByteToString((long)p.SpeedBytesPerSecond)}/s"
: "-";
foreach (var p in orderedPlayers)
var showBar = _configService.Current.ShowPlayerSpeedBarsTransferWindow
&& p.TransferredBytes > 0;
var labelLine =
$"{p.Name} [W:{p.DlSlot}/Q:{p.DlQueue}/P:{p.DlProg}/D:{p.DlDecomp}] {p.TransferredFiles}/{p.TotalFiles}";
if (!showBar)
{
var hasSpeed = p.SpeedBytesPerSecond > 0;
var playerSpeedText = hasSpeed
? $"{UiSharedService.ByteToString((long)p.SpeedBytesPerSecond)}/s"
: "-";
// Label line for the player
var labelLine =
$"{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)
{
UiSharedService.DrawOutlinedFont(
drawList,
labelLine,
cursor,
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
1
);
cursor.Y += lineHeight + spacingY;
continue;
}
// Top label line (only name + W/Q/P/D + files)
UiSharedService.DrawOutlinedFont(
drawList,
labelLine,
@@ -678,82 +658,90 @@ public class DownloadUi : WindowMediatorSubscriberBase
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
1
);
cursor.Y += lineHeight + spacingY;
// Bar background
var barBgMin = new Vector2(boxMin.X + padding, cursor.Y);
var barBgMax = new Vector2(boxMax.X - padding, cursor.Y + perPlayerBarHeight);
drawList.AddRectFilled(
barBgMin,
barBgMax,
UiSharedService.Color(40, 40, 40, _transferBoxTransparency),
3f
);
float ratio = 0f;
if (isDownloading && p.TotalBytes > 0)
{
ratio = (float)p.TransferredBytes / p.TotalBytes;
}
else if (isDecompressing)
{
ratio = 1f;
}
if (ratio < 0f) ratio = 0f;
if (ratio > 1f) ratio = 1f;
var fillX = barBgMin.X + (barBgMax.X - barBgMin.X) * ratio;
var barFillMax = new Vector2(fillX, barBgMax.Y);
drawList.AddRectFilled(
barBgMin,
barFillMax,
UiSharedService.Color(UIColors.Get("LightlessPurple")),
3f
);
string barText;
if (isDownloading)
{
var bytesInside =
$"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}";
barText = hasSpeed
? $"{bytesInside} @ {playerSpeedText}"
: bytesInside;
}
else if (isDecompressing)
{
barText = "Decompressing...";
}
else
{
barText = string.Empty;
}
if (!string.IsNullOrEmpty(barText))
{
var barTextSize = ImGui.CalcTextSize(barText);
var barTextPos = new Vector2(
barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1,
barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1
);
UiSharedService.DrawOutlinedFont(
drawList,
barText,
barTextPos,
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
1
);
}
cursor.Y += perPlayerBarHeight + spacingY;
continue;
}
UiSharedService.DrawOutlinedFont(
drawList,
labelLine,
cursor,
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
1
);
cursor.Y += lineHeight + spacingY;
// Bar background
var barBgMin = new Vector2(boxMin.X + padding, cursor.Y);
var barBgMax = new Vector2(boxMax.X - padding, cursor.Y + perPlayerBarHeight);
drawList.AddRectFilled(
barBgMin,
barBgMax,
UiSharedService.Color(40, 40, 40, _transferBoxTransparency),
3f
);
// Fill based on Progress of download
float ratio = 0f;
if (p.TotalBytes > 0)
ratio = (float)p.TransferredBytes / p.TotalBytes;
if (ratio < 0f) ratio = 0f;
if (ratio > 1f) ratio = 1f;
var fillX = barBgMin.X + (barBgMax.X - barBgMin.X) * ratio;
var barFillMax = new Vector2(fillX, barBgMax.Y);
drawList.AddRectFilled(
barBgMin,
barFillMax,
UiSharedService.Color(UIColors.Get("LightlessPurple")),
3f
);
// Text inside bar: downloading vs decompressing
string barText;
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 =
$"{UiSharedService.ByteToString(p.TransferredBytes, addSuffix: false)}/{UiSharedService.ByteToString(p.TotalBytes)}";
barText = hasSpeed
? $"{bytesInside} @ {playerSpeedText}"
: bytesInside;
}
if (!string.IsNullOrEmpty(barText))
{
var barTextSize = ImGui.CalcTextSize(barText);
var barTextPos = new Vector2(
barBgMin.X + ((barBgMax.X - barBgMin.X) - barTextSize.X) / 2f - 1,
barBgMin.Y + ((perPlayerBarHeight - barTextSize.Y) / 2f) - 1
);
UiSharedService.DrawOutlinedFont(
drawList,
barText,
barTextPos,
UiSharedService.Color(255, 255, 255, _transferBoxTransparency),
UiSharedService.Color(0, 0, 0, _transferBoxTransparency),
1
);
}
cursor.Y += perPlayerBarHeight + spacingY;
}
}

View File

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

View File

@@ -1,21 +1,24 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Textures.TextureWraps;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto;
using LightlessSync.API.Dto.Group;
using LightlessSync.Services;
using LightlessSync.Services.LightFinder;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Services;
using LightlessSync.UI.Tags;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using LightlessSync.UI.Services;
using Microsoft.Extensions.Logging;
using System.Numerics;
using LightlessSync.Services.LightFinder;
namespace LightlessSync.UI;
@@ -28,6 +31,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private readonly PairUiService _pairUiService;
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 List<GroupFullInfoDto> _currentSyncshells = [];
private int _selectedNearbyIndex = -1;
@@ -40,6 +47,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
private bool _useTestSyncshells = false;
private bool _compactView = false;
private readonly LightlessProfileManager _lightlessProfileManager;
public SyncshellFinderUI(
ILogger<SyncshellFinderUI> logger,
@@ -50,7 +58,8 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ApiController apiController,
LightFinderScannerService broadcastScannerService,
PairUiService pairUiService,
DalamudUtilService dalamudUtilService) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
DalamudUtilService dalamudUtilService,
LightlessProfileManager lightlessProfileManager) : base(logger, mediator, "Shellfinder###LightlessSyncshellFinderUI", performanceCollectorService)
{
_broadcastService = broadcastService;
_uiSharedService = uiShared;
@@ -58,11 +67,12 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
_broadcastScannerService = broadcastScannerService;
_pairUiService = pairUiService;
_dalamudUtilService = dalamudUtilService;
_lightlessProfileManager = lightlessProfileManager;
IsOpen = false;
WindowBuilder.For(this)
.SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550))
.Apply();
.SetSizeConstraints(new Vector2(600, 400), new Vector2(600, 550))
.Apply();
Mediator.Subscribe<SyncshellBroadcastsUpdatedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
Mediator.Subscribe<BroadcastStatusChangedMessage>(this, async _ => await RefreshSyncshellsAsync().ConfigureAwait(false));
@@ -80,7 +90,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
{
ImGui.BeginGroup();
_uiSharedService.MediumText("Nearby Syncshells", UIColors.Get("LightlessPurple"));
#if DEBUG
if (ImGui.SmallButton("Show test syncshells"))
{
@@ -92,7 +102,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
string checkboxLabel = "Compact view";
float availWidth = ImGui.GetContentRegionAvail().X;
float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight();
float checkboxWidth = ImGui.CalcTextSize(checkboxLabel).X + ImGui.GetFrameHeight();
float rightX = ImGui.GetCursorPosX() + availWidth - checkboxWidth - 4.0f;
ImGui.SetCursorPosX(rightX);
@@ -130,20 +140,34 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
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 broadcasts = _broadcastScannerService.GetActiveSyncshellBroadcasts();
foreach (var shell in _nearbySyncshells)
{
string broadcasterName;
if (shell?.Group == null || string.IsNullOrEmpty(shell.Group.GID))
continue;
if (_useTestSyncshells)
{
var displayName = !string.IsNullOrEmpty(shell.Group.Alias)
? shell.Group.Alias
: shell.Group.GID;
broadcasterName = $"Tester of {displayName}";
broadcasterName = $"{displayName} (Tester of TestWorld)";
}
else
{
@@ -206,7 +230,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var (shell, broadcasterName) = listData[index];
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);
@@ -234,10 +258,48 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
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);
float btnHeight = ImGui.GetFrameHeightWithSpacing();
float rowHeightUsed = MathF.Max(tagsHeight, btnHeight);
ImGui.SetCursorPos(new Vector2(
rowStartLocal.X,
rowStartLocal.Y + rowHeightUsed));
ImGui.EndChild();
ImGui.PopID();
@@ -311,10 +373,39 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
ImGui.SetTooltip("Broadcaster of the syncshell.");
ImGui.EndGroup();
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)
: [];
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 remainingY = ImGui.GetContentRegionAvail().Y - buttonHeight;
if (remainingY > 0)
@@ -338,7 +429,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
{
if (totalPages > 1)
{
UiSharedService.ColoredSeparator(UIColors.Get("PairBlue"));
UiSharedService.ColoredSeparator(UIColors.Get("LightlessPurpleDefault"));
var style = ImGui.GetStyle();
string pageLabel = $"Page {_syncshellPageIndex + 1}/{totalPages}";
@@ -371,10 +462,6 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
const string visibleLabel = "Join";
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 isRecentlyJoined = _recentlyJoined.Contains(shell.GID);
@@ -386,7 +473,7 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
var textSize = ImGui.CalcTextSize(visibleLabel);
var width = textSize.X + style.FramePadding.X * 20f;
buttonSize = new Vector2(width, 0);
buttonSize = new Vector2(width, 30f);
float availX = ImGui.GetContentRegionAvail().X;
float curX = ImGui.GetCursorPosX();
@@ -400,6 +487,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
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))
{
_logger.LogInformation($"Join requested for Syncshell {shell.Group.GID} ({shell.Group.Alias})");
@@ -436,6 +526,10 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
}
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())
{
ImGui.Button(label, buttonSize);
@@ -446,6 +540,72 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
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()
{
if (_joinDto != null && _joinInfo != null)
@@ -470,9 +630,9 @@ public class SyncshellFinderUI : WindowMediatorSubscriberBase
finalPermissions.SetDisableVFX(_ownPermissions.DisableGroupVFX);
_ = _apiController.GroupJoinFinalize(new GroupJoinDto(_joinDto.Group, _joinDto.Password, finalPermissions));
_recentlyJoined.Add(_joinDto.Group.GID);
_joinDto = null;
_joinInfo = null;
}

View File

@@ -1,19 +1,20 @@
using System;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using LightlessSync.API.Data.Enum;
using LightlessSync.API.Data.Extensions;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.LightFinder;
using LightlessSync.Utils;
using LightlessSync.Services.Mediator;
using LightlessSync.UI.Models;
using LightlessSync.UI.Style;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using System;
using System.Numerics;
namespace LightlessSync.UI;
@@ -799,9 +800,22 @@ public class TopTabMenu
if (!_lightFinderService.IsBroadcasting)
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
.GetActiveSyncshellBroadcasts()
.Where(b => !string.IsNullOrEmpty(b.GID))
.Where(b =>
!string.IsNullOrEmpty(b.GID) &&
!string.Equals(b.HashedCID, myHashedCid, StringComparison.Ordinal))
.Select(b => b.GID!)
.Distinct(StringComparer.Ordinal)
.Count();