Added documentation, support of log out and log on added.

This commit is contained in:
cake
2026-01-16 17:16:29 +01:00
parent d8b4122ec3
commit 828705cbfb
3 changed files with 175 additions and 58 deletions

View File

@@ -1,10 +1,10 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.Mediator; using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Plugin.Services;
namespace LightlessSync.PlayerData.Handlers; namespace LightlessSync.PlayerData.Handlers;
@@ -15,30 +15,51 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
private readonly Func<IntPtr> _getAddress; private readonly Func<IntPtr> _getAddress;
private readonly bool _isOwnedObject; private readonly bool _isOwnedObject;
private readonly PerformanceCollectorService _performanceCollector; private readonly PerformanceCollectorService _performanceCollector;
private readonly object _frameworkUpdateGate = new(); private readonly Lock _frameworkUpdateGate = new();
private bool _frameworkUpdateSubscribed; private bool _frameworkUpdateSubscribed;
private byte _classJob = 0; private byte _classJob = 0;
private Task? _delayedZoningTask; private Task? _delayedZoningTask;
private bool _haltProcessing = false; private bool _haltProcessing = false;
private CancellationTokenSource _zoningCts = new(); private CancellationTokenSource _zoningCts = new();
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector, /// <summary>
LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, IObjectTable objectTable, bool ownedObject = true) : base(logger, mediator) /// Constructor for GameObjectHandler
/// </summary>
/// <param name="logger">Logger</param>
/// <param name="performanceCollector">Performance Collector</param>
/// <param name="mediator">Lightless Mediator</param>
/// <param name="dalamudUtil">Dalamud Utilties Service</param>
/// <param name="objectKind">Object kind of Object</param>
/// <param name="getAddress">Get Adress</param>
/// <param name="objectTable">Object table of Dalamud</param>
/// <param name="ownedObject">Object is owned by user</param>
public GameObjectHandler(
ILogger<GameObjectHandler> logger,
PerformanceCollectorService performanceCollector,
LightlessMediator mediator,
DalamudUtilService dalamudUtil,
ObjectKind objectKind,
Func<IntPtr> getAddress,
IObjectTable objectTable,
bool ownedObject = true) : base(logger, mediator)
{ {
_performanceCollector = performanceCollector; _performanceCollector = performanceCollector;
ObjectKind = objectKind; ObjectKind = objectKind;
_dalamudUtil = dalamudUtil; _dalamudUtil = dalamudUtil;
_objectTable = objectTable;
_getAddress = () => _getAddress = () =>
{ {
_dalamudUtil.EnsureIsOnFramework(); _dalamudUtil.EnsureIsOnFramework();
return getAddress.Invoke(); return getAddress.Invoke();
}; };
_isOwnedObject = ownedObject; _isOwnedObject = ownedObject;
Name = string.Empty; Name = string.Empty;
if (ownedObject) if (ownedObject)
{ {
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) => Mediator.Subscribe<TransientResourceChangedMessage>(this, msg =>
{ {
if (_delayedZoningTask?.IsCompleted ?? true) if (_delayedZoningTask?.IsCompleted ?? true)
{ {
@@ -48,44 +69,36 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
}); });
} }
if (_isOwnedObject)
{
EnableFrameworkUpdates(); EnableFrameworkUpdates();
}
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (_) => ZoneSwitchEnd()); Mediator.Subscribe<ZoneSwitchEndMessage>(this, _ => ZoneSwitchEnd());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) => ZoneSwitchStart()); Mediator.Subscribe<ZoneSwitchStartMessage>(this, _ => ZoneSwitchStart());
Mediator.Subscribe<CutsceneStartMessage>(this, (_) => Mediator.Subscribe<CutsceneStartMessage>(this, _ => _haltProcessing = true);
{ Mediator.Subscribe<CutsceneEndMessage>(this, _ =>
_haltProcessing = true;
});
Mediator.Subscribe<CutsceneEndMessage>(this, (_) =>
{ {
_haltProcessing = false; _haltProcessing = false;
ZoneSwitchEnd(); ZoneSwitchEnd();
}); });
Mediator.Subscribe<PenumbraStartRedrawMessage>(this, (msg) =>
Mediator.Subscribe<PenumbraStartRedrawMessage>(this, msg =>
{ {
if (msg.Address == Address) if (msg.Address == Address) _haltProcessing = true;
{
_haltProcessing = true;
}
}); });
Mediator.Subscribe<PenumbraEndRedrawMessage>(this, (msg) => Mediator.Subscribe<PenumbraEndRedrawMessage>(this, msg =>
{ {
if (msg.Address == Address) if (msg.Address == Address) _haltProcessing = false;
{
_haltProcessing = false;
}
}); });
Mediator.Publish(new GameObjectHandlerCreatedMessage(this, _isOwnedObject)); Mediator.Publish(new GameObjectHandlerCreatedMessage(this, _isOwnedObject));
_objectTable = objectTable;
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult(); _dalamudUtil.EnsureIsOnFramework();
CheckAndUpdateObject(allowPublish: true);
} }
/// <summary>
/// Draw Condition Enum
/// </summary>
public enum DrawCondition public enum DrawCondition
{ {
None, None,
@@ -96,6 +109,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
ModelFilesInSlotLoaded ModelFilesInSlotLoaded
} }
// Properties
public IntPtr Address { get; private set; } public IntPtr Address { get; private set; }
public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None; public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None;
public byte Gender { get; private set; } public byte Gender { get; private set; }
@@ -106,10 +120,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
public byte TribeId { get; private set; } public byte TribeId { get; private set; }
private byte[] CustomizeData { get; set; } = new byte[26]; private byte[] CustomizeData { get; set; } = new byte[26];
private IntPtr DrawObjectAddress { get; set; } private IntPtr DrawObjectAddress { get; set; }
private byte[] EquipSlotData { get; set; } = new byte[40];
private ushort[] MainHandData { get; set; } = new ushort[3];
private ushort[] OffHandData { get; set; } = new ushort[3];
/// <summary>
/// Act on framework thread after ensuring no draw condition
/// </summary>
/// <param name="act">Action of Character</param>
/// <param name="token">Cancellation Token</param>
/// <returns>Task Completion</returns>
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<ICharacter> act, CancellationToken token) public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<ICharacter> act, CancellationToken token)
{ {
while (await _dalamudUtil.RunOnFrameworkThread(() => while (await _dalamudUtil.RunOnFrameworkThread(() =>
@@ -128,6 +145,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Compare Name And Throw if not equal
/// </summary>
/// <param name="name">Name that will be compared to Object Handler.</param>
/// <exception cref="InvalidOperationException">Not equal if thrown</exception>
public void CompareNameAndThrow(string name) public void CompareNameAndThrow(string name)
{ {
if (!string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) if (!string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
@@ -140,11 +162,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Gets the game object from the address
/// </summary>
/// <returns>Gane object</returns>
public IGameObject? GetGameObject() public IGameObject? GetGameObject()
{ {
return _dalamudUtil.CreateGameObject(Address); return _dalamudUtil.CreateGameObject(Address);
} }
/// <summary>
/// Invalidate the object handler
/// </summary>
public void Invalidate() public void Invalidate()
{ {
Address = IntPtr.Zero; Address = IntPtr.Zero;
@@ -153,26 +182,43 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
_haltProcessing = false; _haltProcessing = false;
} }
/// <summary>
/// Refresh the object handler state
/// </summary>
public void Refresh() public void Refresh()
{ {
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult(); _dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult();
} }
/// <summary>
/// Is Being Drawn Run On Framework Asyncronously
/// </summary>
/// <returns>Object is being run in framework</returns>
public async Task<bool> IsBeingDrawnRunOnFrameworkAsync() public async Task<bool> IsBeingDrawnRunOnFrameworkAsync()
{ {
return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false); return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false);
} }
/// <summary>
/// Override ToString method for GameObjectHandler
/// </summary>
/// <returns>String</returns>
public override string ToString() public override string ToString()
{ {
var owned = _isOwnedObject ? "Self" : "Other"; var owned = _isOwnedObject ? "Self" : "Other";
return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})"; return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})";
} }
/// <summary>
/// Try Get Object By Address from Object Table
/// </summary>
/// <param name="address">Object address</param>
/// <returns>Game Object of adress</returns>
private IGameObject? TryGetObjectByAddress(nint address) private IGameObject? TryGetObjectByAddress(nint address)
{ {
if (address == nint.Zero) return null; if (address == nint.Zero) return null;
// Search object table
foreach (var obj in _objectTable) foreach (var obj in _objectTable)
{ {
if (obj is null) continue; if (obj is null) continue;
@@ -182,8 +228,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
return null; return null;
} }
/// <summary>
/// Checks and updates the object state
/// </summary>
private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true); private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true);
/// <summary>
/// Checks and updates the object state with option to allow publish
/// </summary>
/// <param name="allowPublish">Allows to publish the object</param>
private void CheckAndUpdateObject(bool allowPublish) private void CheckAndUpdateObject(bool allowPublish)
{ {
var prevAddr = Address; var prevAddr = Address;
@@ -197,6 +250,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
if (Address != nint.Zero) if (Address != nint.Zero)
{ {
// Try get object
obj = TryGetObjectByAddress(Address); obj = TryGetObjectByAddress(Address);
if (obj is not null) if (obj is not null)
@@ -205,6 +259,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
DrawObjectAddress = Address; DrawObjectAddress = Address;
// Name update
nameString = obj.Name.TextValue ?? string.Empty; nameString = obj.Name.TextValue ?? string.Empty;
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal)) if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
Name = nameString; Name = nameString;
@@ -223,13 +278,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
EntityId = uint.MaxValue; EntityId = uint.MaxValue;
} }
// Update draw condition
CurrentDrawCondition = IsBeingDrawnSafe(obj, chara); CurrentDrawCondition = IsBeingDrawnSafe(obj, chara);
if (_haltProcessing || !allowPublish) return; if (_haltProcessing || !allowPublish) return;
// Determine differences
bool drawObjDiff = DrawObjectAddress != prevDrawObj; bool drawObjDiff = DrawObjectAddress != prevDrawObj;
bool addrDiff = Address != prevAddr; bool addrDiff = Address != prevAddr;
// Name change check
bool nameChange = false; bool nameChange = false;
if (nameString is not null) if (nameString is not null)
{ {
@@ -237,10 +295,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
if (nameChange) Name = nameString; if (nameChange) Name = nameString;
} }
// Customize data change check
bool customizeDiff = false; bool customizeDiff = false;
if (chara is not null) if (chara is not null)
{ {
// Class job change check
var classJob = chara.ClassJob.RowId; var classJob = chara.ClassJob.RowId;
if (classJob != _classJob) if (classJob != _classJob)
{ {
@@ -249,8 +308,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
Mediator.Publish(new ClassJobChangedMessage(this)); Mediator.Publish(new ClassJobChangedMessage(this));
} }
// Customize data comparison
customizeDiff = CompareAndUpdateCustomizeData(chara.Customize); customizeDiff = CompareAndUpdateCustomizeData(chara.Customize);
// Census update publish
if (_isOwnedObject && ObjectKind == ObjectKind.Player && chara.Customize.Length > (int)CustomizeIndex.Tribe) if (_isOwnedObject && ObjectKind == ObjectKind.Player && chara.Customize.Length > (int)CustomizeIndex.Tribe)
{ {
var gender = chara.Customize[(int)CustomizeIndex.Gender]; var gender = chara.Customize[(int)CustomizeIndex.Gender];
@@ -287,21 +348,35 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Is object being drawn safe check
/// </summary>
/// <param name="obj">Object thats being checked</param>
/// <param name="chara">Character of the object</param>
/// <returns>Draw Condition of character</returns>
private DrawCondition IsBeingDrawnSafe(IGameObject? obj, ICharacter? chara) private DrawCondition IsBeingDrawnSafe(IGameObject? obj, ICharacter? chara)
{ {
// Object zero check
if (Address == nint.Zero) return DrawCondition.ObjectZero; if (Address == nint.Zero) return DrawCondition.ObjectZero;
if (obj is null) return DrawCondition.DrawObjectZero; if (obj is null) return DrawCondition.DrawObjectZero;
// Draw Object check
if (chara is not null && (chara.Customize is null || chara.Customize.Length == 0)) if (chara is not null && (chara.Customize is null || chara.Customize.Length == 0))
return DrawCondition.DrawObjectZero; return DrawCondition.DrawObjectZero;
return DrawCondition.None; return DrawCondition.None;
} }
/// <summary>
/// Compare and update customize data of character
/// </summary>
/// <param name="customizeData">Customize+ data of object</param>
/// <returns>Successfully applied or not</returns>
private bool CompareAndUpdateCustomizeData(ReadOnlySpan<byte> customizeData) private bool CompareAndUpdateCustomizeData(ReadOnlySpan<byte> customizeData)
{ {
bool hasChanges = false; bool hasChanges = false;
// Resize if needed
var len = Math.Min(customizeData.Length, CustomizeData.Length); var len = Math.Min(customizeData.Length, CustomizeData.Length);
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
@@ -316,6 +391,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
return hasChanges; return hasChanges;
} }
/// <summary>
/// Framework update method
/// </summary>
private void FrameworkUpdate() private void FrameworkUpdate()
{ {
try try
@@ -329,6 +407,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Is object being drawn check
/// </summary>
/// <returns>Is being drawn</returns>
private bool IsBeingDrawn() private bool IsBeingDrawn()
{ {
EnsureLatestObjectState(); EnsureLatestObjectState();
@@ -343,6 +425,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
return CurrentDrawCondition != DrawCondition.None; return CurrentDrawCondition != DrawCondition.None;
} }
/// <summary>
/// Ensures the latest object state
/// </summary>
private void EnsureLatestObjectState() private void EnsureLatestObjectState()
{ {
if (_haltProcessing || !_frameworkUpdateSubscribed) if (_haltProcessing || !_frameworkUpdateSubscribed)
@@ -351,6 +436,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Enables framework updates for the object handler
/// </summary>
private void EnableFrameworkUpdates() private void EnableFrameworkUpdates()
{ {
lock (_frameworkUpdateGate) lock (_frameworkUpdateGate)
@@ -365,6 +453,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Zone switch end handling
/// </summary>
private void ZoneSwitchEnd() private void ZoneSwitchEnd()
{ {
if (!_isOwnedObject) return; if (!_isOwnedObject) return;
@@ -375,7 +466,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
catch (ObjectDisposedException) catch (ObjectDisposedException)
{ {
// ignore // ignore canelled after disposed
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -383,6 +474,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
} }
} }
/// <summary>
/// Zone switch start handling
/// </summary>
private void ZoneSwitchStart() private void ZoneSwitchStart()
{ {
if (!_isOwnedObject) return; if (!_isOwnedObject) return;

View File

@@ -1,14 +1,13 @@
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using LightlessSync.API.Data; using LightlessSync.API.Data;
using LightlessSync.API.Data.Enum;
using LightlessSync.Interop.Ipc; using LightlessSync.Interop.Ipc;
using LightlessSync.PlayerData.Factories; using LightlessSync.PlayerData.Factories;
using LightlessSync.PlayerData.Pairs; using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services; using LightlessSync.Services;
using LightlessSync.Services.ActorTracking; using LightlessSync.Services.ActorTracking;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Dalamud.Game.ClientState.Objects.Enums;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind; using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind; using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;

View File

@@ -816,9 +816,12 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
{ {
_logger.LogInformation("Starting DalamudUtilService"); _logger.LogInformation("Starting DalamudUtilService");
_framework.Update += FrameworkOnUpdate; _framework.Update += FrameworkOnUpdate;
if (IsLoggedIn) _clientState.Login += OnClientLogin;
_clientState.Logout += OnClientLogout;
if (_clientState.IsLoggedIn)
{ {
_classJobId = _objectTable.LocalPlayer!.ClassJob.RowId; OnClientLogin();
} }
_logger.LogInformation("Started DalamudUtilService"); _logger.LogInformation("Started DalamudUtilService");
@@ -831,6 +834,9 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.UnsubscribeAll(this); Mediator.UnsubscribeAll(this);
_framework.Update -= FrameworkOnUpdate; _framework.Update -= FrameworkOnUpdate;
_clientState.Login -= OnClientLogin;
_clientState.Logout -= OnClientLogout;
if (_FocusPairIdent.HasValue) if (_FocusPairIdent.HasValue)
{ {
if (_framework.IsInFrameworkUpdateThread) if (_framework.IsInFrameworkUpdateThread)
@@ -845,6 +851,41 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return Task.CompletedTask; return Task.CompletedTask;
} }
private void OnClientLogin()
{
if (IsLoggedIn)
return;
_ = RunOnFrameworkThread(() =>
{
if (IsLoggedIn)
return;
var localPlayer = _objectTable.LocalPlayer;
IsLoggedIn = true;
_lastZone = _clientState.TerritoryType;
if (localPlayer != null)
{
_lastWorldId = (ushort)localPlayer.CurrentWorld.RowId;
_classJobId = localPlayer.ClassJob.RowId;
}
_cid = RebuildCID();
Mediator.Publish(new DalamudLoginMessage());
});
}
private void OnClientLogout(int type, int code)
{
if (!IsLoggedIn)
return;
_ = RunOnFrameworkThread(() =>
{
if (!IsLoggedIn)
return;
IsLoggedIn = false;
_lastWorldId = 0;
Mediator.Publish(new DalamudLogoutMessage());
});
}
public async Task WaitWhileCharacterIsDrawing( public async Task WaitWhileCharacterIsDrawing(
ILogger logger, ILogger logger,
GameObjectHandler handler, GameObjectHandler handler,
@@ -1272,23 +1313,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
if (isNormalFrameworkUpdate) if (isNormalFrameworkUpdate)
return; return;
if (localPlayer != null && !IsLoggedIn)
{
_logger.LogDebug("Logged in");
IsLoggedIn = true;
_lastZone = _clientState.TerritoryType;
_lastWorldId = (ushort)localPlayer.CurrentWorld.RowId;
_cid = RebuildCID();
Mediator.Publish(new DalamudLoginMessage());
}
else if (localPlayer == null && IsLoggedIn)
{
_logger.LogDebug("Logged out");
IsLoggedIn = false;
_lastWorldId = 0;
Mediator.Publish(new DalamudLogoutMessage());
}
if (_gameConfig != null if (_gameConfig != null
&& _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled)) && _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled))
{ {