boom
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.Interop;
|
||||
@@ -10,6 +9,8 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
|
||||
using IPlayerCharacter = Dalamud.Game.ClientState.Objects.SubKinds.IPlayerCharacter;
|
||||
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||
using LightlessObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||
|
||||
@@ -41,7 +42,6 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
private readonly ConcurrentDictionary<string, ActorDescriptor> _actorsByHash = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, ConcurrentDictionary<nint, ActorDescriptor>> _actorsByName = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<nint, byte> _pendingHashResolutions = new();
|
||||
private readonly OwnedObjectTracker _ownedTracker = new();
|
||||
private ActorSnapshot _snapshot = ActorSnapshot.Empty;
|
||||
private GposeSnapshot _gposeSnapshot = GposeSnapshot.Empty;
|
||||
|
||||
@@ -151,15 +151,16 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
public bool TryGetOwnedObjectByIndex(ushort objectIndex, out LightlessObjectKind ownedKind)
|
||||
{
|
||||
ownedKind = default;
|
||||
var ownedSnapshot = OwnedObjects;
|
||||
foreach (var (address, kind) in ownedSnapshot)
|
||||
var ownedDescriptors = OwnedDescriptors;
|
||||
for (var i = 0; i < ownedDescriptors.Count; i++)
|
||||
{
|
||||
if (!TryGetDescriptor(address, out var descriptor))
|
||||
var descriptor = ownedDescriptors[i];
|
||||
if (descriptor.ObjectIndex != objectIndex)
|
||||
continue;
|
||||
|
||||
if (descriptor.ObjectIndex == objectIndex)
|
||||
if (descriptor.OwnedKind is { } resolvedKind)
|
||||
{
|
||||
ownedKind = kind;
|
||||
ownedKind = resolvedKind;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -316,7 +317,6 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
_actorsByHash.Clear();
|
||||
_actorsByName.Clear();
|
||||
_pendingHashResolutions.Clear();
|
||||
_ownedTracker.Reset();
|
||||
Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
|
||||
Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty);
|
||||
return Task.CompletedTask;
|
||||
@@ -481,50 +481,196 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
return (isLocalPlayer ? LightlessObjectKind.Player : null, entityId);
|
||||
}
|
||||
|
||||
if (isLocalPlayer)
|
||||
{
|
||||
var entityId = ((Character*)gameObject)->EntityId;
|
||||
return (LightlessObjectKind.Player, entityId);
|
||||
}
|
||||
var ownerId = ResolveOwnerId(gameObject);
|
||||
var localPlayerAddress = _objectTable.LocalPlayer?.Address ?? nint.Zero;
|
||||
if (localPlayerAddress == nint.Zero)
|
||||
return (null, ownerId);
|
||||
|
||||
if (_objectTable.LocalPlayer is not { } localPlayer)
|
||||
return (null, 0);
|
||||
var localEntityId = ((Character*)localPlayerAddress)->EntityId;
|
||||
if (localEntityId == 0)
|
||||
return (null, ownerId);
|
||||
|
||||
var ownerId = gameObject->OwnerId;
|
||||
if (ownerId == 0)
|
||||
if (objectKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
||||
{
|
||||
var character = (Character*)gameObject;
|
||||
if (character != null)
|
||||
var expectedMinionOrMount = GetMinionOrMountAddress(localPlayerAddress, localEntityId);
|
||||
if (expectedMinionOrMount != nint.Zero && (nint)gameObject == expectedMinionOrMount)
|
||||
{
|
||||
ownerId = character->CompanionOwnerId;
|
||||
if (ownerId == 0)
|
||||
{
|
||||
var parent = character->GetParentCharacter();
|
||||
if (parent != null)
|
||||
{
|
||||
ownerId = parent->EntityId;
|
||||
}
|
||||
}
|
||||
var resolvedOwner = ownerId != 0 ? ownerId : localEntityId;
|
||||
return (LightlessObjectKind.MinionOrMount, resolvedOwner);
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerId == 0 || ownerId != localPlayer.EntityId)
|
||||
if (objectKind != DalamudObjectKind.BattleNpc)
|
||||
return (null, ownerId);
|
||||
|
||||
var ownedKind = objectKind switch
|
||||
{
|
||||
DalamudObjectKind.MountType => LightlessObjectKind.MinionOrMount,
|
||||
DalamudObjectKind.Companion => LightlessObjectKind.MinionOrMount,
|
||||
DalamudObjectKind.BattleNpc => gameObject->BattleNpcSubKind switch
|
||||
{
|
||||
BattleNpcSubKind.Buddy => LightlessObjectKind.Companion,
|
||||
BattleNpcSubKind.Pet => LightlessObjectKind.Pet,
|
||||
_ => (LightlessObjectKind?)null,
|
||||
},
|
||||
_ => (LightlessObjectKind?)null,
|
||||
};
|
||||
if (ownerId != localEntityId)
|
||||
return (null, ownerId);
|
||||
|
||||
return (ownedKind, ownerId);
|
||||
var expectedPet = GetPetAddress(localPlayerAddress, localEntityId);
|
||||
if (expectedPet != nint.Zero && (nint)gameObject == expectedPet)
|
||||
return (LightlessObjectKind.Pet, ownerId);
|
||||
|
||||
var expectedCompanion = GetCompanionAddress(localPlayerAddress, localEntityId);
|
||||
if (expectedCompanion != nint.Zero && (nint)gameObject == expectedCompanion)
|
||||
return (LightlessObjectKind.Companion, ownerId);
|
||||
|
||||
return (null, ownerId);
|
||||
}
|
||||
|
||||
private unsafe nint GetMinionOrMountAddress(nint localPlayerAddress, uint ownerEntityId)
|
||||
{
|
||||
if (localPlayerAddress == nint.Zero)
|
||||
return nint.Zero;
|
||||
|
||||
var playerObject = (GameObject*)localPlayerAddress;
|
||||
var candidateAddress = _objectTable.GetObjectAddress(playerObject->ObjectIndex + 1);
|
||||
if (candidateAddress != nint.Zero)
|
||||
{
|
||||
var candidate = (GameObject*)candidateAddress;
|
||||
var candidateKind = (DalamudObjectKind)candidate->ObjectKind;
|
||||
if (candidateKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
||||
{
|
||||
if (ownerEntityId == 0 || ResolveOwnerId(candidate) == ownerEntityId)
|
||||
return candidateAddress;
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerEntityId == 0)
|
||||
return candidateAddress;
|
||||
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj is null || obj.Address == nint.Zero || obj.Address == localPlayerAddress)
|
||||
continue;
|
||||
|
||||
if (obj.ObjectKind is not (DalamudObjectKind.MountType or DalamudObjectKind.Companion))
|
||||
continue;
|
||||
|
||||
var candidate = (GameObject*)obj.Address;
|
||||
if (ResolveOwnerId(candidate) == ownerEntityId)
|
||||
return obj.Address;
|
||||
}
|
||||
|
||||
return candidateAddress;
|
||||
}
|
||||
|
||||
private unsafe nint GetPetAddress(nint localPlayerAddress, uint ownerEntityId)
|
||||
{
|
||||
if (localPlayerAddress == nint.Zero || ownerEntityId == 0)
|
||||
return nint.Zero;
|
||||
|
||||
var manager = CharacterManager.Instance();
|
||||
if (manager != null)
|
||||
{
|
||||
var candidate = (nint)manager->LookupPetByOwnerObject((BattleChara*)localPlayerAddress);
|
||||
if (candidate != nint.Zero)
|
||||
{
|
||||
var candidateObj = (GameObject*)candidate;
|
||||
if (IsPetMatch(candidateObj, ownerEntityId))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj is null || obj.Address == nint.Zero || obj.Address == localPlayerAddress)
|
||||
continue;
|
||||
|
||||
if (obj.ObjectKind != DalamudObjectKind.BattleNpc)
|
||||
continue;
|
||||
|
||||
var candidate = (GameObject*)obj.Address;
|
||||
if (candidate->BattleNpcSubKind != BattleNpcSubKind.Pet)
|
||||
continue;
|
||||
|
||||
if (ResolveOwnerId(candidate) == ownerEntityId)
|
||||
return obj.Address;
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
private unsafe nint GetCompanionAddress(nint localPlayerAddress, uint ownerEntityId)
|
||||
{
|
||||
if (localPlayerAddress == nint.Zero || ownerEntityId == 0)
|
||||
return nint.Zero;
|
||||
|
||||
var manager = CharacterManager.Instance();
|
||||
if (manager != null)
|
||||
{
|
||||
var candidate = (nint)manager->LookupBuddyByOwnerObject((BattleChara*)localPlayerAddress);
|
||||
if (candidate != nint.Zero)
|
||||
{
|
||||
var candidateObj = (GameObject*)candidate;
|
||||
if (IsCompanionMatch(candidateObj, ownerEntityId))
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj is null || obj.Address == nint.Zero || obj.Address == localPlayerAddress)
|
||||
continue;
|
||||
|
||||
if (obj.ObjectKind != DalamudObjectKind.BattleNpc)
|
||||
continue;
|
||||
|
||||
var candidate = (GameObject*)obj.Address;
|
||||
if (candidate->BattleNpcSubKind != BattleNpcSubKind.Buddy)
|
||||
continue;
|
||||
|
||||
if (ResolveOwnerId(candidate) == ownerEntityId)
|
||||
return obj.Address;
|
||||
}
|
||||
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
private static unsafe bool IsPetMatch(GameObject* candidate, uint ownerEntityId)
|
||||
{
|
||||
if (candidate == null)
|
||||
return false;
|
||||
|
||||
if ((DalamudObjectKind)candidate->ObjectKind != DalamudObjectKind.BattleNpc)
|
||||
return false;
|
||||
|
||||
if (candidate->BattleNpcSubKind != BattleNpcSubKind.Pet)
|
||||
return false;
|
||||
|
||||
return ResolveOwnerId(candidate) == ownerEntityId;
|
||||
}
|
||||
|
||||
private static unsafe bool IsCompanionMatch(GameObject* candidate, uint ownerEntityId)
|
||||
{
|
||||
if (candidate == null)
|
||||
return false;
|
||||
|
||||
if ((DalamudObjectKind)candidate->ObjectKind != DalamudObjectKind.BattleNpc)
|
||||
return false;
|
||||
|
||||
if (candidate->BattleNpcSubKind != BattleNpcSubKind.Buddy)
|
||||
return false;
|
||||
|
||||
return ResolveOwnerId(candidate) == ownerEntityId;
|
||||
}
|
||||
|
||||
private static unsafe uint ResolveOwnerId(GameObject* gameObject)
|
||||
{
|
||||
if (gameObject == null)
|
||||
return 0;
|
||||
|
||||
if (gameObject->OwnerId != 0)
|
||||
return gameObject->OwnerId;
|
||||
|
||||
var 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 void UntrackGameObject(nint address)
|
||||
@@ -618,11 +764,8 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
private void ReplaceDescriptor(ActorDescriptor existing, ActorDescriptor updated)
|
||||
{
|
||||
RemoveDescriptorFromIndexes(existing);
|
||||
_ownedTracker.OnDescriptorRemoved(existing);
|
||||
|
||||
_activePlayers[updated.Address] = updated;
|
||||
IndexDescriptor(updated);
|
||||
_ownedTracker.OnDescriptorAdded(updated);
|
||||
UpdatePendingHashResolutions(updated);
|
||||
PublishSnapshot();
|
||||
}
|
||||
@@ -690,7 +833,6 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
{
|
||||
_activePlayers[descriptor.Address] = descriptor;
|
||||
IndexDescriptor(descriptor);
|
||||
_ownedTracker.OnDescriptorAdded(descriptor);
|
||||
UpdatePendingHashResolutions(descriptor);
|
||||
PublishSnapshot();
|
||||
}
|
||||
@@ -698,7 +840,6 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
private void RemoveDescriptor(ActorDescriptor descriptor)
|
||||
{
|
||||
RemoveDescriptorFromIndexes(descriptor);
|
||||
_ownedTracker.OnDescriptorRemoved(descriptor);
|
||||
_pendingHashResolutions.TryRemove(descriptor.Address, out _);
|
||||
PublishSnapshot();
|
||||
}
|
||||
@@ -722,17 +863,90 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
|
||||
private void PublishSnapshot()
|
||||
{
|
||||
var playerDescriptors = _activePlayers.Values
|
||||
.Where(descriptor => descriptor.ObjectKind == DalamudObjectKind.Player)
|
||||
.ToArray();
|
||||
var ownedDescriptors = _activePlayers.Values
|
||||
.Where(descriptor => descriptor.OwnedKind is not null)
|
||||
.ToArray();
|
||||
var playerAddresses = new nint[playerDescriptors.Length];
|
||||
for (var i = 0; i < playerDescriptors.Length; i++)
|
||||
playerAddresses[i] = playerDescriptors[i].Address;
|
||||
var descriptors = _activePlayers.Values.ToArray();
|
||||
var playerCount = 0;
|
||||
var ownedCount = 0;
|
||||
var companionCount = 0;
|
||||
|
||||
var ownedSnapshot = _ownedTracker.CreateSnapshot();
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Player)
|
||||
playerCount++;
|
||||
|
||||
if (descriptor.OwnedKind is not null)
|
||||
ownedCount++;
|
||||
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Companion)
|
||||
companionCount++;
|
||||
}
|
||||
|
||||
var playerDescriptors = new ActorDescriptor[playerCount];
|
||||
var ownedDescriptors = new ActorDescriptor[ownedCount];
|
||||
var playerAddresses = new nint[playerCount];
|
||||
var renderedCompanions = new nint[companionCount];
|
||||
var ownedAddresses = new nint[ownedCount];
|
||||
var ownedMap = new Dictionary<nint, LightlessObjectKind>(ownedCount);
|
||||
nint localPlayer = nint.Zero;
|
||||
nint localPet = nint.Zero;
|
||||
nint localMinionOrMount = nint.Zero;
|
||||
nint localCompanion = nint.Zero;
|
||||
|
||||
var playerIndex = 0;
|
||||
var ownedIndex = 0;
|
||||
var companionIndex = 0;
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Player)
|
||||
{
|
||||
playerDescriptors[playerIndex] = descriptor;
|
||||
playerAddresses[playerIndex] = descriptor.Address;
|
||||
playerIndex++;
|
||||
}
|
||||
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Companion)
|
||||
{
|
||||
renderedCompanions[companionIndex] = descriptor.Address;
|
||||
companionIndex++;
|
||||
}
|
||||
|
||||
if (descriptor.OwnedKind is not { } ownedKind)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ownedDescriptors[ownedIndex] = descriptor;
|
||||
ownedAddresses[ownedIndex] = descriptor.Address;
|
||||
ownedMap[descriptor.Address] = ownedKind;
|
||||
|
||||
switch (ownedKind)
|
||||
{
|
||||
case LightlessObjectKind.Player:
|
||||
localPlayer = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.Pet:
|
||||
localPet = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.MinionOrMount:
|
||||
localMinionOrMount = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.Companion:
|
||||
localCompanion = descriptor.Address;
|
||||
break;
|
||||
}
|
||||
|
||||
ownedIndex++;
|
||||
}
|
||||
|
||||
var ownedSnapshot = new OwnedObjectSnapshot(
|
||||
playerAddresses,
|
||||
renderedCompanions,
|
||||
ownedAddresses,
|
||||
ownedMap,
|
||||
localPlayer,
|
||||
localPet,
|
||||
localMinionOrMount,
|
||||
localCompanion);
|
||||
var nextGeneration = Snapshot.Generation + 1;
|
||||
var snapshot = new ActorSnapshot(playerDescriptors, ownedDescriptors, playerAddresses, ownedSnapshot, nextGeneration);
|
||||
Volatile.Write(ref _snapshot, snapshot);
|
||||
@@ -955,109 +1169,6 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed class OwnedObjectTracker
|
||||
{
|
||||
private readonly HashSet<nint> _renderedPlayers = new();
|
||||
private readonly HashSet<nint> _renderedCompanions = new();
|
||||
private readonly Dictionary<nint, LightlessObjectKind> _ownedObjects = new();
|
||||
private nint _localPlayerAddress = nint.Zero;
|
||||
private nint _localPetAddress = nint.Zero;
|
||||
private nint _localMinionMountAddress = nint.Zero;
|
||||
private nint _localCompanionAddress = nint.Zero;
|
||||
|
||||
public void OnDescriptorAdded(ActorDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Player)
|
||||
{
|
||||
_renderedPlayers.Add(descriptor.Address);
|
||||
if (descriptor.IsLocalPlayer)
|
||||
_localPlayerAddress = descriptor.Address;
|
||||
}
|
||||
else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
|
||||
{
|
||||
_renderedCompanions.Add(descriptor.Address);
|
||||
}
|
||||
|
||||
if (descriptor.OwnedKind is { } ownedKind)
|
||||
{
|
||||
_ownedObjects[descriptor.Address] = ownedKind;
|
||||
switch (ownedKind)
|
||||
{
|
||||
case LightlessObjectKind.Player:
|
||||
_localPlayerAddress = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.Pet:
|
||||
_localPetAddress = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.MinionOrMount:
|
||||
_localMinionMountAddress = descriptor.Address;
|
||||
break;
|
||||
case LightlessObjectKind.Companion:
|
||||
_localCompanionAddress = descriptor.Address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDescriptorRemoved(ActorDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.ObjectKind == DalamudObjectKind.Player)
|
||||
{
|
||||
_renderedPlayers.Remove(descriptor.Address);
|
||||
if (descriptor.IsLocalPlayer && _localPlayerAddress == descriptor.Address)
|
||||
_localPlayerAddress = nint.Zero;
|
||||
}
|
||||
else if (descriptor.ObjectKind == DalamudObjectKind.Companion)
|
||||
{
|
||||
_renderedCompanions.Remove(descriptor.Address);
|
||||
if (_localCompanionAddress == descriptor.Address)
|
||||
_localCompanionAddress = nint.Zero;
|
||||
}
|
||||
|
||||
if (descriptor.OwnedKind is { } ownedKind)
|
||||
{
|
||||
_ownedObjects.Remove(descriptor.Address);
|
||||
switch (ownedKind)
|
||||
{
|
||||
case LightlessObjectKind.Player when _localPlayerAddress == descriptor.Address:
|
||||
_localPlayerAddress = nint.Zero;
|
||||
break;
|
||||
case LightlessObjectKind.Pet when _localPetAddress == descriptor.Address:
|
||||
_localPetAddress = nint.Zero;
|
||||
break;
|
||||
case LightlessObjectKind.MinionOrMount when _localMinionMountAddress == descriptor.Address:
|
||||
_localMinionMountAddress = nint.Zero;
|
||||
break;
|
||||
case LightlessObjectKind.Companion when _localCompanionAddress == descriptor.Address:
|
||||
_localCompanionAddress = nint.Zero;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OwnedObjectSnapshot CreateSnapshot()
|
||||
=> new(
|
||||
_renderedPlayers.ToArray(),
|
||||
_renderedCompanions.ToArray(),
|
||||
_ownedObjects.Keys.ToArray(),
|
||||
new Dictionary<nint, LightlessObjectKind>(_ownedObjects),
|
||||
_localPlayerAddress,
|
||||
_localPetAddress,
|
||||
_localMinionMountAddress,
|
||||
_localCompanionAddress);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_renderedPlayers.Clear();
|
||||
_renderedCompanions.Clear();
|
||||
_ownedObjects.Clear();
|
||||
_localPlayerAddress = nint.Zero;
|
||||
_localPetAddress = nint.Zero;
|
||||
_localMinionMountAddress = nint.Zero;
|
||||
_localCompanionAddress = nint.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record OwnedObjectSnapshot(
|
||||
IReadOnlyList<nint> RenderedPlayers,
|
||||
IReadOnlyList<nint> RenderedCompanions,
|
||||
|
||||
Reference in New Issue
Block a user