From dc7fb73d842187e0647fe34ed1e9cb07ee3ace03 Mon Sep 17 00:00:00 2001 From: RisaDev <151885272+RisaDev@users.noreply.github.com> Date: Sat, 3 Feb 2024 03:07:50 +0300 Subject: [PATCH] Updated to latest Penumbra.GameData, updated ObjectManager --- .../Extensions/ActorIdentifierExtensions.cs | 16 ++- .../Hooks/Objects/CharacterDestructor.cs | 48 +++++++ .../Hooks/Objects/CopyCharacter.cs | 46 +++++++ .../Services/CutsceneService.cs | 122 +++++++++++++++--- .../Services/ObjectManager.cs | 28 ++-- .../Services/ServiceWrapper.cs | 78 ----------- .../Armatures/Services/ArmatureManager.cs | 9 +- ...iceManager.cs => ServiceManagerBuilder.cs} | 55 ++++---- .../Core/Services/DalamudServices.cs | 95 +++----------- CustomizePlus/Core/Services/HookingService.cs | 14 +- .../Game/Services/GameObjectService.cs | 9 +- CustomizePlus/Plugin.cs | 15 ++- CustomizePlus/Profiles/ProfileManager.cs | 11 +- .../MainWindow/Tabs/Debug/IPCTestTab.cs | 9 +- submodules/Penumbra.Api | 2 +- submodules/Penumbra.GameData | 2 +- 16 files changed, 313 insertions(+), 246 deletions(-) create mode 100644 CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs create mode 100644 CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs delete mode 100644 CustomizePlus.GameData/Services/ServiceWrapper.cs rename CustomizePlus/Core/{ServiceManager.cs => ServiceManagerBuilder.cs} (72%) diff --git a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs index 4b66e1a..823819c 100644 --- a/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs +++ b/CustomizePlus.GameData/Extensions/ActorIdentifierExtensions.cs @@ -1,5 +1,7 @@ using Dalamud.Game.ClientState.Objects.Enums; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using PenumbraExtensions = Penumbra.GameData.Actors.ActorIdentifierExtensions; namespace CustomizePlus.GameData.Extensions; @@ -19,10 +21,10 @@ public static class ActorIdentifierExtensions if (identifier.Type != IdentifierType.Owned) return identifier.ToName(); - if (ActorIdentifier.Manager == null) + if (PenumbraExtensions.Manager == null) throw new Exception("ActorIdentifier.Manager is not initialized"); - return ActorIdentifier.Manager.Data.ToName(identifier.Kind, identifier.DataId); + return PenumbraExtensions.Manager.Data.ToName(identifier.Kind, identifier.DataId); } /// @@ -93,7 +95,7 @@ public static class ActorIdentifierExtensions if (identifier.Type != IdentifierType.Special) return ActorIdentifier.Invalid; - if (ActorIdentifier.Manager == null) + if (PenumbraExtensions.Manager == null) throw new Exception("ActorIdentifier.Manager is not initialized"); switch (identifier.Special) @@ -103,12 +105,12 @@ public static class ActorIdentifierExtensions case ScreenActor.FittingRoom: case ScreenActor.DyePreview: case ScreenActor.Portrait: - return ActorIdentifier.Manager.GetCurrentPlayer(); + return PenumbraExtensions.Manager.GetCurrentPlayer(); case ScreenActor.ExamineScreen: - var examineIdentifier = ActorIdentifier.Manager.GetInspectPlayer(); + var examineIdentifier = PenumbraExtensions.Manager.GetInspectPlayer(); if (!examineIdentifier.IsValid) - examineIdentifier = ActorIdentifier.Manager.GetGlamourPlayer(); //returns ActorIdentifier.Invalid if player is invalid + examineIdentifier = PenumbraExtensions.Manager.GetGlamourPlayer(); //returns ActorIdentifier.Invalid if player is invalid if (!examineIdentifier.IsValid) return ActorIdentifier.Invalid; @@ -117,7 +119,7 @@ public static class ActorIdentifierExtensions case ScreenActor.Card6: case ScreenActor.Card7: case ScreenActor.Card8: - return ActorIdentifier.Manager.GetCardPlayer(); + return PenumbraExtensions.Manager.GetCardPlayer(); } return ActorIdentifier.Invalid; diff --git a/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs b/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs new file mode 100644 index 0000000..0d2b0e7 --- /dev/null +++ b/CustomizePlus.GameData/Hooks/Objects/CharacterDestructor.cs @@ -0,0 +1,48 @@ +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using OtterGui.Classes; +using OtterGui.Services; +using Penumbra.GameData; + +namespace CustomizePlus.GameData.Hooks.Objects; +public sealed unsafe class CharacterDestructor : EventWrapperPtr, IHookService +{ + public enum Priority + { + /// + CutsceneService = 0, + + /// + IdentifiedCollectionCache = 0, + } + + public CharacterDestructor(HookManager hooks) + : base("Character Destructor") + => _task = hooks.CreateHook(Name, Sigs.CharacterDestructor, Detour, true); + + private readonly Task> _task; + + public nint Address + => _task.Result.Address; + + public void Enable() + => _task.Result.Enable(); + + public void Disable() + => _task.Result.Disable(); + + public Task Awaiter + => _task; + + public bool Finished + => _task.IsCompletedSuccessfully; + + private delegate void Delegate(Character* character); + + private void Detour(Character* character) + { + //Penumbra.Log.Verbose($"[{Name}] Triggered with 0x{(nint)character:X}."); + Invoke(character); + _task.Result.Original(character); + } +} \ No newline at end of file diff --git a/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs b/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs new file mode 100644 index 0000000..e82ae82 --- /dev/null +++ b/CustomizePlus.GameData/Hooks/Objects/CopyCharacter.cs @@ -0,0 +1,46 @@ +using Dalamud.Hooking; +using FFXIVClientStructs.FFXIV.Client.Game.Character; +using OtterGui.Classes; +using OtterGui.Services; + +namespace CustomizePlus.GameData.Hooks.Objects; +public sealed unsafe class CopyCharacter : EventWrapperPtr, IHookService +{ + public enum Priority + { + /// + CutsceneService = 0, + } + + public CopyCharacter(HookManager hooks) + : base("Copy Character") + => _task = hooks.CreateHook(Name, Address, Detour, true); + + private readonly Task> _task; + + public nint Address + => (nint)CharacterSetup.MemberFunctionPointers.CopyFromCharacter; + + public void Enable() + => _task.Result.Enable(); + + public void Disable() + => _task.Result.Disable(); + + public Task Awaiter + => _task; + + public bool Finished + => _task.IsCompletedSuccessfully; + + private delegate ulong Delegate(CharacterSetup* target, Character* source, uint unk); + + private ulong Detour(CharacterSetup* target, Character* source, uint unk) + { + // TODO: update when CS updated. + var character = ((Character**)target)[1]; + //Penumbra.Log.Verbose($"[{Name}] Triggered with target: 0x{(nint)target:X}, source : 0x{(nint)source:X} unk: {unk}."); + Invoke(character, source); + return _task.Result.Original(target, source, unk); + } +} \ No newline at end of file diff --git a/CustomizePlus.GameData/Services/CutsceneService.cs b/CustomizePlus.GameData/Services/CutsceneService.cs index e352afc..3fb34cf 100644 --- a/CustomizePlus.GameData/Services/CutsceneService.cs +++ b/CustomizePlus.GameData/Services/CutsceneService.cs @@ -1,23 +1,23 @@ -using Dalamud.Plugin.Services; +using CustomizePlus.GameData.Hooks.Objects; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; -using Penumbra.GameData.Actors; -using System; -using System.Collections.Generic; +using FFXIVClientStructs.FFXIV.Client.Game.Object; +using OtterGui.Services; +using Penumbra.GameData.Enums; +using Penumbra.String; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace CustomizePlus.GameData.Services; -public class CutsceneService : IDisposable +public class CutsceneService : IService, 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 CopyCharacter _copyCharacter; + private readonly CharacterDestructor _characterDestructor; private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray(); public IEnumerable> Actors @@ -25,14 +25,19 @@ public class CutsceneService : IDisposable .Where(i => _objects[i] != null) .Select(i => KeyValuePair.Create(i, this[i] ?? _objects[i]!)); - public unsafe CutsceneService(IObjectTable objects, GameEventManager events) + public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor, + IClientState clientState) { _objects = objects; - _events = events; - _events.CopyCharacter += OnCharacterCopy; - _events.CharacterDestructor += OnCharacterDestructor; + _copyCharacter = copyCharacter; + _characterDestructor = characterDestructor; + _copyCharacter.Subscribe(OnCharacterCopy, CopyCharacter.Priority.CutsceneService); + _characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.CutsceneService); + if (clientState.IsGPosing) + RecoverGPoseActors(); } + /// /// Get the related actor to a cutscene actor. /// Does not check for valid input index. @@ -50,6 +55,27 @@ public class CutsceneService : IDisposable /// Return the currently set index of a parent or -1 if none is set or the index is invalid. public int GetParentIndex(int idx) + => GetParentIndex((ushort)idx); + + public bool SetParentIndex(int copyIdx, int parentIdx) + { + if (copyIdx is < CutsceneStartIdx or >= CutsceneEndIdx) + return false; + + if (parentIdx is < -1 or >= CutsceneEndIdx) + return false; + + if (_objects.GetObjectAddress(copyIdx) == nint.Zero) + return false; + + if (parentIdx != -1 && _objects.GetObjectAddress(parentIdx) == nint.Zero) + return false; + + _copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx; + return true; + } + + public short GetParentIndex(ushort idx) { if (idx is >= CutsceneStartIdx and < CutsceneEndIdx) return _copiedCharacters[idx - CutsceneStartIdx]; @@ -59,17 +85,34 @@ public class CutsceneService : IDisposable public unsafe void Dispose() { - _events.CopyCharacter -= OnCharacterCopy; - _events.CharacterDestructor -= OnCharacterDestructor; + _copyCharacter.Unsubscribe(OnCharacterCopy); + _characterDestructor.Unsubscribe(OnCharacterDestructor); } private unsafe void OnCharacterDestructor(Character* character) { - if (character->GameObject.ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx) - return; + if (character->GameObject.ObjectIndex < CutsceneStartIdx) + { + // Remove all associations for now non-existing actor. + for (var i = 0; i < _copiedCharacters.Length; ++i) + { + if (_copiedCharacters[i] == character->GameObject.ObjectIndex) + { + // A hack to deal with GPose actors leaving and thus losing the link, we just set the home world instead. + // I do not think this breaks anything? + var address = (GameObject*)_objects.GetObjectAddress(i + CutsceneStartIdx); + if (address != null && address->GetObjectKind() is (byte)ObjectKind.Pc) + ((Character*)address)->HomeWorld = character->HomeWorld; - var idx = character->GameObject.ObjectIndex - CutsceneStartIdx; - _copiedCharacters[idx] = -1; + _copiedCharacters[i] = -1; + } + } + } + else if (character->GameObject.ObjectIndex < CutsceneEndIdx) + { + var idx = character->GameObject.ObjectIndex - CutsceneStartIdx; + _copiedCharacters[idx] = -1; + } } private unsafe void OnCharacterCopy(Character* target, Character* source) @@ -80,4 +123,45 @@ public class CutsceneService : IDisposable var idx = target->GameObject.ObjectIndex - CutsceneStartIdx; _copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1); } + + /// Try to recover GPose actors on reloads into a running game. + /// This is not 100% accurate due to world IDs, minions etc., but will be mostly sane. + private unsafe void RecoverGPoseActors() + { + Dictionary? actors = null; + + for (var i = CutsceneStartIdx; i < CutsceneEndIdx; ++i) + { + if (!TryGetName(i, out var name)) + continue; + + if ((actors ??= CreateActors()).TryGetValue(name, out var idx)) + _copiedCharacters[i - CutsceneStartIdx] = idx; + } + + return; + + bool TryGetName(int idx, out ByteString name) + { + name = ByteString.Empty; + var address = (GameObject*)_objects.GetObjectAddress(idx); + if (address == null) + return false; + + name = new ByteString(address->Name); + return !name.IsEmpty; + } + + Dictionary CreateActors() + { + var ret = new Dictionary(); + for (short i = 0; i < CutsceneStartIdx; ++i) + { + if (TryGetName(i, out var name)) + ret.TryAdd(name, i); + } + + return ret; + } + } } diff --git a/CustomizePlus.GameData/Services/ObjectManager.cs b/CustomizePlus.GameData/Services/ObjectManager.cs index 9f5bba9..cf847c6 100644 --- a/CustomizePlus.GameData/Services/ObjectManager.cs +++ b/CustomizePlus.GameData/Services/ObjectManager.cs @@ -4,6 +4,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Control; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; using System; using System.Collections; using System.Collections.Generic; @@ -18,18 +19,18 @@ public class ObjectManager : IReadOnlyDictionary private readonly IFramework _framework; private readonly IClientState _clientState; private readonly IObjectTable _objects; - private readonly ActorService _actors; + private readonly ActorManager _actorManager; private readonly ITargetManager _targets; public IObjectTable Objects => _objects; - public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorService actors, ITargetManager targets) + public ObjectManager(IFramework framework, IClientState clientState, IObjectTable objects, ActorManager actorManager, ITargetManager targets) { _framework = framework; _clientState = clientState; _objects = objects; - _actors = actors; + _actorManager = actorManager; _targets = targets; } @@ -60,23 +61,26 @@ public class ObjectManager : IReadOnlyDictionary for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actorManager, 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) + // Technically the game does not create holes in cutscenes or GPose. + // But for Brio compatibility, we allow holes in GPose. + // Since GPose always has the event actor in the first cutscene slot, we can still optimize in this case. + if (!character.Valid && i == (int)ScreenActor.CutsceneStart) break; - HandleIdentifier(character.GetIdentifier(_actors.AwaitedService), character); + HandleIdentifier(character.GetIdentifier(_actorManager), character); } void AddSpecial(ScreenActor idx, string label) { Actor actor = _objects.GetObjectAddress((int)idx); - if (actor.Identifier(_actors.AwaitedService, out var ident)) + if (actor.Identifier(_actorManager, out var ident)) { var data = new ActorData(actor, label); _identifiers.Add(ident, data); @@ -95,7 +99,7 @@ public class ObjectManager : IReadOnlyDictionary for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i) { Actor character = _objects.GetObjectAddress(i); - if (character.Identifier(_actors.AwaitedService, out var identifier)) + if (character.Identifier(_actorManager, out var identifier)) HandleIdentifier(identifier, character); } @@ -120,7 +124,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Player or IdentifierType.Owned) { - var allWorld = _actors.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, + var allWorld = _actorManager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue, identifier.Kind, identifier.DataId); @@ -137,7 +141,7 @@ public class ObjectManager : IReadOnlyDictionary if (identifier.Type is IdentifierType.Owned) { - var nonOwned = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); + var nonOwned = _actorManager.CreateNpc(identifier.Kind, identifier.DataId); if (!_nonOwnedIdentifiers.TryGetValue(nonOwned, out var nonOwnedData)) { nonOwnedData = new ActorData(character, nonOwned.ToString()); @@ -170,7 +174,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Player.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Player.Identifier(_actorManager, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } @@ -181,7 +185,7 @@ public class ObjectManager : IReadOnlyDictionary get { Update(); - return Target.Identifier(_actors.AwaitedService, out var ident) && _identifiers.TryGetValue(ident, out var data) + return Target.Identifier(_actorManager, out var ident) && _identifiers.TryGetValue(ident, out var data) ? (ident, data) : (ident, ActorData.Invalid); } diff --git a/CustomizePlus.GameData/Services/ServiceWrapper.cs b/CustomizePlus.GameData/Services/ServiceWrapper.cs deleted file mode 100644 index b90abcc..0000000 --- a/CustomizePlus.GameData/Services/ServiceWrapper.cs +++ /dev/null @@ -1,78 +0,0 @@ -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 : 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 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 -{ - 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)) - { } -} diff --git a/CustomizePlus/Armatures/Services/ArmatureManager.cs b/CustomizePlus/Armatures/Services/ArmatureManager.cs index 15da4d8..e0fec96 100644 --- a/CustomizePlus/Armatures/Services/ArmatureManager.cs +++ b/CustomizePlus/Armatures/Services/ArmatureManager.cs @@ -20,6 +20,7 @@ using CustomizePlus.GameData.Services; using CustomizePlus.GameData.Extensions; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using System.Drawing; +using Penumbra.GameData.Enums; namespace CustomizePlus.Armatures.Services; @@ -33,7 +34,7 @@ public unsafe sealed class ArmatureManager : IDisposable private readonly Logger _logger; private readonly FrameworkManager _framework; private readonly ObjectManager _objectManager; - private readonly ActorService _actorService; + private readonly ActorManager _actorManager; private readonly ArmatureChanged _event; public Dictionary Armatures { get; private set; } = new(); @@ -47,7 +48,7 @@ public unsafe sealed class ArmatureManager : IDisposable Logger logger, FrameworkManager framework, ObjectManager objectManager, - ActorService actorService, + ActorManager actorManager, ArmatureChanged @event) { _profileManager = profileManager; @@ -58,7 +59,7 @@ public unsafe sealed class ArmatureManager : IDisposable _logger = logger; _framework = framework; _objectManager = objectManager; - _actorService = actorService; + _actorManager = actorManager; _event = @event; _templateChangedEvent.Subscribe(OnTemplateChange, TemplateChanged.Priority.ArmatureManager); @@ -92,7 +93,7 @@ public unsafe sealed class ArmatureManager : IDisposable /// public void OnGameObjectMove(Actor actor) { - if (!actor.Identifier(_actorService.AwaitedService, out var identifier)) + if (!actor.Identifier(_actorManager, out var identifier)) return; if (Armatures.TryGetValue(identifier, out var armature) && armature.IsBuilt && armature.IsVisible) diff --git a/CustomizePlus/Core/ServiceManager.cs b/CustomizePlus/Core/ServiceManagerBuilder.cs similarity index 72% rename from CustomizePlus/Core/ServiceManager.cs rename to CustomizePlus/Core/ServiceManagerBuilder.cs index 520f89e..ae8ee9b 100644 --- a/CustomizePlus/Core/ServiceManager.cs +++ b/CustomizePlus/Core/ServiceManagerBuilder.cs @@ -28,16 +28,22 @@ using CustomizePlus.Game.Services.GPose; using CustomizePlus.Game.Services.GPose.ExternalTools; using CustomizePlus.GameData.Services; using CustomizePlus.Configuration.Services.Temporary; +using OtterGui.Services; +using Penumbra.GameData.Actors; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; +using OtterGui; namespace CustomizePlus.Core; -public static class ServiceManager +public static class ServiceManagerBuilder { - public static ServiceProvider CreateProvider(DalamudPluginInterface pi, Logger logger) + public static ServiceManager CreateProvider(DalamudPluginInterface pi, Logger logger) { - var services = new ServiceCollection() - .AddSingleton(logger) - .AddDalamud(pi) + EventWrapperBase.ChangeLogger(logger); + + var services = new ServiceManager(logger) + .AddExistingService(logger) .AddCore() .AddEvents() .AddGPoseServices() @@ -49,16 +55,20 @@ public static class ServiceManager .AddGameServices() .AddConfigServices() .AddRestOfServices(); - return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true }); - } - private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pluginInterface) - { - new DalamudServices(pluginInterface).AddServices(services); + DalamudServices.AddServices(services, pi); + + services.AddIServices(typeof(EquipItem).Assembly); + services.AddIServices(typeof(Plugin).Assembly); + services.AddIServices(typeof(ObjectManager).Assembly); + services.AddIServices(typeof(ImGuiUtil).Assembly); + + services.CreateProvider(); + return services; } - private static IServiceCollection AddGPoseServices(this IServiceCollection services) + private static ServiceManager AddGPoseServices(this ServiceManager services) { services .AddSingleton() @@ -67,14 +77,14 @@ public static class ServiceManager return services; } - private static IServiceCollection AddArmatureServices(this IServiceCollection services) + private static ServiceManager AddArmatureServices(this ServiceManager services) { services .AddSingleton(); return services; } - private static IServiceCollection AddUI(this IServiceCollection services) + private static ServiceManager AddUI(this ServiceManager services) { services .AddSingleton() @@ -107,7 +117,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddEvents(this IServiceCollection services) + private static ServiceManager AddEvents(this ServiceManager services) { services .AddSingleton() @@ -118,7 +128,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddCore(this IServiceCollection services) + private static ServiceManager AddCore(this ServiceManager services) { services .AddSingleton() @@ -133,7 +143,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddRestOfServices(this IServiceCollection services) //temp + private static ServiceManager AddRestOfServices(this ServiceManager services) //temp { services .AddSingleton() @@ -142,7 +152,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddConfigServices(this IServiceCollection services) + private static ServiceManager AddConfigServices(this ServiceManager services) { services .AddSingleton() @@ -153,7 +163,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddGameServices(this IServiceCollection services) + private static ServiceManager AddGameServices(this ServiceManager services) { services .AddSingleton() @@ -162,7 +172,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddProfileServices(this IServiceCollection services) + private static ServiceManager AddProfileServices(this ServiceManager services) { services .AddSingleton() @@ -172,7 +182,7 @@ public static class ServiceManager return services; } - private static IServiceCollection AddTemplateServices(this IServiceCollection services) + private static ServiceManager AddTemplateServices(this ServiceManager services) { services .AddSingleton() @@ -182,12 +192,13 @@ public static class ServiceManager return services; } - private static IServiceCollection AddGameDataServices(this IServiceCollection services) + private static ServiceManager AddGameDataServices(this ServiceManager services) { services + .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton(p => new CutsceneResolver(idx => (short)p.GetRequiredService().GetParentIndex(idx))) .AddSingleton(); return services; diff --git a/CustomizePlus/Core/Services/DalamudServices.cs b/CustomizePlus/Core/Services/DalamudServices.cs index 5866536..2eb2c18 100644 --- a/CustomizePlus/Core/Services/DalamudServices.cs +++ b/CustomizePlus/Core/Services/DalamudServices.cs @@ -4,90 +4,27 @@ using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Microsoft.Extensions.DependencyInjection; +using OtterGui.Services; namespace CustomizePlus.Core.Services; public class DalamudServices { - [PluginService] - [RequiredVersion("1.0")] - public DalamudPluginInterface PluginInterface { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public ISigScanner SigScanner { get; private set; } = null!; - - [PluginService] - public IFramework Framework { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IObjectTable ObjectTable { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public ICommandManager CommandManager { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IChatGui ChatGui { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IClientState ClientState { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IGameGui GameGui { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - internal IGameInteropProvider Hooker { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IKeyState KeyState { get; private set; } = null!; - - //GameData - [PluginService] - [RequiredVersion("1.0")] - public IDataManager DataManager { get; private set; } = null!; - - [PluginService] - [RequiredVersion("1.0")] - public IPluginLog PluginLog { get; private set; } = null!; - - /*[PluginService] - [RequiredVersion("1.0")] - public ICondition Condition { get; private set; } = null!;*/ - - [PluginService] - [RequiredVersion("1.0")] - public ITargetManager TargetManager { get; private set; } = null!; - - public DalamudServices(DalamudPluginInterface pluginInterface) + public static void AddServices(ServiceManager services, DalamudPluginInterface pi) { - pluginInterface.Inject(this); - } - - public void AddServices(IServiceCollection services) - { - services - .AddSingleton(PluginInterface) - .AddSingleton(SigScanner) - .AddSingleton(Framework) - .AddSingleton(ObjectTable) - .AddSingleton(CommandManager) - .AddSingleton(ChatGui) - .AddSingleton(ClientState) - .AddSingleton(GameGui) - .AddSingleton(Hooker) - .AddSingleton(KeyState) - .AddSingleton(this) - .AddSingleton(PluginInterface.UiBuilder) - .AddSingleton(DataManager) - .AddSingleton(PluginLog) - //.AddSingleton(Condition) - .AddSingleton(TargetManager); + services.AddExistingService(pi) + .AddExistingService(pi.UiBuilder) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi) + .AddDalamudService(pi); } } \ No newline at end of file diff --git a/CustomizePlus/Core/Services/HookingService.cs b/CustomizePlus/Core/Services/HookingService.cs index a7edefa..db44507 100644 --- a/CustomizePlus/Core/Services/HookingService.cs +++ b/CustomizePlus/Core/Services/HookingService.cs @@ -140,9 +140,17 @@ public class HookingService : IDisposable return; } - var actor = (Actor)gameObjectPtr; - if (actor.Valid) - _armatureManager.OnGameObjectMove((Actor)gameObjectPtr); + try + { + var actor = (Actor)gameObjectPtr; + if (actor.Valid) + _armatureManager.OnGameObjectMove((Actor)gameObjectPtr); + } + catch (Exception ex) + { + _logger.Error($"Exception in Customize+ movement hook: {ex}"); + _gameObjectMovementHook?.Disable(); + } } public void Dispose() diff --git a/CustomizePlus/Game/Services/GameObjectService.cs b/CustomizePlus/Game/Services/GameObjectService.cs index b07f328..efa1f81 100644 --- a/CustomizePlus/Game/Services/GameObjectService.cs +++ b/CustomizePlus/Game/Services/GameObjectService.cs @@ -5,18 +5,19 @@ using CustomizePlus.Core.Data; using CustomizePlus.GameData.Data; using CustomizePlus.GameData.Services; using CustomizePlus.GameData.Extensions; +using Penumbra.GameData.Enums; namespace CustomizePlus.Game.Services; public class GameObjectService { - private readonly ActorService _actorService; + private readonly ActorManager _actorManager; private readonly IObjectTable _objectTable; private readonly ObjectManager _objectManager; - public GameObjectService(ActorService actorService, IObjectTable objectTable, ObjectManager objectManager) + public GameObjectService(ActorManager actorManager, IObjectTable objectTable, ObjectManager objectManager) { - _actorService = actorService; + _actorManager = actorManager; _objectTable = objectTable; _objectManager = objectManager; } @@ -33,7 +34,7 @@ public class GameObjectService public bool IsActorHasScalableRoot(Actor actor) { - if (!actor.Identifier(_actorService.AwaitedService, out var identifier)) + if (!actor.Identifier(_actorManager, out var identifier)) return false; return !Constants.IsInObjectTableBusyNPCRange(actor.Index.Index) diff --git a/CustomizePlus/Plugin.cs b/CustomizePlus/Plugin.cs index 5e92882..ef75de2 100644 --- a/CustomizePlus/Plugin.cs +++ b/CustomizePlus/Plugin.cs @@ -8,6 +8,7 @@ using CustomizePlus.UI; using CustomizePlus.Core; using CustomizePlus.Api.Compatibility; using CustomizePlus.Configuration.Services.Temporary; +using OtterGui.Services; namespace CustomizePlus; @@ -19,7 +20,7 @@ public sealed class Plugin : IDalamudPlugin public static readonly string Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; #endif - private readonly ServiceProvider _services; + private readonly ServiceManager _services; public static readonly Logger Logger = new(); //for loggin in static classes/methods @@ -27,18 +28,18 @@ public sealed class Plugin : IDalamudPlugin { try { - _services = ServiceManager.CreateProvider(pluginInterface, Logger); + _services = ServiceManagerBuilder.CreateProvider(pluginInterface, Logger); //temporary - var configMover = _services.GetRequiredService(); + var configMover = _services.GetService(); configMover.MoveConfigsIfNeeded(); - var v3ConfigFixer = _services.GetRequiredService(); + var v3ConfigFixer = _services.GetService(); v3ConfigFixer.FixV3ConfigIfNeeded(); - _services.GetRequiredService(); - _services.GetRequiredService(); - _services.GetRequiredService(); + _services.GetService(); + _services.GetService(); + _services.GetService(); Logger.Information($"Customize+ v{Version} [FantasiaPlus] started"); } diff --git a/CustomizePlus/Profiles/ProfileManager.cs b/CustomizePlus/Profiles/ProfileManager.cs index 34e28b8..ac275a2 100644 --- a/CustomizePlus/Profiles/ProfileManager.cs +++ b/CustomizePlus/Profiles/ProfileManager.cs @@ -22,6 +22,7 @@ using CustomizePlus.GameData.Data; using CustomizePlus.GameData.Services; using CustomizePlus.GameData.Extensions; using CustomizePlus.Profiles.Enums; +using Penumbra.GameData.Enums; namespace CustomizePlus.Profiles; @@ -35,7 +36,7 @@ public class ProfileManager : IDisposable private readonly SaveService _saveService; private readonly Logger _logger; private readonly PluginConfiguration _configuration; - private readonly ActorService _actorService; + private readonly ActorManager _actorManager; private readonly ProfileChanged _event; private readonly TemplateChanged _templateChangedEvent; private readonly ReloadEvent _reloadEvent; @@ -51,7 +52,7 @@ public class ProfileManager : IDisposable SaveService saveService, Logger logger, PluginConfiguration configuration, - ActorService actorService, + ActorManager actorManager, ProfileChanged @event, TemplateChanged templateChangedEvent, ReloadEvent reloadEvent, @@ -62,7 +63,7 @@ public class ProfileManager : IDisposable _saveService = saveService; _logger = logger; _configuration = configuration; - _actorService = actorService; + _actorManager = actorManager; _event = @event; _templateChangedEvent = templateChangedEvent; _templateChangedEvent.Subscribe(OnTemplateChange, TemplateChanged.Priority.ProfileManager); @@ -375,7 +376,7 @@ public class ProfileManager : IDisposable public void AddTemporaryProfile(Profile profile, Actor actor/*, Template template*/) { - if (!actor.Identifier(_actorService.AwaitedService, out var identifier)) + if (!actor.Identifier(_actorManager, out var identifier)) return; profile.Enabled = true; @@ -413,7 +414,7 @@ public class ProfileManager : IDisposable public void RemoveTemporaryProfile(Actor actor) { - if (!actor.Identifier(_actorService.AwaitedService, out var identifier)) + if (!actor.Identifier(_actorManager, out var identifier)) return; var profile = Profiles.FirstOrDefault(x => x.TemporaryActor == identifier && x.IsTemporary); diff --git a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs index c8c0bc4..d20e9c7 100644 --- a/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs +++ b/CustomizePlus/UI/Windows/MainWindow/Tabs/Debug/IPCTestTab.cs @@ -10,6 +10,7 @@ using CustomizePlus.Profiles; using CustomizePlus.Configuration.Helpers; using CustomizePlus.Game.Services; using CustomizePlus.GameData.Services; +using Penumbra.GameData.Actors; namespace CustomizePlus.UI.Windows.MainWindow.Tabs.Debug; @@ -20,7 +21,7 @@ public class IPCTestTab //: IDisposable private readonly PopupSystem _popupSystem; private readonly GameObjectService _gameObjectService; private readonly ObjectManager _objectManager; - private readonly ActorService _actorService; + private readonly ActorManager _actorManager; private readonly ICallGateSubscriber<(int, int)>? _getApiVersion; private readonly ICallGateSubscriber? _setCharacterProfile; @@ -41,14 +42,14 @@ public class IPCTestTab //: IDisposable PopupSystem popupSystem, ObjectManager objectManager, GameObjectService gameObjectService, - ActorService actorService) + ActorManager actorManager) { _objectTable = objectTable; _profileManager = profileManager; _popupSystem = popupSystem; _objectManager = objectManager; _gameObjectService = gameObjectService; - _actorService = actorService; + _actorManager = actorManager; _popupSystem.RegisterPopup("ipc_v4_profile_remembered", "Current profile has been copied into memory"); _popupSystem.RegisterPopup("ipc_get_profile_from_character_remembered", "GetProfileFromCharacter result has been copied into memory"); @@ -96,7 +97,7 @@ public class IPCTestTab //: IDisposable if (actors.Count == 0) return; - if (!actors[0].Item2.Identifier(_actorService.AwaitedService, out var identifier)) + if (!actors[0].Item2.Identifier(_actorManager, out var identifier)) return; var profile = _profileManager.GetEnabledProfilesByActor(identifier).FirstOrDefault(); diff --git a/submodules/Penumbra.Api b/submodules/Penumbra.Api index 80f9793..cfc5171 160000 --- a/submodules/Penumbra.Api +++ b/submodules/Penumbra.Api @@ -1 +1 @@ -Subproject commit 80f9793ef2ddaa50246b7112fde4d9b2098d8823 +Subproject commit cfc51714f74cae93608bc507775a9580cd1801de diff --git a/submodules/Penumbra.GameData b/submodules/Penumbra.GameData index ffdb966..260ac69 160000 --- a/submodules/Penumbra.GameData +++ b/submodules/Penumbra.GameData @@ -1 +1 @@ -Subproject commit ffdb966fec5a657893289e655c641ceb3af1d59f +Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4