211 lines
7.3 KiB
C#
211 lines
7.3 KiB
C#
using Dalamud.Game.ClientState.Objects.SubKinds;
|
|
using Dalamud.Game.Gui.ContextMenu;
|
|
using Dalamud.Plugin;
|
|
using Dalamud.Plugin.Services;
|
|
using LightlessSync.LightlessConfiguration;
|
|
using LightlessSync.PlayerData.Pairs;
|
|
using LightlessSync.Utils;
|
|
using LightlessSync.WebAPI;
|
|
using Lumina.Excel.Sheets;
|
|
using Microsoft.Extensions.Hosting;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace LightlessSync.Services;
|
|
|
|
internal class ContextMenuService : IHostedService
|
|
{
|
|
private readonly IContextMenu _contextMenu;
|
|
private readonly IDalamudPluginInterface _pluginInterface;
|
|
private readonly IDataManager _gameData;
|
|
private readonly ILogger<ContextMenuService> _logger;
|
|
private readonly DalamudUtilService _dalamudUtil;
|
|
private readonly IClientState _clientState;
|
|
private readonly PairManager _pairManager;
|
|
private readonly PairRequestService _pairRequestService;
|
|
private readonly ApiController _apiController;
|
|
private readonly IObjectTable _objectTable;
|
|
private readonly LightlessConfigService _configService;
|
|
|
|
public ContextMenuService(
|
|
IContextMenu contextMenu,
|
|
IDalamudPluginInterface pluginInterface,
|
|
IDataManager gameData,
|
|
ILogger<ContextMenuService> logger,
|
|
DalamudUtilService dalamudUtil,
|
|
ApiController apiController,
|
|
IObjectTable objectTable,
|
|
LightlessConfigService configService,
|
|
PairRequestService pairRequestService,
|
|
PairManager pairManager,
|
|
IClientState clientState)
|
|
{
|
|
_contextMenu = contextMenu;
|
|
_pluginInterface = pluginInterface;
|
|
_gameData = gameData;
|
|
_logger = logger;
|
|
_dalamudUtil = dalamudUtil;
|
|
_apiController = apiController;
|
|
_objectTable = objectTable;
|
|
_configService = configService;
|
|
_pairManager = pairManager;
|
|
_pairRequestService = pairRequestService;
|
|
_clientState = clientState;
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
_contextMenu.OnMenuOpened += OnMenuOpened;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public void Enable()
|
|
{
|
|
_contextMenu.OnMenuOpened += OnMenuOpened;
|
|
_logger.LogDebug("Context menu enabled.");
|
|
}
|
|
|
|
public void Disable()
|
|
{
|
|
_contextMenu.OnMenuOpened -= OnMenuOpened;
|
|
_logger.LogDebug("Context menu disabled.");
|
|
}
|
|
|
|
private void OnMenuOpened(IMenuOpenedArgs args)
|
|
{
|
|
|
|
if (!_pluginInterface.UiBuilder.ShouldModifyUi)
|
|
return;
|
|
|
|
if (args.AddonName != null)
|
|
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 == nint.Zero)
|
|
return;
|
|
|
|
//Check if user is directly 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;
|
|
|
|
if (!_configService.Current.EnableRightClickMenus)
|
|
return;
|
|
|
|
args.AddMenuItem(new MenuItem
|
|
{
|
|
Name = "Send Direct Pair Request",
|
|
PrefixChar = 'L',
|
|
UseDefaultPrefix = false,
|
|
PrefixColor = 708,
|
|
OnClicked = async _ => await HandleSelection(args).ConfigureAwait(false)
|
|
});
|
|
}
|
|
|
|
private async Task HandleSelection(IMenuArgs args)
|
|
{
|
|
if (args.Target is not MenuTargetDefault target)
|
|
return;
|
|
|
|
var world = GetWorld(target.TargetHomeWorld.RowId);
|
|
if (!IsWorldValid(world))
|
|
return;
|
|
|
|
try
|
|
{
|
|
IPlayerCharacter? targetData = GetPlayerFromObjectTable(target);
|
|
|
|
if (targetData == null || targetData.Address == nint.Zero)
|
|
{
|
|
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name);
|
|
return;
|
|
}
|
|
|
|
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);
|
|
await _apiController.TryPairWithContentId(receiverCid).ConfigureAwait(false);
|
|
if (!string.IsNullOrWhiteSpace(receiverCid))
|
|
{
|
|
_pairRequestService.RemoveRequest(receiverCid);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error sending pair request.");
|
|
}
|
|
}
|
|
|
|
private HashSet<ulong> VisibleUserIds => [.. _pairManager.DirectPairs
|
|
.Where(u => u.IsVisible && u.PlayerCharacterId != uint.MaxValue)
|
|
.Select(u => (ulong)u.PlayerCharacterId)];
|
|
|
|
private IPlayerCharacter? GetPlayerFromObjectTable(MenuTargetDefault target)
|
|
{
|
|
return _objectTable
|
|
.OfType<IPlayerCharacter>()
|
|
.FirstOrDefault(p =>
|
|
string.Equals(p.Name.TextValue, target.TargetName, StringComparison.OrdinalIgnoreCase) &&
|
|
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
|
}
|
|
|
|
private World GetWorld(uint worldId)
|
|
{
|
|
var sheet = _gameData.GetExcelSheet<World>()!;
|
|
var luminaWorlds = sheet.Where(x =>
|
|
{
|
|
var dc = x.DataCenter.ValueNullable;
|
|
var name = x.Name.ExtractText();
|
|
var internalName = x.InternalName.ExtractText();
|
|
|
|
if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText()))
|
|
return false;
|
|
|
|
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName))
|
|
return false;
|
|
|
|
if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal))
|
|
return false;
|
|
|
|
return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name);
|
|
});
|
|
|
|
return luminaWorlds.FirstOrDefault(x => x.RowId == worldId);
|
|
}
|
|
|
|
private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter);
|
|
|
|
private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF;
|
|
|
|
public bool IsWorldValid(uint worldId) => IsWorldValid(GetWorld(worldId));
|
|
|
|
public static bool IsWorldValid(World world)
|
|
{
|
|
var name = world.Name.ToString();
|
|
return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]);
|
|
}
|
|
}
|