Code commit
This commit is contained in:
83
CustomizePlus.GameData/Services/CutsceneService.cs
Normal file
83
CustomizePlus.GameData/Services/CutsceneService.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Actors;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CustomizePlus.GameData.Services;
|
||||
|
||||
public class CutsceneService : IDisposable
|
||||
{
|
||||
public const int CutsceneStartIdx = (int)ScreenActor.CutsceneStart;
|
||||
public const int CutsceneEndIdx = (int)ScreenActor.CutsceneEnd;
|
||||
public const int CutsceneSlots = CutsceneEndIdx - CutsceneStartIdx;
|
||||
|
||||
private readonly GameEventManager _events;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();
|
||||
|
||||
public IEnumerable<KeyValuePair<int, Dalamud.Game.ClientState.Objects.Types.GameObject>> Actors
|
||||
=> Enumerable.Range(CutsceneStartIdx, CutsceneSlots)
|
||||
.Where(i => _objects[i] != null)
|
||||
.Select(i => KeyValuePair.Create(i, this[i] ?? _objects[i]!));
|
||||
|
||||
public unsafe CutsceneService(IObjectTable objects, GameEventManager events)
|
||||
{
|
||||
_objects = objects;
|
||||
_events = events;
|
||||
_events.CopyCharacter += OnCharacterCopy;
|
||||
_events.CharacterDestructor += OnCharacterDestructor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the related actor to a cutscene actor.
|
||||
/// Does not check for valid input index.
|
||||
/// Returns null if no connected actor is set or the actor does not exist anymore.
|
||||
/// </summary>
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(idx is >= CutsceneStartIdx and < CutsceneEndIdx);
|
||||
idx = _copiedCharacters[idx - CutsceneStartIdx];
|
||||
return idx < 0 ? null : _objects[idx];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Return the currently set index of a parent or -1 if none is set or the index is invalid. </summary>
|
||||
public int GetParentIndex(int idx)
|
||||
{
|
||||
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||
return _copiedCharacters[idx - CutsceneStartIdx];
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_events.CopyCharacter -= OnCharacterCopy;
|
||||
_events.CharacterDestructor -= OnCharacterDestructor;
|
||||
}
|
||||
|
||||
private unsafe void OnCharacterDestructor(Character* character)
|
||||
{
|
||||
if (character->GameObject.ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx)
|
||||
return;
|
||||
|
||||
var idx = character->GameObject.ObjectIndex - CutsceneStartIdx;
|
||||
_copiedCharacters[idx] = -1;
|
||||
}
|
||||
|
||||
private unsafe void OnCharacterCopy(Character* target, Character* source)
|
||||
{
|
||||
if (target == null || target->GameObject.ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx)
|
||||
return;
|
||||
|
||||
var idx = target->GameObject.ObjectIndex - CutsceneStartIdx;
|
||||
_copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1);
|
||||
}
|
||||
}
|
||||
193
CustomizePlus.GameData/Services/GameEventManager.cs
Normal file
193
CustomizePlus.GameData/Services/GameEventManager.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using Penumbra.GameData;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CustomizePlus.GameData.Services;
|
||||
|
||||
public unsafe class GameEventManager : IDisposable
|
||||
{
|
||||
private const string Prefix = $"[{nameof(GameEventManager)}]";
|
||||
|
||||
public event CharacterDestructorEvent? CharacterDestructor;
|
||||
public event CopyCharacterEvent? CopyCharacter;
|
||||
public event CreatingCharacterBaseEvent? CreatingCharacterBase;
|
||||
public event CharacterBaseCreatedEvent? CharacterBaseCreated;
|
||||
public event CharacterBaseDestructorEvent? CharacterBaseDestructor;
|
||||
|
||||
public GameEventManager(IGameInteropProvider interop)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
|
||||
_copyCharacterHook =
|
||||
interop.HookFromAddress<CopyCharacterDelegate>((nint)CharacterSetup.MemberFunctionPointers.CopyFromCharacter, CopyCharacterDetour);
|
||||
_characterBaseCreateHook =
|
||||
interop.HookFromAddress<CharacterBaseCreateDelegate>((nint)CharacterBase.MemberFunctionPointers.Create, CharacterBaseCreateDetour);
|
||||
_characterBaseDestructorHook =
|
||||
interop.HookFromAddress<CharacterBaseDestructorEvent>((nint)CharacterBase.MemberFunctionPointers.Destroy,
|
||||
CharacterBaseDestructorDetour);
|
||||
_characterDtorHook.Enable();
|
||||
_copyCharacterHook.Enable();
|
||||
_characterBaseCreateHook.Enable();
|
||||
_characterBaseDestructorHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_characterDtorHook.Dispose();
|
||||
_copyCharacterHook.Dispose();
|
||||
_characterBaseCreateHook.Dispose();
|
||||
_characterBaseDestructorHook.Dispose();
|
||||
}
|
||||
|
||||
#region Character Destructor
|
||||
|
||||
private delegate void CharacterDestructorDelegate(Character* character);
|
||||
|
||||
[Signature(Sigs.CharacterDestructor, DetourName = nameof(CharacterDestructorDetour))]
|
||||
private readonly Hook<CharacterDestructorDelegate> _characterDtorHook = null!;
|
||||
|
||||
private void CharacterDestructorDetour(Character* character)
|
||||
{
|
||||
if (CharacterDestructor != null)
|
||||
foreach (var subscriber in CharacterDestructor.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((CharacterDestructorEvent)subscriber).Invoke(character);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Penumbra.Log.Error($"{Prefix} Error in {nameof(CharacterDestructor)} event when executing {subscriber.Method.Name}:\n{ex}");
|
||||
//todo: log
|
||||
}
|
||||
}
|
||||
|
||||
//Penumbra.Log.Verbose($"{Prefix} {nameof(CharacterDestructor)} triggered with 0x{(nint)character:X}.");
|
||||
//todo: log
|
||||
_characterDtorHook.Original(character);
|
||||
}
|
||||
|
||||
public delegate void CharacterDestructorEvent(Character* character);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Copy Character
|
||||
|
||||
private delegate ulong CopyCharacterDelegate(CharacterSetup* target, GameObject* source, uint unk);
|
||||
|
||||
private readonly Hook<CopyCharacterDelegate> _copyCharacterHook;
|
||||
|
||||
private ulong CopyCharacterDetour(CharacterSetup* target, GameObject* source, uint unk)
|
||||
{
|
||||
// TODO: update when CS updated.
|
||||
var character = ((Character**)target)[1];
|
||||
if (CopyCharacter != null)
|
||||
foreach (var subscriber in CopyCharacter.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((CopyCharacterEvent)subscriber).Invoke(character, (Character*)source);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
/*Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(CopyCharacter)} event when executing {subscriber.Method.Name}:\n{ex}");*/
|
||||
//todo: log
|
||||
}
|
||||
}
|
||||
|
||||
/*Penumbra.Log.Verbose(
|
||||
$"{Prefix} {nameof(CopyCharacter)} triggered with target 0x{(nint)target:X} and source 0x{(nint)source:X}.");*/
|
||||
//todo: log
|
||||
return _copyCharacterHook.Original(target, source, unk);
|
||||
}
|
||||
|
||||
public delegate void CopyCharacterEvent(Character* target, Character* source);
|
||||
|
||||
#endregion
|
||||
|
||||
#region CharacterBaseCreate
|
||||
|
||||
private delegate nint CharacterBaseCreateDelegate(uint a, nint b, nint c, byte d);
|
||||
|
||||
private readonly Hook<CharacterBaseCreateDelegate> _characterBaseCreateHook;
|
||||
|
||||
private nint CharacterBaseCreateDetour(uint a, nint b, nint c, byte d)
|
||||
{
|
||||
if (CreatingCharacterBase != null)
|
||||
foreach (var subscriber in CreatingCharacterBase.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((CreatingCharacterBaseEvent)subscriber).Invoke((nint)(&a), b, c);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
/*Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(CharacterBaseCreateDetour)} event when executing {subscriber.Method.Name}:\n{ex}");*/
|
||||
//todo: log
|
||||
}
|
||||
}
|
||||
|
||||
var ret = _characterBaseCreateHook.Original(a, b, c, d);
|
||||
if (CharacterBaseCreated != null)
|
||||
foreach (var subscriber in CharacterBaseCreated.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((CharacterBaseCreatedEvent)subscriber).Invoke(a, b, c, ret);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
/*Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(CharacterBaseCreateDetour)} event when executing {subscriber.Method.Name}:\n{ex}");*/
|
||||
//todo: log
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public delegate void CreatingCharacterBaseEvent(nint modelCharaId, nint customize, nint equipment);
|
||||
public delegate void CharacterBaseCreatedEvent(uint modelCharaId, nint customize, nint equipment, nint drawObject);
|
||||
|
||||
#endregion
|
||||
|
||||
#region CharacterBase Destructor
|
||||
|
||||
public delegate void CharacterBaseDestructorEvent(nint drawBase);
|
||||
|
||||
private readonly Hook<CharacterBaseDestructorEvent> _characterBaseDestructorHook;
|
||||
|
||||
private void CharacterBaseDestructorDetour(nint drawBase)
|
||||
{
|
||||
if (CharacterBaseDestructor != null)
|
||||
foreach (var subscriber in CharacterBaseDestructor.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((CharacterBaseDestructorEvent)subscriber).Invoke(drawBase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
/*Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(CharacterBaseDestructorDetour)} event when executing {subscriber.Method.Name}:\n{ex}");*/
|
||||
//todo: log
|
||||
}
|
||||
}
|
||||
|
||||
_characterBaseDestructorHook.Original.Invoke(drawBase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
238
CustomizePlus.GameData/Services/ObjectManager.cs
Normal file
238
CustomizePlus.GameData/Services/ObjectManager.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using CustomizePlus.GameData.Data;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using Penumbra.GameData.Actors;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CustomizePlus.GameData.Services;
|
||||
|
||||
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||
{
|
||||
private readonly IFramework _framework;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ITargetManager _targets;
|
||||
|
||||
public IObjectTable Objects
|
||||
=> _objects;
|
||||
|
||||
public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorService actors, ITargetManager targets)
|
||||
{
|
||||
_framework = framework;
|
||||
_clientState = clientState;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
_targets = targets;
|
||||
}
|
||||
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
||||
public bool IsInGPose { get; private set; }
|
||||
public ushort World { get; private set; }
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _allWorldIdentifiers = new(200);
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _nonOwnedIdentifiers = new(200);
|
||||
|
||||
public IReadOnlyDictionary<ActorIdentifier, ActorData> Identifiers
|
||||
=> _identifiers;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var lastUpdate = _framework.LastUpdate;
|
||||
if (lastUpdate <= LastUpdate)
|
||||
return;
|
||||
|
||||
LastUpdate = lastUpdate;
|
||||
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||
_identifiers.Clear();
|
||||
_allWorldIdentifiers.Clear();
|
||||
_nonOwnedIdentifiers.Clear();
|
||||
|
||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (character.Identifier(_actors.AwaitedService, out var identifier))
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (!character.Valid)
|
||||
break;
|
||||
|
||||
HandleIdentifier(character.GetIdentifier(_actors.AwaitedService), character);
|
||||
}
|
||||
|
||||
void AddSpecial(ScreenActor idx, string label)
|
||||
{
|
||||
Actor actor = _objects.GetObjectAddress((int)idx);
|
||||
if (actor.Identifier(_actors.AwaitedService, out var ident))
|
||||
{
|
||||
var data = new ActorData(actor, label);
|
||||
_identifiers.Add(ident, data);
|
||||
}
|
||||
}
|
||||
|
||||
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
|
||||
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
|
||||
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
|
||||
AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor");
|
||||
AddSpecial(ScreenActor.Portrait, "Portrait Actor");
|
||||
AddSpecial(ScreenActor.Card6, "Card Actor 6");
|
||||
AddSpecial(ScreenActor.Card7, "Card Actor 7");
|
||||
AddSpecial(ScreenActor.Card8, "Card Actor 8");
|
||||
|
||||
for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (character.Identifier(_actors.AwaitedService, out var identifier))
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
var gPose = GPosePlayer;
|
||||
IsInGPose = gPose.Utf8Name.Length > 0;
|
||||
}
|
||||
|
||||
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
||||
{
|
||||
if (!character.Model || !identifier.IsValid)
|
||||
return;
|
||||
|
||||
if (!_identifiers.TryGetValue(identifier, out var data))
|
||||
{
|
||||
data = new ActorData(character, identifier.ToString());
|
||||
_identifiers[identifier] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Objects.Add(character);
|
||||
}
|
||||
|
||||
if (identifier.Type is IdentifierType.Player or IdentifierType.Owned)
|
||||
{
|
||||
var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
||||
identifier.Kind,
|
||||
identifier.DataId);
|
||||
|
||||
if (!_allWorldIdentifiers.TryGetValue(allWorld, out var allWorldData))
|
||||
{
|
||||
allWorldData = new ActorData(character, allWorld.ToString());
|
||||
_allWorldIdentifiers[allWorld] = allWorldData;
|
||||
}
|
||||
else
|
||||
{
|
||||
allWorldData.Objects.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
if (identifier.Type is IdentifierType.Owned)
|
||||
{
|
||||
var nonOwned = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId);
|
||||
if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData))
|
||||
{
|
||||
nonOwnedData = new ActorData(character, nonOwned.ToString());
|
||||
_nonOwnedIdentifiers[nonOwned] = nonOwnedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonOwnedData.Objects.Add(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Actor GPosePlayer
|
||||
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
|
||||
|
||||
public Actor Player
|
||||
=> _objects.GetObjectAddress(0);
|
||||
|
||||
public unsafe Actor Target
|
||||
=> _clientState.IsGPosing ? TargetSystem.Instance()->GPoseTarget : TargetSystem.Instance()->Target;
|
||||
|
||||
public Actor Focus
|
||||
=> _targets.FocusTarget?.Address ?? nint.Zero;
|
||||
|
||||
public Actor MouseOver
|
||||
=> _targets.MouseOverTarget?.Address ?? nint.Zero;
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) PlayerData
|
||||
{
|
||||
get
|
||||
{
|
||||
Update();
|
||||
return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||
? (ident, data)
|
||||
: (ident, ActorData.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public (ActorIdentifier Identifier, ActorData Data) TargetData
|
||||
{
|
||||
get
|
||||
{
|
||||
Update();
|
||||
return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data)
|
||||
? (ident, data)
|
||||
: (ident, ActorData.Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
||||
=> Identifiers.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> Identifiers.Count;
|
||||
|
||||
/// <summary> Also handles All Worlds players and non-owned NPCs. </summary>
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> Identifiers.ContainsKey(key) || _allWorldIdentifiers.ContainsKey(key) || _nonOwnedIdentifiers.ContainsKey(key);
|
||||
|
||||
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||
=> Identifiers.TryGetValue(key, out value);
|
||||
|
||||
public bool TryGetValueAllWorld(ActorIdentifier key, out ActorData value)
|
||||
=> _allWorldIdentifiers.TryGetValue(key, out value);
|
||||
|
||||
public bool TryGetValueNonOwned(ActorIdentifier key, out ActorData value)
|
||||
=> _nonOwnedIdentifiers.TryGetValue(key, out value);
|
||||
|
||||
public ActorData this[ActorIdentifier key]
|
||||
=> Identifiers[key];
|
||||
|
||||
public IEnumerable<ActorIdentifier> Keys
|
||||
=> Identifiers.Keys;
|
||||
|
||||
public IEnumerable<ActorData> Values
|
||||
=> Identifiers.Values;
|
||||
|
||||
public bool GetName(string lowerName, out Actor actor)
|
||||
{
|
||||
(actor, var ret) = lowerName switch
|
||||
{
|
||||
"" => (Actor.Null, true),
|
||||
"<me>" => (Player, true),
|
||||
"self" => (Player, true),
|
||||
"<t>" => (Target, true),
|
||||
"target" => (Target, true),
|
||||
"<f>" => (Focus, true),
|
||||
"focus" => (Focus, true),
|
||||
"<mo>" => (MouseOver, true),
|
||||
"mouseover" => (MouseOver, true),
|
||||
_ => (Actor.Null, false),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
78
CustomizePlus.GameData/Services/ServiceWrapper.cs
Normal file
78
CustomizePlus.GameData/Services/ServiceWrapper.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Actors;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CustomizePlus.GameData.Services;
|
||||
|
||||
public abstract class AsyncServiceWrapper<T> : IDisposable
|
||||
{
|
||||
public string Name { get; }
|
||||
public T? Service { get; private set; }
|
||||
|
||||
public T AwaitedService
|
||||
{
|
||||
get
|
||||
{
|
||||
_task?.Wait();
|
||||
return Service!;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
=> Service != null && !_isDisposed;
|
||||
|
||||
public event Action? FinishedCreation;
|
||||
private Task? _task;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
protected AsyncServiceWrapper(string name, Func<T> factory)
|
||||
{
|
||||
Name = name;
|
||||
_task = Task.Run(() =>
|
||||
{
|
||||
var service = factory();
|
||||
if (_isDisposed)
|
||||
{
|
||||
if (service is IDisposable d)
|
||||
d.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
Service = service;
|
||||
_task = null;
|
||||
}
|
||||
});
|
||||
_task.ContinueWith((t, x) =>
|
||||
{
|
||||
if (!_isDisposed)
|
||||
FinishedCreation?.Invoke();
|
||||
}, null);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
_isDisposed = true;
|
||||
_task = null;
|
||||
if (Service is IDisposable d)
|
||||
d.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
|
||||
{
|
||||
public ActorService(DalamudPluginInterface pi, IObjectTable objects, IClientState clientState, IFramework framework, IGameInteropProvider interop, IDataManager gameData,
|
||||
IGameGui gui, CutsceneService cutsceneService, IPluginLog log)
|
||||
: base(nameof(ActorService),
|
||||
() => new ActorManager(pi, objects, clientState, framework, interop, gameData, gui, idx => (short)cutsceneService.GetParentIndex(idx), log))
|
||||
{ }
|
||||
}
|
||||
Reference in New Issue
Block a user