2025-08-22 02:19:48 +01:00
|
|
|
|
using Dalamud.Game.Gui.Dtr;
|
|
|
|
|
using Dalamud.Game.Text.SeStringHandling;
|
|
|
|
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|
|
|
|
using Dalamud.Plugin.Services;
|
|
|
|
|
using MareSynchronos.MareConfiguration;
|
|
|
|
|
using MareSynchronos.MareConfiguration.Configurations;
|
|
|
|
|
using MareSynchronos.PlayerData.Pairs;
|
|
|
|
|
using MareSynchronos.Services.Mediator;
|
|
|
|
|
using MareSynchronos.WebAPI;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
namespace MareSynchronos.UI;
|
|
|
|
|
|
|
|
|
|
public sealed class DtrEntry : IDisposable, IHostedService
|
|
|
|
|
{
|
|
|
|
|
private enum DtrStyle
|
|
|
|
|
{
|
|
|
|
|
Default,
|
|
|
|
|
Style1,
|
|
|
|
|
Style2,
|
|
|
|
|
Style3,
|
|
|
|
|
Style4,
|
|
|
|
|
Style5,
|
|
|
|
|
Style6,
|
|
|
|
|
Style7,
|
|
|
|
|
Style8,
|
|
|
|
|
Style9
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public const int NumStyles = 10;
|
|
|
|
|
|
|
|
|
|
private readonly ApiController _apiController;
|
|
|
|
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
|
|
|
|
private readonly MareConfigService _configService;
|
|
|
|
|
private readonly IDtrBar _dtrBar;
|
|
|
|
|
private readonly Lazy<IDtrBarEntry> _entry;
|
|
|
|
|
private readonly ILogger<DtrEntry> _logger;
|
|
|
|
|
private readonly MareMediator _mareMediator;
|
|
|
|
|
private readonly PairManager _pairManager;
|
|
|
|
|
private Task? _runTask;
|
|
|
|
|
private string? _text;
|
|
|
|
|
private string? _tooltip;
|
|
|
|
|
private Colors _colors;
|
|
|
|
|
|
|
|
|
|
public DtrEntry(ILogger<DtrEntry> logger, IDtrBar dtrBar, MareConfigService configService, MareMediator mareMediator, PairManager pairManager, ApiController apiController)
|
|
|
|
|
{
|
|
|
|
|
_logger = logger;
|
|
|
|
|
_dtrBar = dtrBar;
|
|
|
|
|
_entry = new(CreateEntry);
|
|
|
|
|
_configService = configService;
|
|
|
|
|
_mareMediator = mareMediator;
|
|
|
|
|
_pairManager = pairManager;
|
|
|
|
|
_apiController = apiController;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
if (_entry.IsValueCreated)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogDebug("Disposing DtrEntry");
|
|
|
|
|
Clear();
|
|
|
|
|
_entry.Value.Remove();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Starting DtrEntry");
|
|
|
|
|
_runTask = Task.Run(RunAsync, _cancellationTokenSource.Token);
|
|
|
|
|
_logger.LogInformation("Started DtrEntry");
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
_cancellationTokenSource.Cancel();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await _runTask!.ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
catch (OperationCanceledException)
|
|
|
|
|
{
|
|
|
|
|
// ignore cancelled
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
_cancellationTokenSource.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Clear()
|
|
|
|
|
{
|
|
|
|
|
if (!_entry.IsValueCreated) return;
|
|
|
|
|
_logger.LogInformation("Clearing entry");
|
|
|
|
|
_text = null;
|
|
|
|
|
_tooltip = null;
|
|
|
|
|
_colors = default;
|
|
|
|
|
|
|
|
|
|
_entry.Value.Shown = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IDtrBarEntry CreateEntry()
|
|
|
|
|
{
|
|
|
|
|
_logger.LogTrace("Creating new DtrBar entry");
|
2025-08-22 21:17:09 +01:00
|
|
|
|
var entry = _dtrBar.Get("Snowcloak");
|
2025-08-22 02:19:48 +01:00
|
|
|
|
entry.OnClick = _ => _mareMediator.Publish(new UiToggleMessage(typeof(CompactUi)));
|
|
|
|
|
|
|
|
|
|
return entry;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RunAsync()
|
|
|
|
|
{
|
|
|
|
|
while (!_cancellationTokenSource.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(1000, _cancellationTokenSource.Token).ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
Update();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
|
|
|
|
if (!_configService.Current.EnableDtrEntry || !_configService.Current.HasValidSetup())
|
|
|
|
|
{
|
|
|
|
|
if (_entry.IsValueCreated && _entry.Value.Shown)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Disabling entry");
|
|
|
|
|
|
|
|
|
|
Clear();
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_entry.Value.Shown)
|
|
|
|
|
{
|
|
|
|
|
_logger.LogInformation("Showing entry");
|
|
|
|
|
_entry.Value.Shown = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string text;
|
|
|
|
|
string tooltip;
|
|
|
|
|
Colors colors;
|
|
|
|
|
if (_apiController.IsConnected)
|
|
|
|
|
{
|
|
|
|
|
var pairCount = _pairManager.GetVisibleUserCount();
|
|
|
|
|
|
|
|
|
|
text = RenderDtrStyle(_configService.Current.DtrStyle, pairCount.ToString());
|
|
|
|
|
if (pairCount > 0)
|
|
|
|
|
{
|
|
|
|
|
IEnumerable<string> visiblePairs;
|
|
|
|
|
if (_configService.Current.ShowUidInDtrTooltip)
|
|
|
|
|
{
|
|
|
|
|
visiblePairs = _pairManager.GetOnlineUserPairs()
|
|
|
|
|
.Where(x => x.IsVisible)
|
|
|
|
|
.Select(x => string.Format("{0} ({1})", _configService.Current.PreferNoteInDtrTooltip ? x.GetNoteOrName() : x.PlayerName, x.UserData.AliasOrUID));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
visiblePairs = _pairManager.GetOnlineUserPairs()
|
|
|
|
|
.Where(x => x.IsVisible)
|
|
|
|
|
.Select(x => string.Format("{0}", _configService.Current.PreferNoteInDtrTooltip ? x.GetNoteOrName() : x.PlayerName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tooltip = $"Elezen: Connected{Environment.NewLine}----------{Environment.NewLine}{string.Join(Environment.NewLine, visiblePairs)}";
|
|
|
|
|
colors = _configService.Current.DtrColorsPairsInRange;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
tooltip = "Elezen: Connected";
|
|
|
|
|
colors = _configService.Current.DtrColorsDefault;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
text = RenderDtrStyle(_configService.Current.DtrStyle, "\uE04C");
|
|
|
|
|
tooltip = "Elezen: Not Connected";
|
|
|
|
|
colors = _configService.Current.DtrColorsNotConnected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_configService.Current.UseColorsInDtr)
|
|
|
|
|
colors = default;
|
|
|
|
|
|
|
|
|
|
if (!string.Equals(text, _text, StringComparison.Ordinal) || !string.Equals(tooltip, _tooltip, StringComparison.Ordinal) || colors != _colors)
|
|
|
|
|
{
|
|
|
|
|
_text = text;
|
|
|
|
|
_tooltip = tooltip;
|
|
|
|
|
_colors = colors;
|
|
|
|
|
_entry.Value.Text = BuildColoredSeString(text, colors);
|
|
|
|
|
_entry.Value.Tooltip = tooltip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string RenderDtrStyle(int styleNum, string text)
|
|
|
|
|
{
|
|
|
|
|
var style = (DtrStyle)styleNum;
|
|
|
|
|
|
|
|
|
|
return style switch {
|
|
|
|
|
DtrStyle.Style1 => $"\xE039 {text}",
|
|
|
|
|
DtrStyle.Style2 => $"\xE0BC {text}",
|
|
|
|
|
DtrStyle.Style3 => $"\xE0BD {text}",
|
|
|
|
|
DtrStyle.Style4 => $"\xE03A {text}",
|
|
|
|
|
DtrStyle.Style5 => $"\xE033 {text}",
|
|
|
|
|
DtrStyle.Style6 => $"\xE038 {text}",
|
|
|
|
|
DtrStyle.Style7 => $"\xE05D {text}",
|
|
|
|
|
DtrStyle.Style8 => $"\xE03C{text}",
|
|
|
|
|
DtrStyle.Style9 => $"\xE040 {text} \xE041",
|
|
|
|
|
_ => $"\uE044 {text}"
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Colored SeString
|
|
|
|
|
private const byte _colorTypeForeground = 0x13;
|
|
|
|
|
private const byte _colorTypeGlow = 0x14;
|
|
|
|
|
|
|
|
|
|
private static SeString BuildColoredSeString(string text, Colors colors)
|
|
|
|
|
{
|
|
|
|
|
var ssb = new SeStringBuilder();
|
|
|
|
|
if (colors.Foreground != default)
|
|
|
|
|
ssb.Add(BuildColorStartPayload(_colorTypeForeground, colors.Foreground));
|
|
|
|
|
if (colors.Glow != default)
|
|
|
|
|
ssb.Add(BuildColorStartPayload(_colorTypeGlow, colors.Glow));
|
|
|
|
|
ssb.AddText(text);
|
|
|
|
|
if (colors.Glow != default)
|
|
|
|
|
ssb.Add(BuildColorEndPayload(_colorTypeGlow));
|
|
|
|
|
if (colors.Foreground != default)
|
|
|
|
|
ssb.Add(BuildColorEndPayload(_colorTypeForeground));
|
|
|
|
|
return ssb.Build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static RawPayload BuildColorStartPayload(byte colorType, uint color)
|
|
|
|
|
=> new(unchecked([0x02, colorType, 0x05, 0xF6, byte.Max((byte)color, 0x01), byte.Max((byte)(color >> 8), 0x01), byte.Max((byte)(color >> 16), 0x01), 0x03]));
|
|
|
|
|
|
|
|
|
|
private static RawPayload BuildColorEndPayload(byte colorType)
|
|
|
|
|
=> new([0x02, colorType, 0x02, 0xEC, 0x03]);
|
|
|
|
|
|
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
|
|
|
public readonly record struct Colors(uint Foreground = default, uint Glow = default);
|
|
|
|
|
#endregion
|
|
|
|
|
}
|