Removed unsafe handling of game object and owned object
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace LightlessSync.PlayerData.Handlers;
|
||||
|
||||
public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighPriorityMediatorSubscriber
|
||||
{
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly IObjectTable _objectTable;
|
||||
private readonly Func<IntPtr> _getAddress;
|
||||
private readonly bool _isOwnedObject;
|
||||
private readonly PerformanceCollectorService _performanceCollector;
|
||||
@@ -24,7 +23,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
private CancellationTokenSource _zoningCts = new();
|
||||
|
||||
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector,
|
||||
LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, bool ownedObject = true) : base(logger, mediator)
|
||||
LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, IObjectTable objectTable, bool ownedObject = true) : base(logger, mediator)
|
||||
{
|
||||
_performanceCollector = performanceCollector;
|
||||
ObjectKind = objectKind;
|
||||
@@ -82,6 +81,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
});
|
||||
|
||||
Mediator.Publish(new GameObjectHandlerCreatedMessage(this, _isOwnedObject));
|
||||
_objectTable = objectTable;
|
||||
|
||||
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult();
|
||||
}
|
||||
@@ -110,14 +110,14 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
private ushort[] MainHandData { get; set; } = new ushort[3];
|
||||
private ushort[] OffHandData { get; set; } = new ushort[3];
|
||||
|
||||
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.ICharacter> act, CancellationToken token)
|
||||
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<ICharacter> act, CancellationToken token)
|
||||
{
|
||||
while (await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
EnsureLatestObjectState();
|
||||
if (CurrentDrawCondition != DrawCondition.None) return true;
|
||||
var gameObj = _dalamudUtil.CreateGameObject(Address);
|
||||
if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara)
|
||||
if (gameObj is ICharacter chara)
|
||||
{
|
||||
act.Invoke(chara);
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
}
|
||||
}
|
||||
|
||||
public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject()
|
||||
public IGameObject? GetGameObject()
|
||||
{
|
||||
return _dalamudUtil.CreateGameObject(Address);
|
||||
}
|
||||
@@ -169,9 +169,22 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})";
|
||||
}
|
||||
|
||||
private IGameObject? TryGetObjectByAddress(nint address)
|
||||
{
|
||||
if (address == nint.Zero) return null;
|
||||
|
||||
foreach (var obj in _objectTable)
|
||||
{
|
||||
if (obj is null) continue;
|
||||
if (obj.Address == address)
|
||||
return obj;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true);
|
||||
|
||||
private unsafe void CheckAndUpdateObject(bool allowPublish)
|
||||
private void CheckAndUpdateObject(bool allowPublish)
|
||||
{
|
||||
var prevAddr = Address;
|
||||
var prevDrawObj = DrawObjectAddress;
|
||||
@@ -179,127 +192,118 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
Address = _getAddress();
|
||||
|
||||
if (Address != IntPtr.Zero)
|
||||
{
|
||||
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
|
||||
DrawObjectAddress = (IntPtr)gameObject->DrawObject;
|
||||
EntityId = gameObject->EntityId;
|
||||
IGameObject? obj = null;
|
||||
ICharacter? chara = null;
|
||||
|
||||
var chara = (Character*)Address;
|
||||
nameString = chara->GameObject.NameString;
|
||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||
Name = nameString;
|
||||
if (Address != nint.Zero)
|
||||
{
|
||||
obj = TryGetObjectByAddress(Address);
|
||||
|
||||
if (obj is not null)
|
||||
{
|
||||
EntityId = obj.EntityId;
|
||||
|
||||
DrawObjectAddress = Address;
|
||||
|
||||
nameString = obj.Name.TextValue ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||
Name = nameString;
|
||||
|
||||
chara = obj as ICharacter;
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawObjectAddress = nint.Zero;
|
||||
EntityId = uint.MaxValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawObjectAddress = IntPtr.Zero;
|
||||
DrawObjectAddress = nint.Zero;
|
||||
EntityId = uint.MaxValue;
|
||||
}
|
||||
|
||||
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
||||
CurrentDrawCondition = IsBeingDrawnSafe(obj, chara);
|
||||
|
||||
if (_haltProcessing || !allowPublish) return;
|
||||
|
||||
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
||||
bool addrDiff = Address != prevAddr;
|
||||
|
||||
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
||||
bool nameChange = false;
|
||||
if (nameString is not null)
|
||||
{
|
||||
var chara = (Character*)Address;
|
||||
var drawObj = (DrawObject*)DrawObjectAddress;
|
||||
var objType = drawObj->Object.GetObjectType();
|
||||
var isHuman = objType == ObjectType.CharacterBase
|
||||
&& ((CharacterBase*)drawObj)->GetModelType() == CharacterBase.ModelType.Human;
|
||||
|
||||
nameString ??= ((Character*)Address)->GameObject.NameString;
|
||||
var nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
|
||||
nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
|
||||
if (nameChange) Name = nameString;
|
||||
}
|
||||
|
||||
bool equipDiff = false;
|
||||
bool customizeDiff = false;
|
||||
|
||||
if (isHuman)
|
||||
if (chara is not null)
|
||||
{
|
||||
var classJob = chara.ClassJob.RowId;
|
||||
if (classJob != _classJob)
|
||||
{
|
||||
var classJob = chara->CharacterData.ClassJob;
|
||||
if (classJob != _classJob)
|
||||
{
|
||||
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
||||
_classJob = classJob;
|
||||
Mediator.Publish(new ClassJobChangedMessage(this));
|
||||
}
|
||||
|
||||
equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)drawObj)->Head);
|
||||
|
||||
ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand);
|
||||
ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand);
|
||||
equipDiff |= CompareAndUpdateMainHand((Weapon*)mh.DrawObject);
|
||||
equipDiff |= CompareAndUpdateOffHand((Weapon*)oh.DrawObject);
|
||||
|
||||
if (equipDiff)
|
||||
Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, equipDiff);
|
||||
}
|
||||
else
|
||||
{
|
||||
equipDiff = CompareAndUpdateEquipByteData((byte*)Unsafe.AsPointer(ref chara->DrawData.EquipmentModelIds[0]));
|
||||
if (equipDiff)
|
||||
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
|
||||
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
||||
_classJob = (byte)classJob;
|
||||
Mediator.Publish(new ClassJobChangedMessage(this));
|
||||
}
|
||||
|
||||
if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self
|
||||
customizeDiff = CompareAndUpdateCustomizeData(chara.Customize);
|
||||
|
||||
if (_isOwnedObject && ObjectKind == ObjectKind.Player && chara.Customize.Length > (int)CustomizeIndex.Tribe)
|
||||
{
|
||||
Logger.LogTrace("[{this}] Changed", this);
|
||||
return;
|
||||
}
|
||||
var gender = chara.Customize[(int)CustomizeIndex.Gender];
|
||||
var raceId = chara.Customize[(int)CustomizeIndex.Race];
|
||||
var tribeId = chara.Customize[(int)CustomizeIndex.Tribe];
|
||||
|
||||
bool customizeDiff = false;
|
||||
|
||||
if (isHuman)
|
||||
{
|
||||
var gender = ((Human*)drawObj)->Customize.Sex;
|
||||
var raceId = ((Human*)drawObj)->Customize.Race;
|
||||
var tribeId = ((Human*)drawObj)->Customize.Tribe;
|
||||
|
||||
if (_isOwnedObject && ObjectKind == ObjectKind.Player
|
||||
&& (gender != Gender || raceId != RaceId || tribeId != TribeId))
|
||||
if (gender != Gender || raceId != RaceId || tribeId != TribeId)
|
||||
{
|
||||
Mediator.Publish(new CensusUpdateMessage(gender, raceId, tribeId));
|
||||
Gender = gender;
|
||||
RaceId = raceId;
|
||||
TribeId = tribeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->Customize.Data);
|
||||
if (customizeDiff)
|
||||
Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff);
|
||||
}
|
||||
else
|
||||
{
|
||||
customizeDiff = CompareAndUpdateCustomizeData(chara->DrawData.CustomizeData.Data);
|
||||
if (customizeDiff)
|
||||
Logger.LogTrace("Checking [{this}] customize data from game obj, result: {diff}", this, equipDiff);
|
||||
}
|
||||
|
||||
if ((addrDiff || drawObjDiff || equipDiff || customizeDiff || nameChange) && _isOwnedObject)
|
||||
{
|
||||
Logger.LogDebug("[{this}] Changed, Sending CreateCacheObjectMessage", this);
|
||||
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||
}
|
||||
if ((addrDiff || drawObjDiff || customizeDiff || nameChange) && _isOwnedObject)
|
||||
{
|
||||
Logger.LogDebug("[{this}] Changed, Sending CreateCacheObjectMessage", this);
|
||||
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||
}
|
||||
else if (addrDiff || drawObjDiff)
|
||||
{
|
||||
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||
if (Address == nint.Zero)
|
||||
CurrentDrawCondition = DrawCondition.ObjectZero;
|
||||
else if (DrawObjectAddress == nint.Zero)
|
||||
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||
|
||||
Logger.LogTrace("[{this}] Changed", this);
|
||||
|
||||
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
||||
{
|
||||
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
||||
private DrawCondition IsBeingDrawnSafe(IGameObject? obj, ICharacter? chara)
|
||||
{
|
||||
if (Address == nint.Zero) return DrawCondition.ObjectZero;
|
||||
if (obj is null) return DrawCondition.DrawObjectZero;
|
||||
|
||||
if (chara is not null && (chara.Customize is null || chara.Customize.Length == 0))
|
||||
return DrawCondition.DrawObjectZero;
|
||||
|
||||
return DrawCondition.None;
|
||||
}
|
||||
|
||||
private bool CompareAndUpdateCustomizeData(ReadOnlySpan<byte> customizeData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
|
||||
for (int i = 0; i < customizeData.Length; i++)
|
||||
var len = Math.Min(customizeData.Length, CustomizeData.Length);
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
var data = customizeData[i];
|
||||
if (CustomizeData[i] != data)
|
||||
@@ -312,48 +316,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateEquipByteData(byte* equipSlotData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||
{
|
||||
var data = equipSlotData[i];
|
||||
if (EquipSlotData[i] != data)
|
||||
{
|
||||
EquipSlotData[i] = data;
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
|
||||
{
|
||||
if ((nint)weapon == nint.Zero) return false;
|
||||
bool hasChanges = false;
|
||||
hasChanges |= weapon->ModelSetId != MainHandData[0];
|
||||
MainHandData[0] = weapon->ModelSetId;
|
||||
hasChanges |= weapon->Variant != MainHandData[1];
|
||||
MainHandData[1] = weapon->Variant;
|
||||
hasChanges |= weapon->SecondaryId != MainHandData[2];
|
||||
MainHandData[2] = weapon->SecondaryId;
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
|
||||
{
|
||||
if ((nint)weapon == nint.Zero) return false;
|
||||
bool hasChanges = false;
|
||||
hasChanges |= weapon->ModelSetId != OffHandData[0];
|
||||
OffHandData[0] = weapon->ModelSetId;
|
||||
hasChanges |= weapon->Variant != OffHandData[1];
|
||||
OffHandData[1] = weapon->Variant;
|
||||
hasChanges |= weapon->SecondaryId != OffHandData[2];
|
||||
OffHandData[2] = weapon->SecondaryId;
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
private void FrameworkUpdate()
|
||||
{
|
||||
try
|
||||
@@ -403,24 +365,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe DrawCondition IsBeingDrawnUnsafe()
|
||||
{
|
||||
if (Address == IntPtr.Zero) return DrawCondition.ObjectZero;
|
||||
if (DrawObjectAddress == IntPtr.Zero) return DrawCondition.DrawObjectZero;
|
||||
var visibilityFlags = ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->RenderFlags;
|
||||
if (visibilityFlags != VisibilityFlags.None) return DrawCondition.RenderFlags;
|
||||
|
||||
if (ObjectKind == ObjectKind.Player)
|
||||
{
|
||||
var modelInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelInSlotLoaded != 0);
|
||||
if (modelInSlotLoaded) return DrawCondition.ModelInSlotLoaded;
|
||||
var modelFilesInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelFilesInSlotLoaded != 0);
|
||||
if (modelFilesInSlotLoaded) return DrawCondition.ModelFilesInSlotLoaded;
|
||||
}
|
||||
|
||||
return DrawCondition.None;
|
||||
}
|
||||
|
||||
private void ZoneSwitchEnd()
|
||||
{
|
||||
if (!_isOwnedObject) return;
|
||||
|
||||
Reference in New Issue
Block a user