Files
UmbraClient/MareSynchronos/Services/GuiHookService.cs
2025-10-04 19:15:27 +02:00

178 lines
6.9 KiB
C#

using System;
using Dalamud.Game.Gui.NamePlate;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Plugin.Services;
using MareSynchronos.API.Dto.User;
using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator;
using MareSynchronos.UI;
using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging;
using Dalamud.Game.Text;
namespace MareSynchronos.Services;
public class GuiHookService : DisposableMediatorSubscriberBase
{
private readonly ILogger<GuiHookService> _logger;
private readonly DalamudUtilService _dalamudUtil;
private readonly MareConfigService _configService;
private readonly INamePlateGui _namePlateGui;
private readonly IGameConfig _gameConfig;
private readonly IPartyList _partyList;
private readonly PairManager _pairManager;
private readonly IClientState _clientState;
private readonly ApiController _apiController;
private readonly TypingIndicatorStateService _typingStateService;
private static readonly TimeSpan TypingDisplayTime = TimeSpan.FromSeconds(2);
private bool _isModified = false;
private bool _namePlateRoleColorsEnabled = false;
public GuiHookService(ILogger<GuiHookService> logger, DalamudUtilService dalamudUtil, MareMediator mediator, MareConfigService configService,
INamePlateGui namePlateGui, IGameConfig gameConfig, IPartyList partyList, PairManager pairManager, ApiController apiController,
IClientState clientState, TypingIndicatorStateService typingStateService)
: base(logger, mediator)
{
_logger = logger;
_dalamudUtil = dalamudUtil;
_configService = configService;
_namePlateGui = namePlateGui;
_gameConfig = gameConfig;
_partyList = partyList;
_pairManager = pairManager;
_apiController = apiController;
_clientState = clientState;
_typingStateService = typingStateService;
_namePlateGui.OnNamePlateUpdate += OnNamePlateUpdate;
_namePlateGui.RequestRedraw();
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => GameSettingsCheck());
Mediator.Subscribe<PairHandlerVisibleMessage>(this, (_) => RequestRedraw());
Mediator.Subscribe<NameplateRedrawMessage>(this, (_) => RequestRedraw());
Mediator.Subscribe<UserTypingStateMessage>(this, (_) => RequestRedraw());
}
public void RequestRedraw(bool force = false)
{
var useColors = _configService.Current.UseNameColors;
var showTyping = _configService.Current.TypingIndicatorShowOnNameplates;
if (!useColors && !showTyping)
{
if (!_isModified && !force)
return;
_isModified = false;
}
else if (!useColors)
{
_isModified = false;
}
_ = Task.Run(async () => {
await _dalamudUtil.RunOnFrameworkThread(() => _namePlateGui.RequestRedraw()).ConfigureAwait(false);
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_namePlateGui.OnNamePlateUpdate -= OnNamePlateUpdate;
_ = Task.Run(async () => {
await _dalamudUtil.RunOnFrameworkThread(() => _namePlateGui.RequestRedraw()).ConfigureAwait(false);
});
}
private void OnNamePlateUpdate(INamePlateUpdateContext context, IReadOnlyList<INamePlateUpdateHandler> handlers)
{
var applyColors = _configService.Current.UseNameColors;
var showTypingIndicator = _configService.Current.TypingIndicatorShowOnNameplates;
if (!applyColors && !showTypingIndicator)
return;
var visibleUsers = _pairManager.GetOnlineUserPairs().Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue);
var visibleUsersIds = visibleUsers.Select(u => (ulong)u.PlayerCharacterId).ToHashSet();
var visibleUsersDict = visibleUsers.ToDictionary(u => (ulong)u.PlayerCharacterId);
var partyMembers = new nint[_partyList.Count];
for (int i = 0; i < _partyList.Count; ++i)
partyMembers[i] = _partyList[i]?.GameObject?.Address ?? nint.MaxValue;
var now = DateTime.UtcNow;
var activeTypers = _typingStateService.GetActiveTypers(TypingDisplayTime);
var selfTypingActive = showTypingIndicator && _typingStateService.TryGetSelfTyping(TypingDisplayTime, out _, out _);
var localPlayerAddress = selfTypingActive ? _clientState.LocalPlayer?.Address ?? nint.Zero : nint.Zero;
foreach (var handler in handlers)
{
if (handler != null && visibleUsersIds.Contains(handler.GameObjectId))
{
if (_namePlateRoleColorsEnabled && partyMembers.Contains(handler.GameObject?.Address ?? nint.MaxValue))
continue;
var pair = visibleUsersDict[handler.GameObjectId];
if (applyColors)
{
var colors = !pair.IsApplicationBlocked ? _configService.Current.NameColors : _configService.Current.BlockedNameColors;
handler.NameParts.TextWrap = (
BuildColorStartSeString(colors),
BuildColorEndSeString(colors)
);
_isModified = true;
}
}
}
}
private void GameSettingsCheck()
{
if (!_gameConfig.TryGet(Dalamud.Game.Config.UiConfigOption.NamePlateSetRoleColor, out bool namePlateRoleColorsEnabled))
return;
if (_namePlateRoleColorsEnabled != namePlateRoleColorsEnabled)
{
_namePlateRoleColorsEnabled = namePlateRoleColorsEnabled;
RequestRedraw(force: true);
}
}
#region Colored SeString
private const byte _colorTypeForeground = 0x13;
private const byte _colorTypeGlow = 0x14;
private static SeString BuildColorStartSeString(DtrEntry.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));
return ssb.Build();
}
private static SeString BuildColorEndSeString(DtrEntry.Colors colors)
{
var ssb = new SeStringBuilder();
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]);
#endregion
}