Added debug information regarding minions
This commit is contained in:
@@ -1,22 +1,37 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Dalamud.Plugin.Services;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
using LightlessSync.PlayerData.Factories;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.ActorTracking;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace LightlessSync.PlayerData.Handlers;
|
||||
|
||||
internal sealed class OwnedObjectHandler
|
||||
{
|
||||
internal readonly record struct OwnedResolveDebug(
|
||||
DateTime? ResolvedAtUtc,
|
||||
nint Address,
|
||||
ushort? ObjectIndex,
|
||||
string Stage,
|
||||
string? FailureReason)
|
||||
{
|
||||
public string? AddressHex => Address == nint.Zero ? null : $"0x{Address:X}";
|
||||
public static OwnedResolveDebug Empty => new(null, nint.Zero, null, string.Empty, null);
|
||||
}
|
||||
|
||||
private OwnedResolveDebug _minionResolveDebug = OwnedResolveDebug.Empty;
|
||||
public OwnedResolveDebug MinionResolveDebug => _minionResolveDebug;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly GameObjectHandlerFactory _handlerFactory;
|
||||
private readonly IpcManager _ipc;
|
||||
private readonly ActorObjectService _actorObjectService;
|
||||
|
||||
private IObjectTable _objectTable;
|
||||
private const int _fullyLoadedTimeoutMsPlayer = 30000;
|
||||
private const int _fullyLoadedTimeoutMsOther = 5000;
|
||||
|
||||
@@ -25,13 +40,15 @@ internal sealed class OwnedObjectHandler
|
||||
DalamudUtilService dalamudUtil,
|
||||
GameObjectHandlerFactory handlerFactory,
|
||||
IpcManager ipc,
|
||||
ActorObjectService actorObjectService)
|
||||
ActorObjectService actorObjectService,
|
||||
IObjectTable objectTable)
|
||||
{
|
||||
_logger = logger;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_handlerFactory = handlerFactory;
|
||||
_ipc = ipc;
|
||||
_actorObjectService = actorObjectService;
|
||||
_objectTable = objectTable;
|
||||
}
|
||||
|
||||
public async Task<bool> ApplyAsync(
|
||||
@@ -204,10 +221,29 @@ internal sealed class OwnedObjectHandler
|
||||
|
||||
private async Task<GameObjectHandler?> CreateHandlerAsync(ObjectKind kind, GameObjectHandler playerHandler, CancellationToken token)
|
||||
{
|
||||
void SetMinionDebug(string stage, string? failure, nint addr = default, ushort? objIndex = null)
|
||||
{
|
||||
if (kind != ObjectKind.MinionOrMount)
|
||||
return;
|
||||
|
||||
_minionResolveDebug = new OwnedResolveDebug(
|
||||
DateTime.UtcNow,
|
||||
addr,
|
||||
objIndex,
|
||||
stage,
|
||||
failure);
|
||||
}
|
||||
|
||||
if (kind == ObjectKind.Player)
|
||||
return playerHandler;
|
||||
|
||||
var playerPtr = playerHandler.Address;
|
||||
if (playerPtr == nint.Zero)
|
||||
{
|
||||
SetMinionDebug("player_ptr_zero", "playerHandler.Address == 0");
|
||||
return null;
|
||||
}
|
||||
|
||||
nint ownedPtr = kind switch
|
||||
{
|
||||
ObjectKind.Companion => await _dalamudUtil.GetCompanionAsync(playerPtr).ConfigureAwait(false),
|
||||
@@ -216,12 +252,115 @@ internal sealed class OwnedObjectHandler
|
||||
_ => nint.Zero
|
||||
};
|
||||
|
||||
if (ownedPtr == nint.Zero)
|
||||
return null;
|
||||
var stage = ownedPtr != nint.Zero ? "direct" : "direct_miss";
|
||||
|
||||
return await _handlerFactory.Create(kind, () => ownedPtr, isWatched: false).ConfigureAwait(false);
|
||||
if (ownedPtr == nint.Zero)
|
||||
{
|
||||
var ownerEntityId = playerHandler.EntityId;
|
||||
if (ownerEntityId == 0 || ownerEntityId == uint.MaxValue)
|
||||
{
|
||||
ownerEntityId = await _dalamudUtil.RunOnFrameworkThread(() => ReadEntityIdUnsafe(playerPtr))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (ownerEntityId != 0 && ownerEntityId != uint.MaxValue)
|
||||
{
|
||||
ownedPtr = await _dalamudUtil.RunOnFrameworkThread(() => FindOwnedByOwnerIdUnsafe(kind, ownerEntityId))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
stage = ownedPtr != nint.Zero ? "owner_scan" : "owner_scan_miss";
|
||||
}
|
||||
else
|
||||
{
|
||||
stage = "owner_id_unavailable";
|
||||
}
|
||||
}
|
||||
|
||||
if (ownedPtr == nint.Zero)
|
||||
{
|
||||
SetMinionDebug(stage, "ownedPtr == 0");
|
||||
return null;
|
||||
}
|
||||
|
||||
var handler = await _handlerFactory.Create(kind, () => ownedPtr, isWatched: false).ConfigureAwait(false);
|
||||
if (handler is null || handler.Address == nint.Zero)
|
||||
{
|
||||
SetMinionDebug(stage, "handlerFactory returned null/zero", ownedPtr);
|
||||
return null;
|
||||
}
|
||||
|
||||
ushort? objIndex = await _dalamudUtil.RunOnFrameworkThread(() => handler.GetGameObject()?.ObjectIndex)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
SetMinionDebug(stage, null, handler.Address, objIndex);
|
||||
return handler;
|
||||
}
|
||||
|
||||
private static unsafe uint ReadEntityIdUnsafe(nint playerPtr)
|
||||
{
|
||||
if (playerPtr == nint.Zero) return 0;
|
||||
var ch = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)playerPtr;
|
||||
return ch != null ? ch->EntityId : 0;
|
||||
}
|
||||
|
||||
private unsafe nint FindOwnedByOwnerIdUnsafe(ObjectKind kind, uint ownerEntityId)
|
||||
{
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj is null || obj.Address == nint.Zero)
|
||||
continue;
|
||||
|
||||
var addr = obj.Address;
|
||||
var go = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)addr;
|
||||
if (go == null)
|
||||
continue;
|
||||
|
||||
var ok = kind switch
|
||||
{
|
||||
ObjectKind.MinionOrMount =>
|
||||
obj.ObjectKind is Dalamud.Game.ClientState.Objects.Enums.ObjectKind.MountType
|
||||
or Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Companion,
|
||||
|
||||
ObjectKind.Pet =>
|
||||
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc
|
||||
&& go->BattleNpcSubKind == FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind.Pet,
|
||||
|
||||
ObjectKind.Companion =>
|
||||
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.BattleNpc
|
||||
&& go->BattleNpcSubKind == FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind.Buddy,
|
||||
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
var resolvedOwner = ResolveOwnerIdUnsafe(go);
|
||||
if (resolvedOwner == ownerEntityId)
|
||||
return addr;
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
private static unsafe uint ResolveOwnerIdUnsafe(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject)
|
||||
{
|
||||
if (gameObject == null) return 0;
|
||||
|
||||
if (gameObject->OwnerId != 0)
|
||||
return gameObject->OwnerId;
|
||||
|
||||
var character = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)gameObject;
|
||||
if (character == null) return 0;
|
||||
|
||||
if (character->CompanionOwnerId != 0)
|
||||
return character->CompanionOwnerId;
|
||||
|
||||
var parent = character->GetParentCharacter();
|
||||
return parent != null ? parent->EntityId : 0;
|
||||
}
|
||||
|
||||
|
||||
private async Task ApplyCustomizeAsync(nint address, string customizeData, ObjectKind kind, Dictionary<ObjectKind, Guid?> customizeIds)
|
||||
{
|
||||
customizeIds[kind] = await _ipc.CustomizePlus.SetBodyScaleAsync(address, customizeData).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user