mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Move all animation and game event hooks to own classes.
This commit is contained in:
parent
81cdcad72e
commit
da019e729d
62 changed files with 1402 additions and 1143 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit f6a8ad0f8e585408e0aa17c90209358403b52535
|
||||
Subproject commit 22ae2a8993ebf3af2313072968a44905a3fcdd2a
|
||||
|
|
@ -259,7 +259,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
|||
}
|
||||
else if (tab != TabType.None)
|
||||
{
|
||||
_communicator.SelectTab.Invoke(tab);
|
||||
_communicator.SelectTab.Invoke(tab, null);
|
||||
}
|
||||
|
||||
return PenumbraApiEc.Success;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the clicked object data if any. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class ChangedItemClick : EventWrapper<Action<MouseButton, object?>, ChangedItemClick.Priority>
|
||||
public sealed class ChangedItemClick() : EventWrapper<MouseButton, object?, ChangedItemClick.Priority>(nameof(ChangedItemClick))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -21,11 +21,4 @@ public sealed class ChangedItemClick : EventWrapper<Action<MouseButton, object?>
|
|||
/// <seealso cref="Penumbra.SetupApi"/>
|
||||
Link = 1,
|
||||
}
|
||||
|
||||
public ChangedItemClick()
|
||||
: base(nameof(ChangedItemClick))
|
||||
{ }
|
||||
|
||||
public void Invoke(MouseButton button, object? data)
|
||||
=> Invoke(this, button, data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the hovered object data if any. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class ChangedItemHover : EventWrapper<Action<object?>, ChangedItemHover.Priority>
|
||||
public sealed class ChangedItemHover() : EventWrapper<object?, ChangedItemHover.Priority>(nameof(ChangedItemHover))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -19,13 +19,6 @@ public sealed class ChangedItemHover : EventWrapper<Action<object?>, ChangedItem
|
|||
Link = 1,
|
||||
}
|
||||
|
||||
public ChangedItemHover()
|
||||
: base(nameof(ChangedItemHover))
|
||||
{ }
|
||||
|
||||
public void Invoke(object? data)
|
||||
=> Invoke(this, data);
|
||||
|
||||
public bool HasTooltip
|
||||
=> HasSubscribers;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the new collection, or null on deletions.</item>
|
||||
/// <item>Parameter is the display name for Individual collections or an empty string otherwise.</item>
|
||||
/// </list> </summary>
|
||||
public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCollection?, ModCollection?, string>, CollectionChange.Priority>
|
||||
public sealed class CollectionChange()
|
||||
: EventWrapper<CollectionType, ModCollection?, ModCollection?, string, CollectionChange.Priority>(nameof(CollectionChange))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -46,11 +47,4 @@ public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCo
|
|||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
|
||||
ModFileSystemSelector = 0,
|
||||
}
|
||||
|
||||
public CollectionChange()
|
||||
: base(nameof(CollectionChange))
|
||||
{ }
|
||||
|
||||
public void Invoke(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string displayName)
|
||||
=> Invoke(this, collectionType, oldCollection, newCollection, displayName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is whether the change was itself inherited, i.e. if it happened in a direct parent (false) or a more removed ancestor (true). </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class CollectionInheritanceChanged : EventWrapper<Action<ModCollection, bool>, CollectionInheritanceChanged.Priority>
|
||||
public sealed class CollectionInheritanceChanged()
|
||||
: EventWrapper<ModCollection, bool, CollectionInheritanceChanged.Priority>(nameof(CollectionInheritanceChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -23,11 +24,4 @@ public sealed class CollectionInheritanceChanged : EventWrapper<Action<ModCollec
|
|||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnInheritanceChange"/>
|
||||
ModFileSystemSelector = 0,
|
||||
}
|
||||
|
||||
public CollectionInheritanceChanged()
|
||||
: base(nameof(CollectionInheritanceChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModCollection collection, bool inherited)
|
||||
=> Invoke(this, collection, inherited);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,12 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the applied collection. </item>
|
||||
/// <item>Parameter is the created draw object. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class CreatedCharacterBase : EventWrapper<Action<nint, ModCollection, nint>, CreatedCharacterBase.Priority>
|
||||
public sealed class CreatedCharacterBase()
|
||||
: EventWrapper<nint, ModCollection, nint, CreatedCharacterBase.Priority>(nameof(CreatedCharacterBase))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PenumbraApi.CreatedCharacterBase"/>
|
||||
Api = int.MinValue,
|
||||
}
|
||||
|
||||
public CreatedCharacterBase()
|
||||
: base(nameof(CreatedCharacterBase))
|
||||
{ }
|
||||
|
||||
public void Invoke(nint gameObject, ModCollection appliedCollection, nint drawObject)
|
||||
=> Invoke(this, gameObject, appliedCollection, drawObject);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,12 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is a pointer to the customize array. </item>
|
||||
/// <item>Parameter is a pointer to the equip data array. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class CreatingCharacterBase : EventWrapper<Action<nint, string, nint, nint, nint>, CreatingCharacterBase.Priority>
|
||||
public sealed class CreatingCharacterBase()
|
||||
: EventWrapper<nint, string, nint, nint, nint, CreatingCharacterBase.Priority>(nameof(CreatingCharacterBase))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PenumbraApi.CreatingCharacterBase"/>
|
||||
Api = 0,
|
||||
}
|
||||
|
||||
public CreatingCharacterBase()
|
||||
: base(nameof(CreatingCharacterBase))
|
||||
{ }
|
||||
|
||||
public void Invoke(nint gameObject, string appliedCollectionName, nint modelIdAddress, nint customizeArrayAddress, nint equipDataAddress)
|
||||
=> Invoke(this, gameObject, appliedCollectionName, modelIdAddress, customizeArrayAddress, equipDataAddress);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is whether Penumbra is now Enabled (true) or Disabled (false). </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class EnabledChanged : EventWrapper<Action<bool>, EnabledChanged.Priority>
|
||||
public sealed class EnabledChanged() : EventWrapper<bool, EnabledChanged.Priority>(nameof(EnabledChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -19,11 +19,4 @@ public sealed class EnabledChanged : EventWrapper<Action<bool>, EnabledChanged.P
|
|||
/// <seealso cref="Api.DalamudSubstitutionProvider.OnEnabledChange"/>
|
||||
DalamudSubstitutionProvider = 0,
|
||||
}
|
||||
|
||||
public EnabledChanged()
|
||||
: base(nameof(EnabledChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(bool enabled)
|
||||
=> Invoke(this, enabled);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the changed mod. </item>
|
||||
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod, string?>, ModDataChanged.Priority>
|
||||
public sealed class ModDataChanged() : EventWrapper<ModDataChangeType, Mod, string?, ModDataChanged.Priority>(nameof(ModDataChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -27,11 +27,4 @@ public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod,
|
|||
/// <seealso cref="UI.ModsTab.ModPanelHeader.OnModDataChange"/>
|
||||
ModPanelHeader = 0,
|
||||
}
|
||||
|
||||
public ModDataChanged()
|
||||
: base(nameof(ModDataChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModDataChangeType changeType, Mod mod, string? oldName)
|
||||
=> Invoke(this, changeType, mod, oldName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is whether the new directory is valid. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class ModDirectoryChanged : EventWrapper<Action<string, bool>, ModDirectoryChanged.Priority>
|
||||
public sealed class ModDirectoryChanged() : EventWrapper<string, bool, ModDirectoryChanged.Priority>(nameof(ModDirectoryChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -20,11 +20,4 @@ public sealed class ModDirectoryChanged : EventWrapper<Action<string, bool>, Mod
|
|||
/// <seealso cref="UI.FileDialogService.OnModDirectoryChange"/>
|
||||
FileDialogService = 0,
|
||||
}
|
||||
|
||||
public ModDirectoryChanged()
|
||||
: base(nameof(ModDirectoryChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(string newModDirectory, bool newDirectoryValid)
|
||||
=> Invoke(this, newModDirectory, newDirectoryValid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
||||
namespace Penumbra.Communication;
|
||||
|
||||
/// <summary> Triggered whenever a new mod discovery has finished. </summary>
|
||||
public sealed class ModDiscoveryFinished : EventWrapper<Action, ModDiscoveryFinished.Priority>
|
||||
public sealed class ModDiscoveryFinished() : EventWrapper<ModDiscoveryFinished.Priority>(nameof(ModDiscoveryFinished))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -23,11 +22,4 @@ public sealed class ModDiscoveryFinished : EventWrapper<Action, ModDiscoveryFini
|
|||
/// <seealso cref="Mods.Manager.ModFileSystem.Reload"/>
|
||||
ModFileSystem = 0,
|
||||
}
|
||||
|
||||
public ModDiscoveryFinished()
|
||||
: base(nameof(ModDiscoveryFinished))
|
||||
{ }
|
||||
|
||||
public void Invoke()
|
||||
=> Invoke(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using OtterGui.Classes;
|
|||
namespace Penumbra.Communication;
|
||||
|
||||
/// <summary> Triggered whenever mods are prepared to be rediscovered. </summary>
|
||||
public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStarted.Priority>
|
||||
public sealed class ModDiscoveryStarted() : EventWrapper<ModDiscoveryStarted.Priority>(nameof(ModDiscoveryStarted))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -16,11 +16,4 @@ public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStart
|
|||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
|
||||
ModFileSystemSelector = 200,
|
||||
}
|
||||
|
||||
public ModDiscoveryStarted()
|
||||
: base(nameof(ModDiscoveryStarted))
|
||||
{ }
|
||||
|
||||
public void Invoke()
|
||||
=> Invoke(this);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the index of the changed option inside the group or -1 if it does not concern a specific option. </item>
|
||||
/// <item>Parameter is the index of the group an option was moved to. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class ModOptionChanged : EventWrapper<Action<ModOptionChangeType, Mod, int, int, int>, ModOptionChanged.Priority>
|
||||
public sealed class ModOptionChanged()
|
||||
: EventWrapper<ModOptionChangeType, Mod, int, int, int, ModOptionChanged.Priority>(nameof(ModOptionChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -29,11 +30,4 @@ public sealed class ModOptionChanged : EventWrapper<Action<ModOptionChangeType,
|
|||
/// <seealso cref="Collections.Manager.CollectionStorage.OnModOptionChange"/>
|
||||
CollectionStorage = 100,
|
||||
}
|
||||
|
||||
public ModOptionChanged()
|
||||
: base(nameof(ModOptionChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModOptionChangeType changeType, Mod mod, int groupIndex, int optionIndex, int moveToIndex)
|
||||
=> Invoke(this, changeType, mod, groupIndex, optionIndex, moveToIndex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the new directory on addition, move or reload and null on deletion. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod, DirectoryInfo?, DirectoryInfo?>, ModPathChanged.Priority>
|
||||
public sealed class ModPathChanged()
|
||||
: EventWrapper<ModPathChangeType, Mod, DirectoryInfo?, DirectoryInfo?, ModPathChanged.Priority>(nameof(ModPathChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -48,11 +49,4 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
|
|||
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeRemoval"/>
|
||||
CollectionCacheManagerRemoval = 100,
|
||||
}
|
||||
|
||||
public ModPathChanged()
|
||||
: base(nameof(ModPathChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModPathChangeType changeType, Mod mod, DirectoryInfo? oldModDirectory, DirectoryInfo? newModDirectory)
|
||||
=> Invoke(this, changeType, mod, oldModDirectory, newModDirectory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is whether the change was inherited from another collection. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class ModSettingChanged : EventWrapper<Action<ModCollection, ModSettingChange, Mod?, int, int, bool>, ModSettingChanged.Priority>
|
||||
public sealed class ModSettingChanged()
|
||||
: EventWrapper<ModCollection, ModSettingChange, Mod?, int, int, bool, ModSettingChanged.Priority>(nameof(ModSettingChanged))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -33,11 +34,4 @@ public sealed class ModSettingChanged : EventWrapper<Action<ModCollection, ModSe
|
|||
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnSettingChange"/>
|
||||
ModFileSystemSelector = 0,
|
||||
}
|
||||
|
||||
public ModSettingChanged()
|
||||
: base(nameof(ModSettingChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited)
|
||||
=> Invoke(this, collection, type, mod, oldValue, groupIdx, inherited);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,11 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the material resource handle for which the shader package has been loaded. </item>
|
||||
/// <item>Parameter is the associated game object. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class MtrlShpkLoaded : EventWrapper<Action<nint, nint>, MtrlShpkLoaded.Priority>
|
||||
public sealed class MtrlShpkLoaded() : EventWrapper<nint, nint, MtrlShpkLoaded.Priority>(nameof(MtrlShpkLoaded))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Interop.Services.SkinFixer.OnMtrlShpkLoaded"/>
|
||||
SkinFixer = 0,
|
||||
}
|
||||
|
||||
public MtrlShpkLoaded()
|
||||
: base(nameof(MtrlShpkLoaded))
|
||||
{ }
|
||||
|
||||
public void Invoke(nint mtrlResourceHandle, nint gameObject)
|
||||
=> Invoke(this, mtrlResourceHandle, gameObject);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,11 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class PostSettingsPanelDraw : EventWrapper<Action<string>, PostSettingsPanelDraw.Priority>
|
||||
public sealed class PostSettingsPanelDraw() : EventWrapper<string, PostSettingsPanelDraw.Priority>(nameof(PostSettingsPanelDraw))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PostSettingsPanelDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
|
||||
public PostSettingsPanelDraw()
|
||||
: base(nameof(PostSettingsPanelDraw))
|
||||
{ }
|
||||
|
||||
public void Invoke(string modDirectory)
|
||||
=> Invoke(this, modDirectory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,11 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class PreSettingsPanelDraw : EventWrapper<Action<string>, PreSettingsPanelDraw.Priority>
|
||||
public sealed class PreSettingsPanelDraw() : EventWrapper<string, PreSettingsPanelDraw.Priority>(nameof(PreSettingsPanelDraw))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.PenumbraApi.PreSettingsPanelDraw"/>
|
||||
Default = 0,
|
||||
}
|
||||
|
||||
public PreSettingsPanelDraw()
|
||||
: base(nameof(PreSettingsPanelDraw))
|
||||
{ }
|
||||
|
||||
public void Invoke(string modDirectory)
|
||||
=> Invoke(this, modDirectory);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the old redirection path for Replaced, or empty. </item>
|
||||
/// <item>Parameter is the mod responsible for the new redirection if any. </item>
|
||||
/// </list> </summary>
|
||||
public sealed class ResolvedFileChanged : EventWrapper<Action<ModCollection, ResolvedFileChanged.Type, Utf8GamePath, FullPath, FullPath, IMod?>,
|
||||
ResolvedFileChanged.Priority>
|
||||
public sealed class ResolvedFileChanged()
|
||||
: EventWrapper<ModCollection, ResolvedFileChanged.Type, Utf8GamePath, FullPath, FullPath, IMod?, ResolvedFileChanged.Priority>(
|
||||
nameof(ResolvedFileChanged))
|
||||
{
|
||||
public enum Type
|
||||
{
|
||||
|
|
@ -29,14 +30,7 @@ public sealed class ResolvedFileChanged : EventWrapper<Action<ModCollection, Res
|
|||
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Api.DalamudSubstitutionProvider.OnResolvedFileChanged"/>
|
||||
/// <seealso cref="Api.DalamudSubstitutionProvider.OnResolvedFileChange"/>
|
||||
DalamudSubstitutionProvider = 0,
|
||||
}
|
||||
|
||||
public ResolvedFileChanged()
|
||||
: base(nameof(ResolvedFileChanged))
|
||||
{ }
|
||||
|
||||
public void Invoke(ModCollection collection, Type type, Utf8GamePath key, FullPath value, FullPath old, IMod? mod)
|
||||
=> Invoke(this, collection, type, key, value, old, mod);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,18 +11,11 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is the selected mod, if any. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class SelectTab : EventWrapper<Action<TabType, Mod?>, SelectTab.Priority>
|
||||
public sealed class SelectTab() : EventWrapper<TabType, Mod?, SelectTab.Priority>(nameof(SelectTab))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="UI.Tabs.ConfigTabBar.OnSelectTab"/>
|
||||
ConfigTabBar = 0,
|
||||
}
|
||||
|
||||
public SelectTab()
|
||||
: base(nameof(SelectTab))
|
||||
{ }
|
||||
|
||||
public void Invoke(TabType tab = TabType.None, Mod? mod = null)
|
||||
=> Invoke(this, tab, mod);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ namespace Penumbra.Communication;
|
|||
/// <item>Parameter is whether the mod was newly created.</item>
|
||||
/// <item>Parameter is whether the mod was deleted.</item>
|
||||
/// </list> </summary>
|
||||
public sealed class TemporaryGlobalModChange : EventWrapper<Action<TemporaryMod, bool, bool>, TemporaryGlobalModChange.Priority>
|
||||
public sealed class TemporaryGlobalModChange()
|
||||
: EventWrapper<TemporaryMod, bool, bool, TemporaryGlobalModChange.Priority>(nameof(TemporaryGlobalModChange))
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
|
|
@ -20,11 +21,4 @@ public sealed class TemporaryGlobalModChange : EventWrapper<Action<TemporaryMod,
|
|||
/// <seealso cref="Collections.Manager.TempCollectionManager.OnGlobalModChange"/>
|
||||
TempCollectionManager = 0,
|
||||
}
|
||||
|
||||
public TemporaryGlobalModChange()
|
||||
: base(nameof(TemporaryGlobalModChange))
|
||||
{ }
|
||||
|
||||
public void Invoke(TemporaryMod temporaryMod, bool newlyCreated, bool deleted)
|
||||
=> Invoke(this, temporaryMod, newlyCreated, deleted);
|
||||
}
|
||||
|
|
|
|||
117
Penumbra/Interop/GameState.cs
Normal file
117
Penumbra/Interop/GameState.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.String.Classes;
|
||||
|
||||
namespace Penumbra.Interop;
|
||||
|
||||
public class GameState : IService
|
||||
{
|
||||
#region Last Game Object
|
||||
|
||||
private readonly ThreadLocal<Queue<nint>> _lastGameObject = new(() => new Queue<nint>());
|
||||
|
||||
public nint LastGameObject
|
||||
=> _lastGameObject.IsValueCreated && _lastGameObject.Value!.Count > 0 ? _lastGameObject.Value.Peek() : nint.Zero;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public unsafe void QueueGameObject(GameObject* gameObject)
|
||||
=> QueueGameObject((nint)gameObject);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void QueueGameObject(nint gameObject)
|
||||
=> _lastGameObject.Value!.Enqueue(gameObject);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void DequeueGameObject()
|
||||
=> _lastGameObject.Value!.TryDequeue(out _);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Animation Data
|
||||
|
||||
private readonly ThreadLocal<ResolveData> _animationLoadData = new(() => ResolveData.Invalid, true);
|
||||
|
||||
public ResolveData AnimationData
|
||||
=> _animationLoadData.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public ResolveData SetAnimationData(ResolveData data)
|
||||
{
|
||||
var old = _animationLoadData.Value;
|
||||
_animationLoadData.Value = data;
|
||||
return old;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void RestoreAnimationData(ResolveData old)
|
||||
=> _animationLoadData.Value = old;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sound Data
|
||||
|
||||
private readonly ThreadLocal<ResolveData> _characterSoundData = new(() => ResolveData.Invalid, true);
|
||||
|
||||
public ResolveData SoundData
|
||||
=> _animationLoadData.Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public ResolveData SetSoundData(ResolveData data)
|
||||
{
|
||||
var old = _characterSoundData.Value;
|
||||
_characterSoundData.Value = data;
|
||||
return old;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void RestoreSoundData(ResolveData old)
|
||||
=> _characterSoundData.Value = old;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary> Return the correct resolve data from the stored data. </summary>
|
||||
public unsafe bool HandleFiles(CollectionResolver resolver, ResourceType type, Utf8GamePath _, out ResolveData resolveData)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ResourceType.Scd:
|
||||
if (_characterSoundData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _characterSoundData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_animationLoadData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _animationLoadData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Tmb:
|
||||
case ResourceType.Pap:
|
||||
case ResourceType.Avfx:
|
||||
case ResourceType.Atex:
|
||||
if (_animationLoadData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _animationLoadData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var lastObj = LastGameObject;
|
||||
if (lastObj != nint.Zero)
|
||||
{
|
||||
resolveData = resolver.IdentifyCollection((GameObject*)lastObj, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
resolveData = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
54
Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs
Normal file
54
Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Called for some sound effects caused by animations or VFX. </summary>
|
||||
public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSoundPlay.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Apricot Listener Sound Play", Sigs.ApricotListenerSoundPlay, Detour, true);
|
||||
}
|
||||
|
||||
public delegate nint Delegate(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private nint Detour(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Apricot Listener Sound Play] Invoked on 0x{a1:X} with {a2}, {a3}, {a4}, {a5}, {a6}.");
|
||||
if (a6 == nint.Zero)
|
||||
return Task.Result.Original(a1, a2, a3, a4, a5, a6);
|
||||
|
||||
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
|
||||
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
|
||||
var newData = ResolveData.Invalid;
|
||||
if (gameObject != null)
|
||||
{
|
||||
newData = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for VfxListenner we can obtain the associated draw object as its first member,
|
||||
// if the object has different type, drawObject will contain other values or garbage,
|
||||
// but only be used in a dictionary pointer lookup, so this does not hurt.
|
||||
var drawObject = ((DrawObject**)a6)[1];
|
||||
if (drawObject != null)
|
||||
newData = _collectionResolver.IdentifyCollection(drawObject, true);
|
||||
}
|
||||
|
||||
var last = _state.SetAnimationData(newData);
|
||||
var ret = Task.Result.Original(a1, a2, a3, a4, a5, a6);
|
||||
_state.RestoreAnimationData(last);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// Probably used when the base idle animation gets loaded.
|
||||
/// Make it aware of the correct collection to load the correct pap files.
|
||||
/// </summary>
|
||||
public sealed unsafe class CharacterBaseLoadAnimation : FastHook<CharacterBaseLoadAnimation.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
|
||||
public CharacterBaseLoadAnimation(HookManager hooks, GameState state, CollectionResolver collectionResolver,
|
||||
DrawObjectState drawObjectState)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
_drawObjectState = drawObjectState;
|
||||
Task = hooks.CreateHook<Delegate>("CharacterBase Load Animation", Sigs.CharacterBaseLoadAnimation, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(DrawObject* drawBase);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(DrawObject* drawObject)
|
||||
{
|
||||
var lastObj = _state.LastGameObject;
|
||||
if (lastObj == nint.Zero && _drawObjectState.TryGetValue((nint)drawObject, out var p))
|
||||
lastObj = p.Item1;
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)lastObj, true));
|
||||
Penumbra.Log.Excessive($"[CharacterBase Load Animation] Invoked on {(nint)drawObject:X}");
|
||||
Task.Result.Original(drawObject);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
44
Penumbra/Interop/Hooks/Animation/Dismount.cs
Normal file
44
Penumbra/Interop/Hooks/Animation/Dismount.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Called for some animations when dismounting. </summary>
|
||||
public sealed unsafe class Dismount : FastHook<Dismount.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public Dismount(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Dismount", Sigs.Dismount, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(nint a1, nint a2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(nint a1, nint a2)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Dismount] Invoked on {a1:X} with {a2:X}.");
|
||||
if (a1 == nint.Zero)
|
||||
{
|
||||
Task.Result.Original(a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
var gameObject = *(GameObject**)(a1 + 8);
|
||||
if (gameObject == null)
|
||||
{
|
||||
Task.Result.Original(a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(gameObject, true));
|
||||
Task.Result.Original(a1, a2);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
38
Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs
Normal file
38
Penumbra/Interop/Hooks/Animation/LoadAreaVfx.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Load a ground-based area VFX. </summary>
|
||||
public sealed unsafe class LoadAreaVfx : FastHook<LoadAreaVfx.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public LoadAreaVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Load Area VFX", Sigs.LoadAreaVfx, Detour, true);
|
||||
}
|
||||
|
||||
public delegate nint Delegate(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private nint Detour(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3)
|
||||
{
|
||||
var newData = caster != null
|
||||
? _collectionResolver.IdentifyCollection(caster, true)
|
||||
: ResolveData.Invalid;
|
||||
|
||||
var last = _state.SetAnimationData(newData);
|
||||
var ret = Task.Result.Original(vfxId, pos, caster, unk1, unk2, unk3);
|
||||
Penumbra.Log.Excessive(
|
||||
$"[Load Area VFX] Invoked with {vfxId}, [{pos[0]} {pos[1]} {pos[2]}], 0x{(nint)caster:X}, {unk1}, {unk2}, {unk3} -> 0x{ret:X}.");
|
||||
_state.RestoreAnimationData(last);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
34
Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs
Normal file
34
Penumbra/Interop/Hooks/Animation/LoadCharacterSound.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
||||
public sealed unsafe class LoadCharacterSound : FastHook<LoadCharacterSound.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public LoadCharacterSound(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Load Character Sound",
|
||||
(nint)FFXIVClientStructs.FFXIV.Client.Game.Character.Character.VfxContainer.MemberFunctionPointers.LoadCharacterSound, Detour,
|
||||
true);
|
||||
}
|
||||
|
||||
public delegate nint Delegate(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private nint Detour(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7)
|
||||
{
|
||||
var character = *(GameObject**)(container + 8);
|
||||
var last = _state.SetSoundData(_collectionResolver.IdentifyCollection(character, true));
|
||||
var ret = Task.Result.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
||||
Penumbra.Log.Excessive($"[Load Character Sound] Invoked with {container:X} {unk1} {unk2} {unk3} {unk4} {unk5} {unk6} {unk7} -> {ret}.");
|
||||
_state.RestoreSoundData(last);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
65
Penumbra/Interop/Hooks/Animation/LoadCharacterVfx.cs
Normal file
65
Penumbra/Interop/Hooks/Animation/LoadCharacterVfx.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Load a VFX specifically for a character. </summary>
|
||||
public sealed unsafe class LoadCharacterVfx : FastHook<LoadCharacterVfx.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
public LoadCharacterVfx(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
_objects = objects;
|
||||
Task = hooks.CreateHook<Delegate>("Load Character VFX", Sigs.LoadCharacterVfx, Detour, true);
|
||||
}
|
||||
|
||||
public delegate nint Delegate(byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private nint Detour(byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4)
|
||||
{
|
||||
var newData = ResolveData.Invalid;
|
||||
if (vfxParams != null && vfxParams->GameObjectId != unchecked((uint)-1))
|
||||
{
|
||||
var obj = vfxParams->GameObjectType switch
|
||||
{
|
||||
0 => _objects.SearchById(vfxParams->GameObjectId),
|
||||
2 => _objects[(int)vfxParams->GameObjectId],
|
||||
4 => GetOwnedObject(vfxParams->GameObjectId),
|
||||
_ => null,
|
||||
};
|
||||
newData = obj != null
|
||||
? _collectionResolver.IdentifyCollection((GameObject*)obj.Address, true)
|
||||
: ResolveData.Invalid;
|
||||
}
|
||||
|
||||
var last = _state.SetAnimationData(newData);
|
||||
var ret = Task.Result.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
|
||||
Penumbra.Log.Excessive(
|
||||
$"[Load Character VFX] Invoked with {new ByteString(vfxPath)}, 0x{vfxParams->GameObjectId:X}, {vfxParams->TargetCount}, {unk1}, {unk2}, {unk3}, {unk4} -> 0x{ret:X}.");
|
||||
_state.RestoreAnimationData(last);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Search an object by its id, then get its minion/mount/ornament. </summary>
|
||||
private Dalamud.Game.ClientState.Objects.Types.GameObject? GetOwnedObject(uint id)
|
||||
{
|
||||
var owner = _objects.SearchById(id);
|
||||
if (owner == null)
|
||||
return null;
|
||||
|
||||
var idx = ((GameObject*)owner.Address)->ObjectIndex;
|
||||
return _objects[idx + 1];
|
||||
}
|
||||
}
|
||||
71
Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs
Normal file
71
Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary>
|
||||
/// The timeline object loads the requested .tmb and .pap files. The .tmb files load the respective .avfx files.
|
||||
/// We can obtain the associated game object from the timelines 28'th vfunc and use that to apply the correct collection.
|
||||
/// </summary>
|
||||
public sealed unsafe class LoadTimelineResources : FastHook<LoadTimelineResources.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly ICondition _conditions;
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
public LoadTimelineResources(HookManager hooks, GameState state, CollectionResolver collectionResolver, ICondition conditions,
|
||||
IObjectTable objects)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
_conditions = conditions;
|
||||
_objects = objects;
|
||||
Task = hooks.CreateHook<Delegate>("Load Timeline Resources", Sigs.LoadTimelineResources, Detour, true);
|
||||
}
|
||||
|
||||
public delegate ulong Delegate(nint timeline);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private ulong Detour(nint timeline)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Load Timeline Resources] Invoked on {timeline:X}.");
|
||||
// Do not check timeline loading in cutscenes.
|
||||
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
||||
return Task.Result.Original(timeline);
|
||||
|
||||
var last = _state.SetAnimationData(GetDataFromTimeline(_objects, _collectionResolver, timeline));
|
||||
var ret = Task.Result.Original(timeline);
|
||||
_state.RestoreAnimationData(last);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Use timelines vfuncs to obtain the associated game object. </summary>
|
||||
public static ResolveData GetDataFromTimeline(IObjectTable objects, CollectionResolver resolver, nint timeline)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (timeline != nint.Zero)
|
||||
{
|
||||
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
|
||||
var idx = getGameObjectIdx(timeline);
|
||||
if (idx >= 0 && idx < objects.Length)
|
||||
{
|
||||
var obj = (GameObject*)objects.GetObjectAddress(idx);
|
||||
return obj != null ? resolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Error getting timeline data for 0x{timeline:X}:\n{e}");
|
||||
}
|
||||
|
||||
return ResolveData.Invalid;
|
||||
}
|
||||
}
|
||||
30
Penumbra/Interop/Hooks/Animation/PlayFootstep.cs
Normal file
30
Penumbra/Interop/Hooks/Animation/PlayFootstep.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
public sealed unsafe class PlayFootstep : FastHook<PlayFootstep.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public PlayFootstep(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Play Footstep", Sigs.FootStepSound, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(GameObject* gameObject, int id, int unk);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(GameObject* gameObject, int id, int unk)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Play Footstep] Invoked on 0x{(nint)gameObject:X} with {id}, {unk}.");
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(gameObject, true));
|
||||
Task.Result.Original(gameObject, id, unk);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
35
Penumbra/Interop/Hooks/Animation/ScheduleClipUpdate.cs
Normal file
35
Penumbra/Interop/Hooks/Animation/ScheduleClipUpdate.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Called when some action timelines update. </summary>
|
||||
public sealed unsafe class ScheduleClipUpdate : FastHook<ScheduleClipUpdate.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
public ScheduleClipUpdate(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
_objects = objects;
|
||||
Task = hooks.CreateHook<Delegate>("Schedule Clip Update", Sigs.ScheduleClipUpdate, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(ClipScheduler* x);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(ClipScheduler* clipScheduler)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Schedule Clip Update] Invoked on {(nint)clipScheduler:X}.");
|
||||
var last = _state.SetAnimationData(
|
||||
LoadTimelineResources.GetDataFromTimeline(_objects, _collectionResolver, clipScheduler->SchedulerTimeline));
|
||||
Task.Result.Original(clipScheduler);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
32
Penumbra/Interop/Hooks/Animation/SomeActionLoad.cs
Normal file
32
Penumbra/Interop/Hooks/Animation/SomeActionLoad.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Seems to load character actions when zoning or changing class, maybe. </summary>
|
||||
public sealed unsafe class SomeActionLoad : FastHook<SomeActionLoad.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public SomeActionLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Some Action Load", Sigs.LoadSomeAction, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(ActionTimelineManager* timelineManager);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(ActionTimelineManager* timelineManager)
|
||||
{
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)timelineManager->Parent, true));
|
||||
Penumbra.Log.Excessive($"[Some Action Load] Invoked on 0x{(nint)timelineManager:X}.");
|
||||
Task.Result.Original(timelineManager);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
31
Penumbra/Interop/Hooks/Animation/SomeMountAnimation.cs
Normal file
31
Penumbra/Interop/Hooks/Animation/SomeMountAnimation.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Called for some animations when mounted or mounting. </summary>
|
||||
public sealed unsafe class SomeMountAnimation : FastHook<SomeMountAnimation.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public SomeMountAnimation(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Some Mount Animation", Sigs.UnkMountAnimation, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(DrawObject* drawObject, uint unk1, byte unk2, uint unk3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(DrawObject* drawObject, uint unk1, byte unk2, uint unk3)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Some Mount Animation] Invoked on {(nint)drawObject:X} with {unk1}, {unk2}, {unk3}.");
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(drawObject, true));
|
||||
Task.Result.Original(drawObject, unk1, unk2, unk3);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
46
Penumbra/Interop/Hooks/Animation/SomePapLoad.cs
Normal file
46
Penumbra/Interop/Hooks/Animation/SomePapLoad.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Unknown what exactly this is, but it seems to load a bunch of paps. </summary>
|
||||
public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly IObjectTable _objects;
|
||||
|
||||
public SomePapLoad(HookManager hooks, GameState state, CollectionResolver collectionResolver, IObjectTable objects)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
_objects = objects;
|
||||
Task = hooks.CreateHook<Delegate>("Some PAP Load", Sigs.LoadSomePap, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(nint a1, int a2, nint a3, int a4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(nint a1, int a2, nint a3, int a4)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Some PAP Load] Invoked on 0x{a1:X} with {a2}, {a3}, {a4}.");
|
||||
var timelinePtr = a1 + Offsets.TimeLinePtr;
|
||||
if (timelinePtr != nint.Zero)
|
||||
{
|
||||
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
||||
if (actorIdx >= 0 && actorIdx < _objects.Length)
|
||||
{
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection((GameObject*)_objects.GetObjectAddress(actorIdx),
|
||||
true));
|
||||
Task.Result.Original(a1, a2, a3, a4);
|
||||
_state.RestoreAnimationData(last);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Task.Result.Original(a1, a2, a3, a4);
|
||||
}
|
||||
}
|
||||
31
Penumbra/Interop/Hooks/Animation/SomeParasolAnimation.cs
Normal file
31
Penumbra/Interop/Hooks/Animation/SomeParasolAnimation.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
|
||||
namespace Penumbra.Interop.Hooks.Animation;
|
||||
|
||||
/// <summary> Called for some animations when using a Parasol. </summary>
|
||||
public sealed unsafe class SomeParasolAnimation : FastHook<SomeParasolAnimation.Delegate>
|
||||
{
|
||||
private readonly GameState _state;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public SomeParasolAnimation(HookManager hooks, GameState state, CollectionResolver collectionResolver)
|
||||
{
|
||||
_state = state;
|
||||
_collectionResolver = collectionResolver;
|
||||
Task = hooks.CreateHook<Delegate>("Some Parasol Animation", Sigs.UnkParasolAnimation, Detour, true);
|
||||
}
|
||||
|
||||
public delegate void Delegate(DrawObject* drawObject, int unk1);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(DrawObject* drawObject, int unk1)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[Some Mount Animation] Invoked on {(nint)drawObject:X} with {unk1}.");
|
||||
var last = _state.SetAnimationData(_collectionResolver.IdentifyCollection(drawObject, true));
|
||||
Task.Result.Original(drawObject, unk1);
|
||||
_state.RestoreAnimationData(last);
|
||||
}
|
||||
}
|
||||
49
Penumbra/Interop/Hooks/CharacterBaseDestructor.cs
Normal file
49
Penumbra/Interop/Hooks/CharacterBaseDestructor.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.UI.AdvancedWindow;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class CharacterBaseDestructor : EventWrapperPtr<CharacterBase, CharacterBaseDestructor.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.DrawObjectState"/>
|
||||
DrawObjectState = 0,
|
||||
|
||||
/// <seealso cref="ModEditWindow.MtrlTab"/>
|
||||
MtrlTab = -1000,
|
||||
}
|
||||
|
||||
public CharacterBaseDestructor(HookManager hooks)
|
||||
: base("Destroy CharacterBase")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _task;
|
||||
|
||||
public nint Address
|
||||
=> (nint)CharacterBase.MemberFunctionPointers.Destroy;
|
||||
|
||||
public void Enable()
|
||||
=> _task.Result.Enable();
|
||||
|
||||
public void Disable()
|
||||
=> _task.Result.Disable();
|
||||
|
||||
public Task Awaiter
|
||||
=> _task;
|
||||
|
||||
public bool Finished
|
||||
=> _task.IsCompletedSuccessfully;
|
||||
|
||||
private delegate nint Delegate(CharacterBase* characterBase);
|
||||
|
||||
private nint Detour(CharacterBase* characterBase)
|
||||
{
|
||||
Penumbra.Log.Verbose($"[{Name}] Triggered with 0x{(nint)characterBase:X}.");
|
||||
Invoke(characterBase);
|
||||
return _task.Result.Original(characterBase);
|
||||
}
|
||||
}
|
||||
49
Penumbra/Interop/Hooks/CharacterDestructor.cs
Normal file
49
Penumbra/Interop/Hooks/CharacterDestructor.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class CharacterDestructor : EventWrapperPtr<Character, CharacterDestructor.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.CutsceneService"/>
|
||||
CutsceneService = 0,
|
||||
|
||||
/// <seealso cref="PathResolving.IdentifiedCollectionCache"/>
|
||||
IdentifiedCollectionCache = 0,
|
||||
}
|
||||
|
||||
public CharacterDestructor(HookManager hooks)
|
||||
: base("Character Destructor")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.CharacterDestructor, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _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);
|
||||
}
|
||||
}
|
||||
47
Penumbra/Interop/Hooks/CopyCharacter.cs
Normal file
47
Penumbra/Interop/Hooks/CopyCharacter.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class CopyCharacter : EventWrapperPtr<Character, Character, CopyCharacter.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.CutsceneService"/>
|
||||
CutsceneService = 0,
|
||||
}
|
||||
|
||||
public CopyCharacter(HookManager hooks)
|
||||
: base("Copy Character")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _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);
|
||||
}
|
||||
}
|
||||
74
Penumbra/Interop/Hooks/CreateCharacterBase.cs
Normal file
74
Penumbra/Interop/Hooks/CreateCharacterBase.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class CreateCharacterBase : EventWrapperPtr<ModelCharaId, CustomizeArray, CharacterArmor, CreateCharacterBase.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.MetaState"/>
|
||||
MetaState = 0,
|
||||
}
|
||||
|
||||
public CreateCharacterBase(HookManager hooks)
|
||||
: base("Create CharacterBase")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _task;
|
||||
|
||||
public nint Address
|
||||
=> (nint)CharacterBase.MemberFunctionPointers.Create;
|
||||
|
||||
public void Enable()
|
||||
=> _task.Result.Enable();
|
||||
|
||||
public void Disable()
|
||||
=> _task.Result.Disable();
|
||||
|
||||
public Task Awaiter
|
||||
=> _task;
|
||||
|
||||
public bool Finished
|
||||
=> _task.IsCompletedSuccessfully;
|
||||
|
||||
private delegate CharacterBase* Delegate(ModelCharaId model, CustomizeArray* customize, CharacterArmor* equipment, byte unk);
|
||||
|
||||
private CharacterBase* Detour(ModelCharaId model, CustomizeArray* customize, CharacterArmor* equipment, byte unk)
|
||||
{
|
||||
Penumbra.Log.Verbose($"[{Name}] Triggered with model: {model.Id}, customize: 0x{(nint) customize:X}, equipment: 0x{(nint)equipment:X}, unk: {unk}.");
|
||||
Invoke(&model, customize, equipment);
|
||||
var ret = _task.Result.Original(model, customize, equipment, unk);
|
||||
_postEvent.Invoke(model, customize, equipment, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Subscribe(ActionPtr234<ModelCharaId, CustomizeArray, CharacterArmor, CharacterBase> subscriber, PostEvent.Priority priority)
|
||||
=> _postEvent.Subscribe(subscriber, priority);
|
||||
|
||||
public void Unsubscribe(ActionPtr234<ModelCharaId, CustomizeArray, CharacterArmor, CharacterBase> subscriber)
|
||||
=> _postEvent.Unsubscribe(subscriber);
|
||||
|
||||
|
||||
private readonly PostEvent _postEvent = new("Created CharacterBase");
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_postEvent.Dispose();
|
||||
}
|
||||
|
||||
public class PostEvent(string name) : EventWrapperPtr234<ModelCharaId, CustomizeArray, CharacterArmor, CharacterBase, PostEvent.Priority>(name)
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.DrawObjectState"/>
|
||||
DrawObjectState = 0,
|
||||
|
||||
/// <seealso cref="PathResolving.MetaState"/>
|
||||
MetaState = 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Penumbra/Interop/Hooks/DebugHook.cs
Normal file
43
Penumbra/Interop/Hooks/DebugHook.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
#if DEBUG
|
||||
public sealed unsafe class DebugHook : IHookService
|
||||
{
|
||||
public const string Signature = "";
|
||||
|
||||
public DebugHook(HookManager hooks)
|
||||
{
|
||||
if (Signature.Length > 0)
|
||||
_task = hooks.CreateHook<Delegate>("Debug Hook", Signature, Detour, true);
|
||||
}
|
||||
|
||||
private readonly Task<Hook<Delegate>>? _task;
|
||||
|
||||
public nint Address
|
||||
=> _task?.Result.Address ?? nint.Zero;
|
||||
|
||||
public void Enable()
|
||||
=> _task?.Result.Enable();
|
||||
|
||||
public void Disable()
|
||||
=> _task?.Result.Disable();
|
||||
|
||||
public Task Awaiter
|
||||
=> _task ?? Task.CompletedTask;
|
||||
|
||||
public bool Finished
|
||||
=> _task?.IsCompletedSuccessfully ?? true;
|
||||
|
||||
private delegate nint Delegate(ResourceHandle* resourceHandle);
|
||||
|
||||
private nint Detour(ResourceHandle* resourceHandle)
|
||||
{
|
||||
Penumbra.Log.Information($"[Debug Hook] Triggered with 0x{(nint)resourceHandle:X}.");
|
||||
return _task!.Result.Original(resourceHandle);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
48
Penumbra/Interop/Hooks/EnableDraw.cs
Normal file
48
Penumbra/Interop/Hooks/EnableDraw.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
/// <summary>
|
||||
/// EnableDraw is what creates DrawObjects for gameObjects,
|
||||
/// so we always keep track of the current GameObject to be able to link it to the DrawObject.
|
||||
/// </summary>
|
||||
public sealed unsafe class EnableDraw : IHookService
|
||||
{
|
||||
private readonly Task<Hook<Delegate>> _task;
|
||||
private readonly GameState _state;
|
||||
|
||||
public EnableDraw(HookManager hooks, GameState state)
|
||||
{
|
||||
_state = state;
|
||||
_task = hooks.CreateHook<Delegate>("Enable Draw", Sigs.EnableDraw, Detour, true);
|
||||
}
|
||||
|
||||
private delegate void Delegate(GameObject* gameObject);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private void Detour(GameObject* gameObject)
|
||||
{
|
||||
_state.QueueGameObject(gameObject);
|
||||
Penumbra.Log.Excessive($"[Enable Draw] Invoked on 0x{(nint) gameObject:X}.");
|
||||
_task.Result.Original.Invoke(gameObject);
|
||||
_state.DequeueGameObject();
|
||||
}
|
||||
|
||||
public Task Awaiter
|
||||
=> _task;
|
||||
|
||||
public bool Finished
|
||||
=> _task.IsCompletedSuccessfully;
|
||||
|
||||
public nint Address
|
||||
=> _task.Result.Address;
|
||||
|
||||
public void Enable()
|
||||
=> _task.Result.Enable();
|
||||
|
||||
public void Disable()
|
||||
=> _task.Result.Disable();
|
||||
}
|
||||
50
Penumbra/Interop/Hooks/ResourceHandleDestructor.cs
Normal file
50
Penumbra/Interop/Hooks/ResourceHandleDestructor.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using Dalamud.Hooking;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class ResourceHandleDestructor : EventWrapperPtr<ResourceHandle, ResourceHandleDestructor.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.SubfileHelper"/>
|
||||
SubfileHelper,
|
||||
|
||||
/// <seealso cref="SkinFixer"/>
|
||||
SkinFixer,
|
||||
}
|
||||
|
||||
public ResourceHandleDestructor(HookManager hooks)
|
||||
: base("Destroy ResourceHandle")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Sigs.ResourceHandleDestructor, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _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 nint Delegate(ResourceHandle* resourceHandle);
|
||||
|
||||
private nint Detour(ResourceHandle* resourceHandle)
|
||||
{
|
||||
Penumbra.Log.Verbose($"[{Name}] Triggered with 0x{(nint)resourceHandle:X}.");
|
||||
Invoke(resourceHandle);
|
||||
return _task.Result.Original(resourceHandle);
|
||||
}
|
||||
}
|
||||
71
Penumbra/Interop/Hooks/WeaponReload.cs
Normal file
71
Penumbra/Interop/Hooks/WeaponReload.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using Dalamud.Hooking;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Hooks;
|
||||
|
||||
public sealed unsafe class WeaponReload : EventWrapperPtr<DrawDataContainer, Character, CharacterWeapon, WeaponReload.Priority>, IHookService
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.DrawObjectState"/>
|
||||
DrawObjectState = 0,
|
||||
}
|
||||
|
||||
public WeaponReload(HookManager hooks)
|
||||
: base("Reload Weapon")
|
||||
=> _task = hooks.CreateHook<Delegate>(Name, Address, Detour, true);
|
||||
|
||||
private readonly Task<Hook<Delegate>> _task;
|
||||
|
||||
public nint Address
|
||||
=> (nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon;
|
||||
|
||||
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(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g);
|
||||
|
||||
private void Detour(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g)
|
||||
{
|
||||
var gameObject = drawData->Parent;
|
||||
Penumbra.Log.Verbose($"[{Name}] Triggered with drawData: 0x{(nint)drawData:X}, {slot}, {weapon}, {d}, {e}, {f}, {g}.");
|
||||
Invoke(drawData, gameObject, (CharacterWeapon*)(&weapon));
|
||||
_task.Result.Original(drawData, slot, weapon, d, e, f, g);
|
||||
_postEvent.Invoke(drawData, gameObject);
|
||||
}
|
||||
|
||||
public void Subscribe(ActionPtr<DrawDataContainer, Character> subscriber, PostEvent.Priority priority)
|
||||
=> _postEvent.Subscribe(subscriber, priority);
|
||||
|
||||
public void Unsubscribe(ActionPtr<DrawDataContainer, Character> subscriber)
|
||||
=> _postEvent.Unsubscribe(subscriber);
|
||||
|
||||
|
||||
private readonly PostEvent _postEvent = new("Created CharacterBase");
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
_postEvent.Dispose();
|
||||
}
|
||||
|
||||
public class PostEvent(string name) : EventWrapperPtr<DrawDataContainer, Character, PostEvent.Priority>(name)
|
||||
{
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="PathResolving.DrawObjectState"/>
|
||||
DrawObjectState = 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,423 +0,0 @@
|
|||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
||||
public unsafe class AnimationHookService : IDisposable
|
||||
{
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
private readonly CollectionResolver _resolver;
|
||||
private readonly ICondition _conditions;
|
||||
|
||||
private readonly ThreadLocal<ResolveData> _animationLoadData = new(() => ResolveData.Invalid, true);
|
||||
private readonly ThreadLocal<ResolveData> _characterSoundData = new(() => ResolveData.Invalid, true);
|
||||
|
||||
public AnimationHookService(PerformanceTracker performance, IObjectTable objects, CollectionResolver collectionResolver,
|
||||
DrawObjectState drawObjectState, CollectionResolver resolver, ICondition conditions, IGameInteropProvider interop)
|
||||
{
|
||||
_performance = performance;
|
||||
_objects = objects;
|
||||
_collectionResolver = collectionResolver;
|
||||
_drawObjectState = drawObjectState;
|
||||
_resolver = resolver;
|
||||
_conditions = conditions;
|
||||
|
||||
interop.InitializeFromAttributes(this);
|
||||
_loadCharacterSoundHook =
|
||||
interop.HookFromAddress<LoadCharacterSound>(
|
||||
(nint)FFXIVClientStructs.FFXIV.Client.Game.Character.Character.VfxContainer.MemberFunctionPointers.LoadCharacterSound,
|
||||
LoadCharacterSoundDetour);
|
||||
|
||||
_loadCharacterSoundHook.Enable();
|
||||
_loadTimelineResourcesHook.Enable();
|
||||
_characterBaseLoadAnimationHook.Enable();
|
||||
_loadSomePapHook.Enable();
|
||||
_someActionLoadHook.Enable();
|
||||
_loadCharacterVfxHook.Enable();
|
||||
_loadAreaVfxHook.Enable();
|
||||
_scheduleClipUpdateHook.Enable();
|
||||
_unkMountAnimationHook.Enable();
|
||||
_unkParasolAnimationHook.Enable();
|
||||
_dismountHook.Enable();
|
||||
_apricotListenerSoundPlayHook.Enable();
|
||||
_footStepHook.Enable();
|
||||
}
|
||||
|
||||
public bool HandleFiles(ResourceType type, Utf8GamePath _, out ResolveData resolveData)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case ResourceType.Scd:
|
||||
if (_characterSoundData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _characterSoundData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_animationLoadData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _animationLoadData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case ResourceType.Tmb:
|
||||
case ResourceType.Pap:
|
||||
case ResourceType.Avfx:
|
||||
case ResourceType.Atex:
|
||||
if (_animationLoadData is { IsValueCreated: true, Value.Valid: true })
|
||||
{
|
||||
resolveData = _animationLoadData.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
var lastObj = _drawObjectState.LastGameObject;
|
||||
if (lastObj != nint.Zero)
|
||||
{
|
||||
resolveData = _resolver.IdentifyCollection((GameObject*)lastObj, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
resolveData = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loadCharacterSoundHook.Dispose();
|
||||
_loadTimelineResourcesHook.Dispose();
|
||||
_characterBaseLoadAnimationHook.Dispose();
|
||||
_loadSomePapHook.Dispose();
|
||||
_someActionLoadHook.Dispose();
|
||||
_loadCharacterVfxHook.Dispose();
|
||||
_loadAreaVfxHook.Dispose();
|
||||
_scheduleClipUpdateHook.Dispose();
|
||||
_unkMountAnimationHook.Dispose();
|
||||
_unkParasolAnimationHook.Dispose();
|
||||
_dismountHook.Dispose();
|
||||
_apricotListenerSoundPlayHook.Dispose();
|
||||
_footStepHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
||||
private delegate nint LoadCharacterSound(nint character, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7);
|
||||
|
||||
private readonly Hook<LoadCharacterSound> _loadCharacterSoundHook;
|
||||
|
||||
private nint LoadCharacterSoundDetour(nint container, int unk1, int unk2, nint unk3, ulong unk4, int unk5, int unk6, ulong unk7)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadSound);
|
||||
var last = _characterSoundData.Value;
|
||||
var character = *(GameObject**)(container + 8);
|
||||
_characterSoundData.Value = _collectionResolver.IdentifyCollection(character, true);
|
||||
var ret = _loadCharacterSoundHook.Original(container, unk1, unk2, unk3, unk4, unk5, unk6, unk7);
|
||||
_characterSoundData.Value = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The timeline object loads the requested .tmb and .pap files. The .tmb files load the respective .avfx files.
|
||||
/// We can obtain the associated game object from the timelines 28'th vfunc and use that to apply the correct collection.
|
||||
/// </summary>
|
||||
private delegate ulong LoadTimelineResourcesDelegate(nint timeline);
|
||||
|
||||
[Signature(Sigs.LoadTimelineResources, DetourName = nameof(LoadTimelineResourcesDetour))]
|
||||
private readonly Hook<LoadTimelineResourcesDelegate> _loadTimelineResourcesHook = null!;
|
||||
|
||||
private ulong LoadTimelineResourcesDetour(nint timeline)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.TimelineResources);
|
||||
// Do not check timeline loading in cutscenes.
|
||||
if (_conditions[ConditionFlag.OccupiedInCutSceneEvent] || _conditions[ConditionFlag.WatchingCutscene78])
|
||||
return _loadTimelineResourcesHook.Original(timeline);
|
||||
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = GetDataFromTimeline(timeline);
|
||||
var ret = _loadTimelineResourcesHook.Original(timeline);
|
||||
_animationLoadData.Value = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Probably used when the base idle animation gets loaded.
|
||||
/// Make it aware of the correct collection to load the correct pap files.
|
||||
/// </summary>
|
||||
private delegate void CharacterBaseNoArgumentDelegate(nint drawBase);
|
||||
|
||||
[Signature(Sigs.CharacterBaseLoadAnimation, DetourName = nameof(CharacterBaseLoadAnimationDetour))]
|
||||
private readonly Hook<CharacterBaseNoArgumentDelegate> _characterBaseLoadAnimationHook = null!;
|
||||
|
||||
private void CharacterBaseLoadAnimationDetour(nint drawObject)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadCharacterBaseAnimation);
|
||||
var last = _animationLoadData.Value;
|
||||
var lastObj = _drawObjectState.LastGameObject;
|
||||
if (lastObj == nint.Zero && _drawObjectState.TryGetValue(drawObject, out var p))
|
||||
lastObj = p.Item1;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection((GameObject*)lastObj, true);
|
||||
_characterBaseLoadAnimationHook.Original(drawObject);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
/// <summary> Unknown what exactly this is but it seems to load a bunch of paps. </summary>
|
||||
private delegate void LoadSomePap(nint a1, int a2, nint a3, int a4);
|
||||
|
||||
[Signature(Sigs.LoadSomePap, DetourName = nameof(LoadSomePapDetour))]
|
||||
private readonly Hook<LoadSomePap> _loadSomePapHook = null!;
|
||||
|
||||
private void LoadSomePapDetour(nint a1, int a2, nint a3, int a4)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadPap);
|
||||
var timelinePtr = a1 + Offsets.TimeLinePtr;
|
||||
var last = _animationLoadData.Value;
|
||||
if (timelinePtr != nint.Zero)
|
||||
{
|
||||
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
||||
if (actorIdx >= 0 && actorIdx < _objects.Length)
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection((GameObject*)_objects.GetObjectAddress(actorIdx), true);
|
||||
}
|
||||
|
||||
_loadSomePapHook.Original(a1, a2, a3, a4);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
private delegate void SomeActionLoadDelegate(ActionTimelineManager* timelineManager);
|
||||
|
||||
/// <summary> Seems to load character actions when zoning or changing class, maybe. </summary>
|
||||
[Signature(Sigs.LoadSomeAction, DetourName = nameof(SomeActionLoadDetour))]
|
||||
private readonly Hook<SomeActionLoadDelegate> _someActionLoadHook = null!;
|
||||
|
||||
private void SomeActionLoadDetour(ActionTimelineManager* timelineManager)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadAction);
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection((GameObject*)timelineManager->Parent, true);
|
||||
_someActionLoadHook.Original(timelineManager);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
/// <summary> Load a VFX specifically for a character. </summary>
|
||||
private delegate nint LoadCharacterVfxDelegate(byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4);
|
||||
|
||||
[Signature(Sigs.LoadCharacterVfx, DetourName = nameof(LoadCharacterVfxDetour))]
|
||||
private readonly Hook<LoadCharacterVfxDelegate> _loadCharacterVfxHook = null!;
|
||||
|
||||
private nint LoadCharacterVfxDetour(byte* vfxPath, VfxParams* vfxParams, byte unk1, byte unk2, float unk3, int unk4)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadCharacterVfx);
|
||||
var last = _animationLoadData.Value;
|
||||
if (vfxParams != null && vfxParams->GameObjectId != unchecked((uint)-1))
|
||||
{
|
||||
var obj = vfxParams->GameObjectType switch
|
||||
{
|
||||
0 => _objects.SearchById(vfxParams->GameObjectId),
|
||||
2 => _objects[(int)vfxParams->GameObjectId],
|
||||
4 => GetOwnedObject(vfxParams->GameObjectId),
|
||||
_ => null,
|
||||
};
|
||||
_animationLoadData.Value = obj != null
|
||||
? _collectionResolver.IdentifyCollection((GameObject*)obj.Address, true)
|
||||
: ResolveData.Invalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
_animationLoadData.Value = ResolveData.Invalid;
|
||||
}
|
||||
|
||||
var ret = _loadCharacterVfxHook.Original(vfxPath, vfxParams, unk1, unk2, unk3, unk4);
|
||||
Penumbra.Log.Excessive(
|
||||
$"Load Character VFX: {new ByteString(vfxPath)} 0x{vfxParams->GameObjectId:X} {vfxParams->TargetCount} {unk1} {unk2} {unk3} {unk4} -> "
|
||||
+ $"0x{ret:X} {_animationLoadData.Value.ModCollection.Name} {_animationLoadData.Value.AssociatedGameObject} {last.ModCollection.Name} {last.AssociatedGameObject}");
|
||||
_animationLoadData.Value = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary> Load a ground-based area VFX. </summary>
|
||||
private delegate nint LoadAreaVfxDelegate(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3);
|
||||
|
||||
[Signature(Sigs.LoadAreaVfx, DetourName = nameof(LoadAreaVfxDetour))]
|
||||
private readonly Hook<LoadAreaVfxDelegate> _loadAreaVfxHook = null!;
|
||||
|
||||
private nint LoadAreaVfxDetour(uint vfxId, float* pos, GameObject* caster, float unk1, float unk2, byte unk3)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.LoadAreaVfx);
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = caster != null
|
||||
? _collectionResolver.IdentifyCollection(caster, true)
|
||||
: ResolveData.Invalid;
|
||||
|
||||
var ret = _loadAreaVfxHook.Original(vfxId, pos, caster, unk1, unk2, unk3);
|
||||
Penumbra.Log.Excessive(
|
||||
$"Load Area VFX: {vfxId}, {pos[0]} {pos[1]} {pos[2]} {(caster != null ? new ByteString(caster->GetName()).ToString() : "Unknown")} {unk1} {unk2} {unk3}"
|
||||
+ $" -> {ret:X} {_animationLoadData.Value.ModCollection.Name} {_animationLoadData.Value.AssociatedGameObject} {last.ModCollection.Name} {last.AssociatedGameObject}");
|
||||
_animationLoadData.Value = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Called when some action timelines update. </summary>
|
||||
private delegate void ScheduleClipUpdate(ClipScheduler* x);
|
||||
|
||||
[Signature(Sigs.ScheduleClipUpdate, DetourName = nameof(ScheduleClipUpdateDetour))]
|
||||
private readonly Hook<ScheduleClipUpdate> _scheduleClipUpdateHook = null!;
|
||||
|
||||
private void ScheduleClipUpdateDetour(ClipScheduler* x)
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.ScheduleClipUpdate);
|
||||
var last = _animationLoadData.Value;
|
||||
var timeline = x->SchedulerTimeline;
|
||||
_animationLoadData.Value = GetDataFromTimeline(timeline);
|
||||
_scheduleClipUpdateHook.Original(x);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
/// <summary> Search an object by its id, then get its minion/mount/ornament. </summary>
|
||||
private Dalamud.Game.ClientState.Objects.Types.GameObject? GetOwnedObject(uint id)
|
||||
{
|
||||
var owner = _objects.SearchById(id);
|
||||
if (owner == null)
|
||||
return null;
|
||||
|
||||
var idx = ((GameObject*)owner.Address)->ObjectIndex;
|
||||
return _objects[idx + 1];
|
||||
}
|
||||
|
||||
/// <summary> Use timelines vfuncs to obtain the associated game object. </summary>
|
||||
private ResolveData GetDataFromTimeline(nint timeline)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (timeline != nint.Zero)
|
||||
{
|
||||
var getGameObjectIdx = ((delegate* unmanaged<nint, int>**)timeline)[0][Offsets.GetGameObjectIdxVfunc];
|
||||
var idx = getGameObjectIdx(timeline);
|
||||
if (idx >= 0 && idx < _objects.Length)
|
||||
{
|
||||
var obj = (GameObject*)_objects.GetObjectAddress(idx);
|
||||
return obj != null ? _collectionResolver.IdentifyCollection(obj, true) : ResolveData.Invalid;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error($"Error getting timeline data for 0x{timeline:X}:\n{e}");
|
||||
}
|
||||
|
||||
return ResolveData.Invalid;
|
||||
}
|
||||
|
||||
private delegate void UnkMountAnimationDelegate(DrawObject* drawObject, uint unk1, byte unk2, uint unk3);
|
||||
|
||||
[Signature(Sigs.UnkMountAnimation, DetourName = nameof(UnkMountAnimationDetour))]
|
||||
private readonly Hook<UnkMountAnimationDelegate> _unkMountAnimationHook = null!;
|
||||
|
||||
private void UnkMountAnimationDetour(DrawObject* drawObject, uint unk1, byte unk2, uint unk3)
|
||||
{
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(drawObject, true);
|
||||
_unkMountAnimationHook.Original(drawObject, unk1, unk2, unk3);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
private delegate void UnkParasolAnimationDelegate(DrawObject* drawObject, int unk1);
|
||||
|
||||
[Signature(Sigs.UnkParasolAnimation, DetourName = nameof(UnkParasolAnimationDetour))]
|
||||
private readonly Hook<UnkParasolAnimationDelegate> _unkParasolAnimationHook = null!;
|
||||
|
||||
private void UnkParasolAnimationDetour(DrawObject* drawObject, int unk1)
|
||||
{
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(drawObject, true);
|
||||
_unkParasolAnimationHook.Original(drawObject, unk1);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
[Signature(Sigs.Dismount, DetourName = nameof(DismountDetour))]
|
||||
private readonly Hook<DismountDelegate> _dismountHook = null!;
|
||||
|
||||
private delegate void DismountDelegate(nint a1, nint a2);
|
||||
|
||||
private void DismountDetour(nint a1, nint a2)
|
||||
{
|
||||
if (a1 == nint.Zero)
|
||||
{
|
||||
_dismountHook.Original(a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
var gameObject = *(GameObject**)(a1 + 8);
|
||||
if (gameObject == null)
|
||||
{
|
||||
_dismountHook.Original(a1, a2);
|
||||
return;
|
||||
}
|
||||
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
_dismountHook.Original(a1, a2);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
|
||||
[Signature(Sigs.ApricotListenerSoundPlay, DetourName = nameof(ApricotListenerSoundPlayDetour))]
|
||||
private readonly Hook<ApricotListenerSoundPlayDelegate> _apricotListenerSoundPlayHook = null!;
|
||||
|
||||
private delegate nint ApricotListenerSoundPlayDelegate(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
|
||||
|
||||
private nint ApricotListenerSoundPlayDetour(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6)
|
||||
{
|
||||
if (a6 == nint.Zero)
|
||||
return _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6);
|
||||
|
||||
var last = _animationLoadData.Value;
|
||||
// a6 is some instance of Apricot.IInstanceListenner, in some cases we can obtain the associated caster via vfunc 1.
|
||||
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)a6)[1](a6);
|
||||
if (gameObject != null)
|
||||
{
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for VfxListenner we can obtain the associated draw object as its first member,
|
||||
// if the object has different type, drawObject will contain other values or garbage,
|
||||
// but only be used in a dictionary pointer lookup, so this does not hurt.
|
||||
var drawObject = ((DrawObject**)a6)[1];
|
||||
if (drawObject != null)
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(drawObject, true);
|
||||
}
|
||||
|
||||
var ret = _apricotListenerSoundPlayHook!.Original(a1, a2, a3, a4, a5, a6);
|
||||
_animationLoadData.Value = last;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private delegate void FootStepDelegate(GameObject* gameObject, int id, int unk);
|
||||
|
||||
[Signature(Sigs.FootStepSound, DetourName = nameof(FootStepDetour))]
|
||||
private readonly Hook<FootStepDelegate> _footStepHook = null!;
|
||||
|
||||
private void FootStepDetour(GameObject* gameObject, int id, int unk)
|
||||
{
|
||||
var last = _animationLoadData.Value;
|
||||
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
_footStepHook.Original(gameObject, id, unk);
|
||||
_animationLoadData.Value = last;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
|
|
@ -13,70 +13,51 @@ using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
|||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
||||
public unsafe class CollectionResolver
|
||||
public sealed unsafe class CollectionResolver(
|
||||
PerformanceTracker performance,
|
||||
IdentifiedCollectionCache cache,
|
||||
IClientState clientState,
|
||||
IGameGui gameGui,
|
||||
ActorManager actors,
|
||||
CutsceneService cutscenes,
|
||||
Configuration config,
|
||||
CollectionManager collectionManager,
|
||||
TempCollectionManager tempCollections,
|
||||
DrawObjectState drawObjectState,
|
||||
HumanModelList humanModels)
|
||||
: IService
|
||||
{
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly IdentifiedCollectionCache _cache;
|
||||
private readonly HumanModelList _humanModels;
|
||||
|
||||
private readonly IClientState _clientState;
|
||||
private readonly IGameGui _gameGui;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly CutsceneService _cutscenes;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly TempCollectionManager _tempCollections;
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
|
||||
public CollectionResolver(PerformanceTracker performance, IdentifiedCollectionCache cache, IClientState clientState, IGameGui gameGui,
|
||||
ActorManager actors, CutsceneService cutscenes, Configuration config, CollectionManager collectionManager,
|
||||
TempCollectionManager tempCollections, DrawObjectState drawObjectState, HumanModelList humanModels)
|
||||
{
|
||||
_performance = performance;
|
||||
_cache = cache;
|
||||
_clientState = clientState;
|
||||
_gameGui = gameGui;
|
||||
_actors = actors;
|
||||
_cutscenes = cutscenes;
|
||||
_config = config;
|
||||
_collectionManager = collectionManager;
|
||||
_tempCollections = tempCollections;
|
||||
_drawObjectState = drawObjectState;
|
||||
_humanModels = humanModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the collection applying to the current player character
|
||||
/// or the Yourself or Default collection if no player exists.
|
||||
/// </summary>
|
||||
public ModCollection PlayerCollection()
|
||||
{
|
||||
using var performance = _performance.Measure(PerformanceType.IdentifyCollection);
|
||||
var gameObject = (GameObject*)(_clientState.LocalPlayer?.Address ?? nint.Zero);
|
||||
using var performance1 = performance.Measure(PerformanceType.IdentifyCollection);
|
||||
var gameObject = (GameObject*)(clientState.LocalPlayer?.Address ?? nint.Zero);
|
||||
if (gameObject == null)
|
||||
return _collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? _collectionManager.Active.Default;
|
||||
return collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? collectionManager.Active.Default;
|
||||
|
||||
var player = _actors.GetCurrentPlayer();
|
||||
var player = actors.GetCurrentPlayer();
|
||||
var _ = false;
|
||||
return CollectionByIdentifier(player)
|
||||
?? CheckYourself(player, gameObject)
|
||||
?? CollectionByAttributes(gameObject, ref _)
|
||||
?? _collectionManager.Active.Default;
|
||||
?? collectionManager.Active.Default;
|
||||
}
|
||||
|
||||
/// <summary> Identify the correct collection for a game object. </summary>
|
||||
public ResolveData IdentifyCollection(GameObject* gameObject, bool useCache)
|
||||
{
|
||||
using var t = _performance.Measure(PerformanceType.IdentifyCollection);
|
||||
using var t = performance.Measure(PerformanceType.IdentifyCollection);
|
||||
|
||||
if (gameObject == null)
|
||||
return _collectionManager.Active.Default.ToResolveData();
|
||||
return collectionManager.Active.Default.ToResolveData();
|
||||
|
||||
try
|
||||
{
|
||||
if (useCache && _cache.TryGetValue(gameObject, out var data))
|
||||
if (useCache && cache.TryGetValue(gameObject, out var data))
|
||||
return data;
|
||||
|
||||
if (LoginScreen(gameObject, out data))
|
||||
|
|
@ -90,26 +71,26 @@ public unsafe class CollectionResolver
|
|||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Log.Error($"Error identifying collection:\n{ex}");
|
||||
return _collectionManager.Active.Default.ToResolveData(gameObject);
|
||||
return collectionManager.Active.Default.ToResolveData(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Identify the correct collection for the last created game object. </summary>
|
||||
public ResolveData IdentifyLastGameObjectCollection(bool useCache)
|
||||
=> IdentifyCollection((GameObject*)_drawObjectState.LastGameObject, useCache);
|
||||
=> IdentifyCollection((GameObject*)drawObjectState.LastGameObject, useCache);
|
||||
|
||||
/// <summary> Identify the correct collection for a draw object. </summary>
|
||||
public ResolveData IdentifyCollection(DrawObject* drawObject, bool useCache)
|
||||
{
|
||||
var obj = (GameObject*)(_drawObjectState.TryGetValue((nint)drawObject, out var gameObject)
|
||||
var obj = (GameObject*)(drawObjectState.TryGetValue((nint)drawObject, out var gameObject)
|
||||
? gameObject.Item1
|
||||
: _drawObjectState.LastGameObject);
|
||||
: drawObjectState.LastGameObject);
|
||||
return IdentifyCollection(obj, useCache);
|
||||
}
|
||||
|
||||
/// <summary> Return whether the given ModelChara id refers to a human-type model. </summary>
|
||||
public bool IsModelHuman(uint modelCharaId)
|
||||
=> _humanModels.IsHuman(modelCharaId);
|
||||
=> humanModels.IsHuman(modelCharaId);
|
||||
|
||||
/// <summary> Return whether the given character has a human model. </summary>
|
||||
public bool IsModelHuman(Character* character)
|
||||
|
|
@ -124,36 +105,36 @@ public unsafe class CollectionResolver
|
|||
{
|
||||
// Also check for empty names because sometimes named other characters
|
||||
// might be loaded before being officially logged in.
|
||||
if (_clientState.IsLoggedIn || gameObject->Name[0] != '\0')
|
||||
if (clientState.IsLoggedIn || gameObject->Name[0] != '\0')
|
||||
{
|
||||
ret = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
var notYetReady = false;
|
||||
var collection = _collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
var collection = collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? CollectionByAttributes(gameObject, ref notYetReady)
|
||||
?? _collectionManager.Active.Default;
|
||||
ret = notYetReady ? collection.ToResolveData(gameObject) : _cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
||||
?? collectionManager.Active.Default;
|
||||
ret = notYetReady ? collection.ToResolveData(gameObject) : cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Used if at the aesthetician. The relevant actor is yourself, so use player collection when possible. </summary>
|
||||
private bool Aesthetician(GameObject* gameObject, out ResolveData ret)
|
||||
{
|
||||
if (_gameGui.GetAddonByName("ScreenLog") != IntPtr.Zero)
|
||||
if (gameGui.GetAddonByName("ScreenLog") != IntPtr.Zero)
|
||||
{
|
||||
ret = ResolveData.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
var player = _actors.GetCurrentPlayer();
|
||||
var player = actors.GetCurrentPlayer();
|
||||
var notYetReady = false;
|
||||
var collection = (player.IsValid ? CollectionByIdentifier(player) : null)
|
||||
?? _collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? CollectionByAttributes(gameObject, ref notYetReady)
|
||||
?? _collectionManager.Active.Default;
|
||||
ret = notYetReady ? collection.ToResolveData(gameObject) : _cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
||||
?? collectionManager.Active.Default;
|
||||
ret = notYetReady ? collection.ToResolveData(gameObject) : cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -163,12 +144,12 @@ public unsafe class CollectionResolver
|
|||
/// </summary>
|
||||
private ResolveData DefaultState(GameObject* gameObject)
|
||||
{
|
||||
var identifier = _actors.FromObject(gameObject, out var owner, true, false, false);
|
||||
var identifier = actors.FromObject(gameObject, out var owner, true, false, false);
|
||||
if (identifier.Type is IdentifierType.Special)
|
||||
{
|
||||
(identifier, var type) = _collectionManager.Active.Individuals.ConvertSpecialIdentifier(identifier);
|
||||
if (_config.UseNoModsInInspect && type == IndividualCollections.SpecialResult.Inspect)
|
||||
return _cache.Set(ModCollection.Empty, identifier, gameObject);
|
||||
(identifier, var type) = collectionManager.Active.Individuals.ConvertSpecialIdentifier(identifier);
|
||||
if (config.UseNoModsInInspect && type == IndividualCollections.SpecialResult.Inspect)
|
||||
return cache.Set(ModCollection.Empty, identifier, gameObject);
|
||||
}
|
||||
|
||||
var notYetReady = false;
|
||||
|
|
@ -176,15 +157,15 @@ public unsafe class CollectionResolver
|
|||
?? CheckYourself(identifier, gameObject)
|
||||
?? CollectionByAttributes(gameObject, ref notYetReady)
|
||||
?? CheckOwnedCollection(identifier, owner, ref notYetReady)
|
||||
?? _collectionManager.Active.Default;
|
||||
?? collectionManager.Active.Default;
|
||||
|
||||
return notYetReady ? collection.ToResolveData(gameObject) : _cache.Set(collection, identifier, gameObject);
|
||||
return notYetReady ? collection.ToResolveData(gameObject) : cache.Set(collection, identifier, gameObject);
|
||||
}
|
||||
|
||||
/// <summary> Check both temporary and permanent character collections. Temporary first. </summary>
|
||||
private ModCollection? CollectionByIdentifier(ActorIdentifier identifier)
|
||||
=> _tempCollections.Collections.TryGetCollection(identifier, out var collection)
|
||||
|| _collectionManager.Active.Individuals.TryGetCollection(identifier, out collection)
|
||||
=> tempCollections.Collections.TryGetCollection(identifier, out var collection)
|
||||
|| collectionManager.Active.Individuals.TryGetCollection(identifier, out collection)
|
||||
? collection
|
||||
: null;
|
||||
|
||||
|
|
@ -192,9 +173,9 @@ public unsafe class CollectionResolver
|
|||
private ModCollection? CheckYourself(ActorIdentifier identifier, GameObject* actor)
|
||||
{
|
||||
if (actor->ObjectIndex == 0
|
||||
|| _cutscenes.GetParentIndex(actor->ObjectIndex) == 0
|
||||
|| identifier.Equals(_actors.GetCurrentPlayer()))
|
||||
return _collectionManager.Active.ByType(CollectionType.Yourself);
|
||||
|| cutscenes.GetParentIndex(actor->ObjectIndex) == 0
|
||||
|| identifier.Equals(actors.GetCurrentPlayer()))
|
||||
return collectionManager.Active.ByType(CollectionType.Yourself);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
@ -219,8 +200,8 @@ public unsafe class CollectionResolver
|
|||
var bodyType = character->DrawData.CustomizeData[2];
|
||||
var collection = bodyType switch
|
||||
{
|
||||
3 => _collectionManager.Active.ByType(CollectionType.NonPlayerElderly),
|
||||
4 => _collectionManager.Active.ByType(CollectionType.NonPlayerChild),
|
||||
3 => collectionManager.Active.ByType(CollectionType.NonPlayerElderly),
|
||||
4 => collectionManager.Active.ByType(CollectionType.NonPlayerChild),
|
||||
_ => null,
|
||||
};
|
||||
if (collection != null)
|
||||
|
|
@ -231,18 +212,18 @@ public unsafe class CollectionResolver
|
|||
var isNpc = actor->ObjectKind != (byte)ObjectKind.Player;
|
||||
|
||||
var type = CollectionTypeExtensions.FromParts(race, gender, isNpc);
|
||||
collection = _collectionManager.Active.ByType(type);
|
||||
collection ??= _collectionManager.Active.ByType(CollectionTypeExtensions.FromParts(gender, isNpc));
|
||||
collection = collectionManager.Active.ByType(type);
|
||||
collection ??= collectionManager.Active.ByType(CollectionTypeExtensions.FromParts(gender, isNpc));
|
||||
return collection;
|
||||
}
|
||||
|
||||
/// <summary> Get the collection applying to the owner if it is available. </summary>
|
||||
private ModCollection? CheckOwnedCollection(ActorIdentifier identifier, GameObject* owner, ref bool notYetReady)
|
||||
{
|
||||
if (identifier.Type != IdentifierType.Owned || !_config.UseOwnerNameForCharacterCollection || owner == null)
|
||||
if (identifier.Type != IdentifierType.Owned || !config.UseOwnerNameForCharacterCollection || owner == null)
|
||||
return null;
|
||||
|
||||
var id = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||
var id = actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||
ObjectKind.None,
|
||||
uint.MaxValue);
|
||||
return CheckYourself(id, owner)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,34 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Hooks;
|
||||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
||||
public class CutsceneService : IDisposable
|
||||
public sealed 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 short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly CopyCharacter _copyCharacter;
|
||||
private readonly CharacterDestructor _characterDestructor;
|
||||
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)
|
||||
public unsafe CutsceneService(IObjectTable objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor)
|
||||
{
|
||||
_objects = objects;
|
||||
_events = events;
|
||||
_events.CopyCharacter += OnCharacterCopy;
|
||||
_events.CharacterDestructor += OnCharacterDestructor;
|
||||
_objects = objects;
|
||||
_copyCharacter = copyCharacter;
|
||||
_characterDestructor = characterDestructor;
|
||||
_copyCharacter.Subscribe(OnCharacterCopy, CopyCharacter.Priority.CutsceneService);
|
||||
_characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.CutsceneService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -57,8 +60,8 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,35 +1,39 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
||||
public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>
|
||||
public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>, IService
|
||||
{
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly GameEventManager _gameEvents;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly CreateCharacterBase _createCharacterBase;
|
||||
private readonly WeaponReload _weaponReload;
|
||||
private readonly CharacterBaseDestructor _characterBaseDestructor;
|
||||
private readonly GameState _gameState;
|
||||
|
||||
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = new();
|
||||
|
||||
private readonly ThreadLocal<Queue<nint>> _lastGameObject = new(() => new Queue<nint>());
|
||||
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = [];
|
||||
|
||||
public nint LastGameObject
|
||||
=> _lastGameObject.IsValueCreated && _lastGameObject.Value!.Count > 0 ? _lastGameObject.Value.Peek() : nint.Zero;
|
||||
=> _gameState.LastGameObject;
|
||||
|
||||
public DrawObjectState(IObjectTable objects, GameEventManager gameEvents, IGameInteropProvider interop)
|
||||
public unsafe DrawObjectState(IObjectTable objects, CreateCharacterBase createCharacterBase, WeaponReload weaponReload,
|
||||
CharacterBaseDestructor characterBaseDestructor, GameState gameState)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
_enableDrawHook.Enable();
|
||||
_objects = objects;
|
||||
_gameEvents = gameEvents;
|
||||
_gameEvents.WeaponReloading += OnWeaponReloading;
|
||||
_gameEvents.WeaponReloaded += OnWeaponReloaded;
|
||||
_gameEvents.CharacterBaseCreated += OnCharacterBaseCreated;
|
||||
_gameEvents.CharacterBaseDestructor += OnCharacterBaseDestructor;
|
||||
_objects = objects;
|
||||
_createCharacterBase = createCharacterBase;
|
||||
_weaponReload = weaponReload;
|
||||
_characterBaseDestructor = characterBaseDestructor;
|
||||
_gameState = gameState;
|
||||
_weaponReload.Subscribe(OnWeaponReloading, WeaponReload.Priority.DrawObjectState);
|
||||
_weaponReload.Subscribe(OnWeaponReloaded, WeaponReload.PostEvent.Priority.DrawObjectState);
|
||||
_createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.DrawObjectState);
|
||||
_characterBaseDestructor.Subscribe(OnCharacterBaseDestructor, CharacterBaseDestructor.Priority.DrawObjectState);
|
||||
InitializeDrawObjects();
|
||||
}
|
||||
|
||||
|
|
@ -57,32 +61,32 @@ public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, boo
|
|||
public IEnumerable<(nint, bool)> Values
|
||||
=> _drawObjectToGameObject.Values;
|
||||
|
||||
public void Dispose()
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
_gameEvents.WeaponReloading -= OnWeaponReloading;
|
||||
_gameEvents.WeaponReloaded -= OnWeaponReloaded;
|
||||
_gameEvents.CharacterBaseCreated -= OnCharacterBaseCreated;
|
||||
_gameEvents.CharacterBaseDestructor -= OnCharacterBaseDestructor;
|
||||
_enableDrawHook.Dispose();
|
||||
_weaponReload.Unsubscribe(OnWeaponReloading);
|
||||
_weaponReload.Unsubscribe(OnWeaponReloaded);
|
||||
_createCharacterBase.Unsubscribe(OnCharacterBaseCreated);
|
||||
_characterBaseDestructor.Unsubscribe(OnCharacterBaseDestructor);
|
||||
}
|
||||
|
||||
private void OnWeaponReloading(nint _, nint gameObject)
|
||||
=> _lastGameObject.Value!.Enqueue(gameObject);
|
||||
private unsafe void OnWeaponReloading(DrawDataContainer* _, Character* character, CharacterWeapon* _2)
|
||||
=> _gameState.QueueGameObject((nint)character);
|
||||
|
||||
private unsafe void OnWeaponReloaded(nint _, nint gameObject)
|
||||
private unsafe void OnWeaponReloaded(DrawDataContainer* _, Character* character)
|
||||
{
|
||||
_lastGameObject.Value!.Dequeue();
|
||||
IterateDrawObjectTree((Object*)((GameObject*)gameObject)->DrawObject, gameObject, false, false);
|
||||
_gameState.DequeueGameObject();
|
||||
IterateDrawObjectTree((Object*)character->GameObject.DrawObject, (nint)character, false, false);
|
||||
}
|
||||
|
||||
private void OnCharacterBaseDestructor(nint characterBase)
|
||||
=> _drawObjectToGameObject.Remove(characterBase);
|
||||
private unsafe void OnCharacterBaseDestructor(CharacterBase* characterBase)
|
||||
=> _drawObjectToGameObject.Remove((nint)characterBase);
|
||||
|
||||
private void OnCharacterBaseCreated(uint modelCharaId, nint customize, nint equipment, nint drawObject)
|
||||
private unsafe void OnCharacterBaseCreated(ModelCharaId modelCharaId, CustomizeArray* customize, CharacterArmor* equipment,
|
||||
CharacterBase* drawObject)
|
||||
{
|
||||
var gameObject = LastGameObject;
|
||||
if (gameObject != nint.Zero)
|
||||
_drawObjectToGameObject[drawObject] = (gameObject, false);
|
||||
_drawObjectToGameObject[(nint)drawObject] = (gameObject, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,20 +127,4 @@ public class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, boo
|
|||
prevSibling = prevSibling->PreviousSiblingObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EnableDraw is what creates DrawObjects for gameObjects,
|
||||
/// so we always keep track of the current GameObject to be able to link it to the DrawObject.
|
||||
/// </summary>
|
||||
private delegate void EnableDrawDelegate(nint gameObject);
|
||||
|
||||
[Signature(Sigs.EnableDraw, DetourName = nameof(EnableDrawDetour))]
|
||||
private readonly Hook<EnableDrawDelegate> _enableDrawHook = null!;
|
||||
|
||||
private void EnableDrawDetour(nint gameObject)
|
||||
{
|
||||
_lastGameObject.Value!.Enqueue(gameObject);
|
||||
_enableDrawHook.Original.Invoke(gameObject);
|
||||
_lastGameObject.Value!.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Penumbra.Collections;
|
|||
using Penumbra.Collections.Manager;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
|
@ -13,20 +13,20 @@ namespace Penumbra.Interop.PathResolving;
|
|||
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint Address, ActorIdentifier Identifier, ModCollection Collection)>
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly GameEventManager _events;
|
||||
private readonly CharacterDestructor _characterDestructor;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly Dictionary<nint, (ActorIdentifier, ModCollection)> _cache = new(317);
|
||||
private bool _dirty;
|
||||
|
||||
public IdentifiedCollectionCache(IClientState clientState, CommunicatorService communicator, GameEventManager events)
|
||||
public IdentifiedCollectionCache(IClientState clientState, CommunicatorService communicator, CharacterDestructor characterDestructor)
|
||||
{
|
||||
_clientState = clientState;
|
||||
_communicator = communicator;
|
||||
_events = events;
|
||||
_clientState = clientState;
|
||||
_communicator = communicator;
|
||||
_characterDestructor = characterDestructor;
|
||||
|
||||
_communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache);
|
||||
_clientState.TerritoryChanged += TerritoryClear;
|
||||
_events.CharacterDestructor += OnCharacterDestruct;
|
||||
_characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.IdentifiedCollectionCache);
|
||||
}
|
||||
|
||||
public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data)
|
||||
|
|
@ -62,7 +62,7 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
|
|||
{
|
||||
_communicator.CollectionChange.Unsubscribe(CollectionChangeClear);
|
||||
_clientState.TerritoryChanged -= TerritoryClear;
|
||||
_events.CharacterDestructor -= OnCharacterDestruct;
|
||||
_characterDestructor.Unsubscribe(OnCharacterDestructor);
|
||||
}
|
||||
|
||||
public IEnumerator<(nint Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator()
|
||||
|
|
@ -88,6 +88,6 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
|
|||
private void TerritoryClear(ushort _2)
|
||||
=> _dirty = _cache.Count > 0;
|
||||
|
||||
private void OnCharacterDestruct(Character* character)
|
||||
private void OnCharacterDestructor(Character* character)
|
||||
=> _cache.Remove((nint)character);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ using Penumbra.Collections;
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Services;
|
||||
|
|
@ -52,24 +54,24 @@ public unsafe class MetaState : IDisposable
|
|||
private readonly PerformanceTracker _performance;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly ResourceLoader _resources;
|
||||
private readonly GameEventManager _gameEventManager;
|
||||
private readonly CharacterUtility _characterUtility;
|
||||
private readonly CreateCharacterBase _createCharacterBase;
|
||||
|
||||
private ResolveData _lastCreatedCollection = ResolveData.Invalid;
|
||||
private ResolveData _customizeChangeCollection = ResolveData.Invalid;
|
||||
private DisposableContainer _characterBaseCreateMetaChanges = DisposableContainer.Empty;
|
||||
|
||||
public MetaState(PerformanceTracker performance, CommunicatorService communicator, CollectionResolver collectionResolver,
|
||||
ResourceLoader resources, GameEventManager gameEventManager, CharacterUtility characterUtility, Configuration config,
|
||||
ResourceLoader resources, CreateCharacterBase createCharacterBase, CharacterUtility characterUtility, Configuration config,
|
||||
IGameInteropProvider interop)
|
||||
{
|
||||
_performance = performance;
|
||||
_communicator = communicator;
|
||||
_collectionResolver = collectionResolver;
|
||||
_resources = resources;
|
||||
_gameEventManager = gameEventManager;
|
||||
_characterUtility = characterUtility;
|
||||
_config = config;
|
||||
_performance = performance;
|
||||
_communicator = communicator;
|
||||
_collectionResolver = collectionResolver;
|
||||
_resources = resources;
|
||||
_createCharacterBase = createCharacterBase;
|
||||
_characterUtility = characterUtility;
|
||||
_config = config;
|
||||
interop.InitializeFromAttributes(this);
|
||||
_calculateHeightHook =
|
||||
interop.HookFromAddress<CalculateHeightDelegate>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
|
||||
|
|
@ -81,8 +83,8 @@ public unsafe class MetaState : IDisposable
|
|||
_rspSetupCharacterHook.Enable();
|
||||
_changeCustomize.Enable();
|
||||
_calculateHeightHook.Enable();
|
||||
_gameEventManager.CreatingCharacterBase += OnCreatingCharacterBase;
|
||||
_gameEventManager.CharacterBaseCreated += OnCharacterBaseCreated;
|
||||
_createCharacterBase.Subscribe(OnCreatingCharacterBase, CreateCharacterBase.Priority.MetaState);
|
||||
_createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.MetaState);
|
||||
}
|
||||
|
||||
public bool HandleDecalFile(ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData)
|
||||
|
|
@ -124,31 +126,31 @@ public unsafe class MetaState : IDisposable
|
|||
_rspSetupCharacterHook.Dispose();
|
||||
_changeCustomize.Dispose();
|
||||
_calculateHeightHook.Dispose();
|
||||
_gameEventManager.CreatingCharacterBase -= OnCreatingCharacterBase;
|
||||
_gameEventManager.CharacterBaseCreated -= OnCharacterBaseCreated;
|
||||
_createCharacterBase.Unsubscribe(OnCreatingCharacterBase);
|
||||
_createCharacterBase.Unsubscribe(OnCharacterBaseCreated);
|
||||
}
|
||||
|
||||
private void OnCreatingCharacterBase(nint modelCharaId, nint customize, nint equipData)
|
||||
private void OnCreatingCharacterBase(ModelCharaId* modelCharaId, CustomizeArray* customize, CharacterArmor* equipData)
|
||||
{
|
||||
_lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true);
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero)
|
||||
_communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||
_lastCreatedCollection.ModCollection.Name, modelCharaId, customize, equipData);
|
||||
_lastCreatedCollection.ModCollection.Name, (nint) modelCharaId, (nint) customize, (nint) equipData);
|
||||
|
||||
var decal = new DecalReverter(_config, _characterUtility, _resources, _lastCreatedCollection,
|
||||
UsesDecal(*(uint*)modelCharaId, customize));
|
||||
UsesDecal(*(uint*)modelCharaId, (nint) customize));
|
||||
var cmp = _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(_characterUtility);
|
||||
_characterBaseCreateMetaChanges.Dispose(); // Should always be empty.
|
||||
_characterBaseCreateMetaChanges = new DisposableContainer(decal, cmp);
|
||||
}
|
||||
|
||||
private void OnCharacterBaseCreated(uint _1, nint _2, nint _3, nint drawObject)
|
||||
private void OnCharacterBaseCreated(ModelCharaId _1, CustomizeArray* _2, CharacterArmor* _3, CharacterBase* drawObject)
|
||||
{
|
||||
_characterBaseCreateMetaChanges.Dispose();
|
||||
_characterBaseCreateMetaChanges = DisposableContainer.Empty;
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero && drawObject != nint.Zero)
|
||||
if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero && drawObject != null)
|
||||
_communicator.CreatedCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject,
|
||||
_lastCreatedCollection.ModCollection, drawObject);
|
||||
_lastCreatedCollection.ModCollection, (nint)drawObject);
|
||||
_lastCreatedCollection = ResolveData.Invalid;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,26 +18,28 @@ public class PathResolver : IDisposable
|
|||
private readonly TempCollectionManager _tempCollections;
|
||||
private readonly ResourceLoader _loader;
|
||||
|
||||
private readonly AnimationHookService _animationHookService;
|
||||
private readonly SubfileHelper _subfileHelper;
|
||||
private readonly PathState _pathState;
|
||||
private readonly MetaState _metaState;
|
||||
private readonly SubfileHelper _subfileHelper;
|
||||
private readonly PathState _pathState;
|
||||
private readonly MetaState _metaState;
|
||||
private readonly GameState _gameState;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
|
||||
public unsafe PathResolver(PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
||||
TempCollectionManager tempCollections, ResourceLoader loader, AnimationHookService animationHookService, SubfileHelper subfileHelper,
|
||||
PathState pathState, MetaState metaState)
|
||||
TempCollectionManager tempCollections, ResourceLoader loader, SubfileHelper subfileHelper,
|
||||
PathState pathState, MetaState metaState, CollectionResolver collectionResolver, GameState gameState)
|
||||
{
|
||||
_performance = performance;
|
||||
_config = config;
|
||||
_collectionManager = collectionManager;
|
||||
_tempCollections = tempCollections;
|
||||
_animationHookService = animationHookService;
|
||||
_subfileHelper = subfileHelper;
|
||||
_pathState = pathState;
|
||||
_metaState = metaState;
|
||||
_loader = loader;
|
||||
_loader.ResolvePath = ResolvePath;
|
||||
_loader.FileLoaded += ImcLoadResource;
|
||||
_performance = performance;
|
||||
_config = config;
|
||||
_collectionManager = collectionManager;
|
||||
_tempCollections = tempCollections;
|
||||
_subfileHelper = subfileHelper;
|
||||
_pathState = pathState;
|
||||
_metaState = metaState;
|
||||
_gameState = gameState;
|
||||
_collectionResolver = collectionResolver;
|
||||
_loader = loader;
|
||||
_loader.ResolvePath = ResolvePath;
|
||||
_loader.FileLoaded += ImcLoadResource;
|
||||
}
|
||||
|
||||
/// <summary> Obtain a temporary or permanent collection by name. </summary>
|
||||
|
|
@ -98,7 +100,7 @@ public class PathResolver : IDisposable
|
|||
// A potential next request will add the path anew.
|
||||
var nonDefault = _subfileHelper.HandleSubFiles(type, out var resolveData)
|
||||
|| _pathState.Consume(gamePath.Path, out resolveData)
|
||||
|| _animationHookService.HandleFiles(type, gamePath, out resolveData)
|
||||
|| _gameState.HandleFiles(_collectionResolver, type, gamePath, out resolveData)
|
||||
|| _metaState.HandleDecalFile(type, gamePath, out resolveData);
|
||||
if (!nonDefault || !resolveData.Valid)
|
||||
resolveData = _collectionManager.Active.Default.ToResolveData();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Dalamud.Utility.Signatures;
|
|||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.ResourceLoading;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
|
@ -21,30 +22,30 @@ namespace Penumbra.Interop.PathResolving;
|
|||
/// </summary>
|
||||
public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePair<nint, ResolveData>>
|
||||
{
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly ResourceLoader _loader;
|
||||
private readonly GameEventManager _events;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly ResourceLoader _loader;
|
||||
private readonly ResourceHandleDestructor _resourceHandleDestructor;
|
||||
private readonly CommunicatorService _communicator;
|
||||
|
||||
private readonly ThreadLocal<ResolveData> _mtrlData = new(() => ResolveData.Invalid);
|
||||
private readonly ThreadLocal<ResolveData> _avfxData = new(() => ResolveData.Invalid);
|
||||
|
||||
private readonly ConcurrentDictionary<nint, ResolveData> _subFileCollection = new();
|
||||
|
||||
public SubfileHelper(PerformanceTracker performance, ResourceLoader loader, GameEventManager events, CommunicatorService communicator, IGameInteropProvider interop)
|
||||
public SubfileHelper(PerformanceTracker performance, ResourceLoader loader, CommunicatorService communicator, IGameInteropProvider interop, ResourceHandleDestructor resourceHandleDestructor)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
|
||||
_performance = performance;
|
||||
_loader = loader;
|
||||
_events = events;
|
||||
_communicator = communicator;
|
||||
_performance = performance;
|
||||
_loader = loader;
|
||||
_communicator = communicator;
|
||||
_resourceHandleDestructor = resourceHandleDestructor;
|
||||
|
||||
_loadMtrlShpkHook.Enable();
|
||||
_loadMtrlTexHook.Enable();
|
||||
_apricotResourceLoadHook.Enable();
|
||||
_loader.ResourceLoaded += SubfileContainerRequested;
|
||||
_events.ResourceHandleDestructor += ResourceDestroyed;
|
||||
_resourceHandleDestructor.Subscribe(ResourceDestroyed, ResourceHandleDestructor.Priority.SubfileHelper);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -105,7 +106,7 @@ public unsafe class SubfileHelper : IDisposable, IReadOnlyCollection<KeyValuePai
|
|||
public void Dispose()
|
||||
{
|
||||
_loader.ResourceLoaded -= SubfileContainerRequested;
|
||||
_events.ResourceHandleDestructor -= ResourceDestroyed;
|
||||
_resourceHandleDestructor.Unsubscribe(ResourceDestroyed);
|
||||
_loadMtrlShpkHook.Dispose();
|
||||
_loadMtrlTexHook.Dispose();
|
||||
_apricotResourceLoadHook.Dispose();
|
||||
|
|
|
|||
|
|
@ -1,299 +0,0 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.GameData;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Interop.Services;
|
||||
|
||||
public unsafe class GameEventManager : IDisposable
|
||||
{
|
||||
private const string Prefix = $"[{nameof(GameEventManager)}]";
|
||||
|
||||
public event CharacterDestructorEvent? CharacterDestructor;
|
||||
public event CopyCharacterEvent? CopyCharacter;
|
||||
public event ResourceHandleDestructorEvent? ResourceHandleDestructor;
|
||||
public event CreatingCharacterBaseEvent? CreatingCharacterBase;
|
||||
public event CharacterBaseCreatedEvent? CharacterBaseCreated;
|
||||
public event CharacterBaseDestructorEvent? CharacterBaseDestructor;
|
||||
public event WeaponReloadingEvent? WeaponReloading;
|
||||
public event WeaponReloadedEvent? WeaponReloaded;
|
||||
|
||||
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);
|
||||
_weaponReloadHook =
|
||||
interop.HookFromAddress<WeaponReloadFunc>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, WeaponReloadDetour);
|
||||
_characterDtorHook.Enable();
|
||||
_copyCharacterHook.Enable();
|
||||
_resourceHandleDestructorHook.Enable();
|
||||
_characterBaseCreateHook.Enable();
|
||||
_characterBaseDestructorHook.Enable();
|
||||
_weaponReloadHook.Enable();
|
||||
EnableDebugHook();
|
||||
Penumbra.Log.Verbose($"{Prefix} Created.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_characterDtorHook.Dispose();
|
||||
_copyCharacterHook.Dispose();
|
||||
_resourceHandleDestructorHook.Dispose();
|
||||
_characterBaseCreateHook.Dispose();
|
||||
_characterBaseDestructorHook.Dispose();
|
||||
_weaponReloadHook.Dispose();
|
||||
DisposeDebugHook();
|
||||
Penumbra.Log.Verbose($"{Prefix} Disposed.");
|
||||
}
|
||||
|
||||
#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}");
|
||||
}
|
||||
}
|
||||
|
||||
Penumbra.Log.Verbose($"{Prefix} {nameof(CharacterDestructor)} triggered with 0x{(nint)character:X}.");
|
||||
_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}");
|
||||
}
|
||||
}
|
||||
|
||||
Penumbra.Log.Verbose(
|
||||
$"{Prefix} {nameof(CopyCharacter)} triggered with target 0x{(nint)target:X} and source 0x{(nint)source:X}.");
|
||||
return _copyCharacterHook.Original(target, source, unk);
|
||||
}
|
||||
|
||||
public delegate void CopyCharacterEvent(Character* target, Character* source);
|
||||
|
||||
#endregion
|
||||
|
||||
#region ResourceHandle Destructor
|
||||
|
||||
private delegate IntPtr ResourceHandleDestructorDelegate(ResourceHandle* handle);
|
||||
|
||||
[Signature(Sigs.ResourceHandleDestructor, DetourName = nameof(ResourceHandleDestructorDetour))]
|
||||
private readonly Hook<ResourceHandleDestructorDelegate> _resourceHandleDestructorHook = null!;
|
||||
|
||||
private IntPtr ResourceHandleDestructorDetour(ResourceHandle* handle)
|
||||
{
|
||||
if (ResourceHandleDestructor != null)
|
||||
foreach (var subscriber in ResourceHandleDestructor.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((ResourceHandleDestructorEvent)subscriber).Invoke(handle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(ResourceHandleDestructor)} event when executing {subscriber.Method.Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Penumbra.Log.Excessive($"{Prefix} {nameof(ResourceHandleDestructor)} triggered with 0x{(nint)handle:X}.");
|
||||
return _resourceHandleDestructorHook!.Original(handle);
|
||||
}
|
||||
|
||||
public delegate void ResourceHandleDestructorEvent(ResourceHandle* handle);
|
||||
|
||||
#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}");
|
||||
}
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
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(IntPtr 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}");
|
||||
}
|
||||
}
|
||||
|
||||
_characterBaseDestructorHook.Original.Invoke(drawBase);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Weapon Reload
|
||||
|
||||
private delegate void WeaponReloadFunc(nint a1, uint a2, nint a3, byte a4, byte a5, byte a6, byte a7);
|
||||
|
||||
private readonly Hook<WeaponReloadFunc> _weaponReloadHook;
|
||||
|
||||
private void WeaponReloadDetour(nint a1, uint a2, nint a3, byte a4, byte a5, byte a6, byte a7)
|
||||
{
|
||||
var gameObject = *(nint*)(a1 + 8);
|
||||
if (WeaponReloading != null)
|
||||
foreach (var subscriber in WeaponReloading.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((WeaponReloadingEvent)subscriber).Invoke(a1, gameObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(WeaponReloadDetour)} event when executing {subscriber.Method.Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
_weaponReloadHook.Original(a1, a2, a3, a4, a5, a6, a7);
|
||||
|
||||
if (WeaponReloaded != null)
|
||||
foreach (var subscriber in WeaponReloaded.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
((WeaponReloadedEvent)subscriber).Invoke(a1, gameObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Log.Error(
|
||||
$"{Prefix} Error in {nameof(WeaponReloadDetour)} event when executing {subscriber.Method.Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void WeaponReloadingEvent(nint drawDataContainer, nint gameObject);
|
||||
public delegate void WeaponReloadedEvent(nint drawDataContainer, nint gameObject);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Testing
|
||||
|
||||
#if DEBUG
|
||||
//[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 89 54 24 ?? 57 48 83 EC ?? 48 8B F9", DetourName = nameof(TestDetour))]
|
||||
private readonly Hook<TestDelegate>? _testHook = null;
|
||||
|
||||
private delegate void TestDelegate(nint a1, int a2);
|
||||
|
||||
private void TestDetour(nint a1, int a2)
|
||||
{
|
||||
Penumbra.Log.Information($"Test: {a1:X} {a2}");
|
||||
_testHook!.Original(a1, a2);
|
||||
}
|
||||
|
||||
private void EnableDebugHook()
|
||||
=> _testHook?.Enable();
|
||||
|
||||
private void DisposeDebugHook()
|
||||
=> _testHook?.Dispose();
|
||||
#else
|
||||
private void EnableDebugHook()
|
||||
{ }
|
||||
|
||||
private void DisposeDebugHook()
|
||||
{ }
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Interop.Services;
|
||||
|
|
@ -32,9 +33,9 @@ public sealed unsafe class SkinFixer : IDisposable
|
|||
|
||||
private readonly Hook<OnRenderMaterialDelegate> _onRenderMaterialHook;
|
||||
|
||||
private readonly GameEventManager _gameEvents;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CharacterUtility _utility;
|
||||
private readonly ResourceHandleDestructor _resourceHandleDestructor;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CharacterUtility _utility;
|
||||
|
||||
// MaterialResourceHandle set
|
||||
private readonly ConcurrentSet<nint> _moddedSkinShpkMaterials = new();
|
||||
|
|
@ -50,15 +51,16 @@ public sealed unsafe class SkinFixer : IDisposable
|
|||
public int ModdedSkinShpkCount
|
||||
=> _moddedSkinShpkCount;
|
||||
|
||||
public SkinFixer(GameEventManager gameEvents, CharacterUtility utility, CommunicatorService communicator, IGameInteropProvider interop)
|
||||
public SkinFixer(ResourceHandleDestructor resourceHandleDestructor, CharacterUtility utility, CommunicatorService communicator,
|
||||
IGameInteropProvider interop)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
_gameEvents = gameEvents;
|
||||
_utility = utility;
|
||||
_communicator = communicator;
|
||||
_onRenderMaterialHook = interop.HookFromAddress<OnRenderMaterialDelegate>(_humanVTable[62], OnRenderHumanMaterial);
|
||||
_resourceHandleDestructor = resourceHandleDestructor;
|
||||
_utility = utility;
|
||||
_communicator = communicator;
|
||||
_onRenderMaterialHook = interop.HookFromAddress<OnRenderMaterialDelegate>(_humanVTable[62], OnRenderHumanMaterial);
|
||||
_communicator.MtrlShpkLoaded.Subscribe(OnMtrlShpkLoaded, MtrlShpkLoaded.Priority.SkinFixer);
|
||||
_gameEvents.ResourceHandleDestructor += OnResourceHandleDestructor;
|
||||
_resourceHandleDestructor.Subscribe(OnResourceHandleDestructor, ResourceHandleDestructor.Priority.SkinFixer);
|
||||
_onRenderMaterialHook.Enable();
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +68,7 @@ public sealed unsafe class SkinFixer : IDisposable
|
|||
{
|
||||
_onRenderMaterialHook.Dispose();
|
||||
_communicator.MtrlShpkLoaded.Unsubscribe(OnMtrlShpkLoaded);
|
||||
_gameEvents.ResourceHandleDestructor -= OnResourceHandleDestructor;
|
||||
_resourceHandleDestructor.Unsubscribe(OnResourceHandleDestructor);
|
||||
_moddedSkinShpkMaterials.Clear();
|
||||
_moddedSkinShpkCount = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using Dalamud.Utility;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Mods.Manager;
|
||||
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ public class Penumbra : IDalamudPlugin
|
|||
_services.GetService<SkinFixer>();
|
||||
|
||||
_services.GetService<DalamudSubstitutionProvider>(); // Initialize before Interface.
|
||||
|
||||
foreach (var service in _services.GetServicesImplementing<IAwaitedService>())
|
||||
service.Awaiter.Wait();
|
||||
|
||||
SetupInterface();
|
||||
SetupApi();
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EmbedIO" Version="3.4.3" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.2" />
|
||||
<PackageReference Include="SharpCompress" Version="0.33.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
|
|
@ -92,8 +93,8 @@
|
|||
|
||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
|
||||
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess" />
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" />
|
||||
</Exec>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class CommunicatorService : IDisposable, IService
|
|||
{
|
||||
public CommunicatorService(Logger logger)
|
||||
{
|
||||
EventWrapper.ChangeLogger(logger);
|
||||
EventWrapperBase.ChangeLogger(logger);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Communication.CollectionChange"/>
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ public static class ServiceManagerA
|
|||
.AddDalamudService<IPluginLog>(pi);
|
||||
|
||||
private static ServiceManager AddInterop(this ServiceManager services)
|
||||
=> services.AddSingleton<GameEventManager>()
|
||||
.AddSingleton<FrameworkManager>()
|
||||
=> services.AddSingleton<FrameworkManager>()
|
||||
.AddSingleton<CutsceneService>()
|
||||
.AddSingleton(p =>
|
||||
{
|
||||
|
|
@ -135,8 +134,7 @@ public static class ServiceManagerA
|
|||
.AddSingleton<SkinFixer>();
|
||||
|
||||
private static ServiceManager AddResolvers(this ServiceManager services)
|
||||
=> services.AddSingleton<AnimationHookService>()
|
||||
.AddSingleton<CollectionResolver>()
|
||||
=> services.AddSingleton<CollectionResolver>()
|
||||
.AddSingleton<CutsceneService>()
|
||||
.AddSingleton<DrawObjectState>()
|
||||
.AddSingleton<MetaState>()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
|
|
@ -8,6 +9,7 @@ using OtterGui.Raii;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.MaterialPreview;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
|
|
@ -503,12 +505,12 @@ public partial class ModEditWindow
|
|||
ColorTablePreviewers.Clear();
|
||||
}
|
||||
|
||||
private unsafe void UnbindFromDrawObjectMaterialInstances(nint characterBase)
|
||||
private unsafe void UnbindFromDrawObjectMaterialInstances(CharacterBase* characterBase)
|
||||
{
|
||||
for (var i = MaterialPreviewers.Count; i-- > 0;)
|
||||
{
|
||||
var previewer = MaterialPreviewers[i];
|
||||
if ((nint)previewer.DrawObject != characterBase)
|
||||
if (previewer.DrawObject != characterBase)
|
||||
continue;
|
||||
|
||||
previewer.Dispose();
|
||||
|
|
@ -518,7 +520,7 @@ public partial class ModEditWindow
|
|||
for (var i = ColorTablePreviewers.Count; i-- > 0;)
|
||||
{
|
||||
var previewer = ColorTablePreviewers[i];
|
||||
if ((nint)previewer.DrawObject != characterBase)
|
||||
if (previewer.DrawObject != characterBase)
|
||||
continue;
|
||||
|
||||
previewer.Dispose();
|
||||
|
|
@ -663,7 +665,7 @@ public partial class ModEditWindow
|
|||
UpdateConstants();
|
||||
}
|
||||
|
||||
public MtrlTab(ModEditWindow edit, MtrlFile file, string filePath, bool writable)
|
||||
public unsafe MtrlTab(ModEditWindow edit, MtrlFile file, string filePath, bool writable)
|
||||
{
|
||||
_edit = edit;
|
||||
Mtrl = file;
|
||||
|
|
@ -673,16 +675,16 @@ public partial class ModEditWindow
|
|||
LoadShpk(FindAssociatedShpk(out _, out _));
|
||||
if (writable)
|
||||
{
|
||||
_edit._gameEvents.CharacterBaseDestructor += UnbindFromDrawObjectMaterialInstances;
|
||||
_edit._characterBaseDestructor.Subscribe(UnbindFromDrawObjectMaterialInstances, CharacterBaseDestructor.Priority.MtrlTab);
|
||||
BindToMaterialInstances();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public unsafe void Dispose()
|
||||
{
|
||||
UnbindFromMaterialInstances();
|
||||
if (Writable)
|
||||
_edit._gameEvents.CharacterBaseDestructor -= UnbindFromDrawObjectMaterialInstances;
|
||||
_edit._characterBaseDestructor.Unsubscribe(UnbindFromDrawObjectMaterialInstances);
|
||||
}
|
||||
|
||||
public bool Valid
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ using Penumbra.Communication;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Import.Textures;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Interop.Services;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Mods;
|
||||
using Penumbra.Mods.Editor;
|
||||
|
|
@ -32,20 +32,20 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
{
|
||||
private const string WindowBaseLabel = "###SubModEdit";
|
||||
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly ModEditor _editor;
|
||||
private readonly Configuration _config;
|
||||
private readonly ItemSwapTab _itemSwapTab;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
private readonly ActiveCollections _activeCollections;
|
||||
private readonly StainService _stainService;
|
||||
private readonly ModMergeTab _modMergeTab;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly IDragDropManager _dragDropManager;
|
||||
private readonly GameEventManager _gameEvents;
|
||||
private readonly IDataManager _gameData;
|
||||
private readonly IFramework _framework;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly PerformanceTracker _performance;
|
||||
private readonly ModEditor _editor;
|
||||
private readonly Configuration _config;
|
||||
private readonly ItemSwapTab _itemSwapTab;
|
||||
private readonly MetaFileManager _metaFileManager;
|
||||
private readonly ActiveCollections _activeCollections;
|
||||
private readonly StainService _stainService;
|
||||
private readonly ModMergeTab _modMergeTab;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly IDragDropManager _dragDropManager;
|
||||
private readonly IDataManager _gameData;
|
||||
private readonly IFramework _framework;
|
||||
private readonly IObjectTable _objects;
|
||||
private readonly CharacterBaseDestructor _characterBaseDestructor;
|
||||
|
||||
private Mod? _mod;
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
|
|
@ -565,26 +565,26 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents,
|
||||
ChangedItemDrawer changedItemDrawer, IObjectTable objects, IFramework framework)
|
||||
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager,
|
||||
ChangedItemDrawer changedItemDrawer, IObjectTable objects, IFramework framework, CharacterBaseDestructor characterBaseDestructor)
|
||||
: base(WindowBaseLabel)
|
||||
{
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_gameData = gameData;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_metaFileManager = metaFileManager;
|
||||
_stainService = stainService;
|
||||
_activeCollections = activeCollections;
|
||||
_modMergeTab = modMergeTab;
|
||||
_communicator = communicator;
|
||||
_dragDropManager = dragDropManager;
|
||||
_textures = textures;
|
||||
_fileDialog = fileDialog;
|
||||
_gameEvents = gameEvents;
|
||||
_objects = objects;
|
||||
_framework = framework;
|
||||
_performance = performance;
|
||||
_itemSwapTab = itemSwapTab;
|
||||
_gameData = gameData;
|
||||
_config = config;
|
||||
_editor = editor;
|
||||
_metaFileManager = metaFileManager;
|
||||
_stainService = stainService;
|
||||
_activeCollections = activeCollections;
|
||||
_modMergeTab = modMergeTab;
|
||||
_communicator = communicator;
|
||||
_dragDropManager = dragDropManager;
|
||||
_textures = textures;
|
||||
_fileDialog = fileDialog;
|
||||
_objects = objects;
|
||||
_framework = framework;
|
||||
_characterBaseDestructor = characterBaseDestructor;
|
||||
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
|
||||
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
|
||||
|
|
@ -598,12 +598,12 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_resourceTreeFactory = resourceTreeFactory;
|
||||
_quickImportViewer =
|
||||
new ResourceTreeViewer(_config, resourceTreeFactory, changedItemDrawer, 2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ModEditWindow);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
|
||||
_communicator.ModPathChanged.Unsubscribe(OnModPathChange);
|
||||
_editor?.Dispose();
|
||||
_materialTab.Dispose();
|
||||
_modelTab.Dispose();
|
||||
|
|
@ -613,7 +613,7 @@ public partial class ModEditWindow : Window, IDisposable
|
|||
_center.Dispose();
|
||||
}
|
||||
|
||||
private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
|
||||
private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
|
||||
{
|
||||
if (type is ModPathChangeType.Reloaded or ModPathChangeType.Moved)
|
||||
ChangeMod(mod);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,18 @@
|
|||
"Unosquare.Swan.Lite": "3.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Common": {
|
||||
"type": "Direct",
|
||||
"requested": "[4.8.0, )",
|
||||
"resolved": "4.8.0",
|
||||
"contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
|
||||
"dependencies": {
|
||||
"Microsoft.CodeAnalysis.Analyzers": "3.3.4",
|
||||
"System.Collections.Immutable": "7.0.0",
|
||||
"System.Reflection.Metadata": "7.0.0",
|
||||
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection": {
|
||||
"type": "Direct",
|
||||
"requested": "[7.0.0, )",
|
||||
|
|
@ -36,6 +48,11 @@
|
|||
"System.Text.Encoding.CodePages": "5.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.CodeAnalysis.Analyzers": {
|
||||
"type": "Transitive",
|
||||
"resolved": "3.3.4",
|
||||
"contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
|
||||
},
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
|
|
@ -46,10 +63,23 @@
|
|||
"resolved": "5.0.0",
|
||||
"contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ=="
|
||||
},
|
||||
"System.Collections.Immutable": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ=="
|
||||
},
|
||||
"System.Reflection.Metadata": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.0.0",
|
||||
"contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
|
||||
"dependencies": {
|
||||
"System.Collections.Immutable": "7.0.0"
|
||||
}
|
||||
},
|
||||
"System.Runtime.CompilerServices.Unsafe": {
|
||||
"type": "Transitive",
|
||||
"resolved": "5.0.0",
|
||||
"contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA=="
|
||||
"resolved": "6.0.0",
|
||||
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
|
||||
},
|
||||
"System.Text.Encoding.CodePages": {
|
||||
"type": "Transitive",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue