mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-30 20:33:43 +01:00
This is going rather well.
This commit is contained in:
parent
73e2793da6
commit
bdaff7b781
48 changed files with 2944 additions and 2952 deletions
|
|
@ -2,9 +2,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.Services;
|
||||
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
public class CutsceneCharacters : IDisposable
|
||||
|
|
@ -14,39 +14,39 @@ public class CutsceneCharacters : IDisposable
|
|||
public const int CutsceneEndIdx = CutsceneStartIdx + CutsceneSlots;
|
||||
|
||||
private readonly GameEventManager _events;
|
||||
private readonly short[] _copiedCharacters = Enumerable.Repeat( ( short )-1, CutsceneSlots ).ToArray();
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray();
|
||||
|
||||
public IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > Actors
|
||||
=> Enumerable.Range( CutsceneStartIdx, CutsceneSlots )
|
||||
.Where( i => DalamudServices.Objects[ i ] != null )
|
||||
.Select( i => KeyValuePair.Create( i, this[ i ] ?? DalamudServices.Objects[ i ]! ) );
|
||||
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 CutsceneCharacters(GameEventManager events)
|
||||
public CutsceneCharacters(ObjectTable objects, GameEventManager events)
|
||||
{
|
||||
_events = events;
|
||||
_objects = objects;
|
||||
_events = events;
|
||||
Enable();
|
||||
}
|
||||
|
||||
// Get the related actor to a cutscene actor.
|
||||
// Does not check for valid input index.
|
||||
// Returns null if no connected actor is set or the actor does not exist anymore.
|
||||
public global::Dalamud.Game.ClientState.Objects.Types.GameObject? this[ int idx ]
|
||||
public Dalamud.Game.ClientState.Objects.Types.GameObject? this[int idx]
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert( idx is >= CutsceneStartIdx and < CutsceneEndIdx );
|
||||
idx = _copiedCharacters[ idx - CutsceneStartIdx ];
|
||||
return idx < 0 ? null : DalamudServices.Objects[ idx ];
|
||||
Debug.Assert(idx is >= CutsceneStartIdx and < CutsceneEndIdx);
|
||||
idx = _copiedCharacters[idx - CutsceneStartIdx];
|
||||
return idx < 0 ? null : _objects[idx];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the currently set index of a parent or -1 if none is set or the index is invalid.
|
||||
public int GetParentIndex( int idx )
|
||||
public int GetParentIndex(int idx)
|
||||
{
|
||||
if( idx is >= CutsceneStartIdx and < CutsceneEndIdx )
|
||||
{
|
||||
return _copiedCharacters[ idx - CutsceneStartIdx ];
|
||||
}
|
||||
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||
return _copiedCharacters[idx - CutsceneStartIdx];
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -66,21 +66,21 @@ public class CutsceneCharacters : IDisposable
|
|||
public void Dispose()
|
||||
=> Disable();
|
||||
|
||||
private unsafe void OnCharacterDestructor( Character* character )
|
||||
private unsafe void OnCharacterDestructor(Character* character)
|
||||
{
|
||||
if( character->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
|
||||
if (character->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||
{
|
||||
var idx = character->GameObject.ObjectIndex - CutsceneStartIdx;
|
||||
_copiedCharacters[ idx ] = -1;
|
||||
_copiedCharacters[idx] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void OnCharacterCopy( Character* target, Character* source )
|
||||
private unsafe void OnCharacterCopy(Character* target, Character* source)
|
||||
{
|
||||
if( target != null && target->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx )
|
||||
if (target != null && target->GameObject.ObjectIndex is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||
{
|
||||
var idx = target->GameObject.ObjectIndex - CutsceneStartIdx;
|
||||
_copiedCharacters[idx] = (short) (source != null ? source->GameObject.ObjectIndex : -1);
|
||||
_copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,72 +5,68 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.Services;
|
||||
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPtr Address, ActorIdentifier Identifier, ModCollection Collection) >
|
||||
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(IntPtr Address, ActorIdentifier Identifier, ModCollection Collection)>
|
||||
{
|
||||
private readonly GameEventManager _events;
|
||||
private readonly Dictionary< IntPtr, (ActorIdentifier, ModCollection) > _cache = new(317);
|
||||
private bool _dirty = false;
|
||||
private bool _enabled = false;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly GameEventManager _events;
|
||||
private readonly Dictionary<IntPtr, (ActorIdentifier, ModCollection)> _cache = new(317);
|
||||
private bool _dirty = false;
|
||||
private bool _enabled = false;
|
||||
|
||||
public IdentifiedCollectionCache(GameEventManager events)
|
||||
public IdentifiedCollectionCache(CommunicatorService communicator, GameEventManager events)
|
||||
{
|
||||
_events = events;
|
||||
_communicator = communicator;
|
||||
_events = events;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if( _enabled )
|
||||
{
|
||||
if (_enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
Penumbra.CollectionManager.CollectionChanged += CollectionChangeClear;
|
||||
Penumbra.TempMods.CollectionChanged += CollectionChangeClear;
|
||||
DalamudServices.ClientState.TerritoryChanged += TerritoryClear;
|
||||
_communicator.CollectionChange.Event += CollectionChangeClear;
|
||||
DalamudServices.ClientState.TerritoryChanged += TerritoryClear;
|
||||
_events.CharacterDestructor += OnCharacterDestruct;
|
||||
_enabled = true;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if( !_enabled )
|
||||
{
|
||||
if (!_enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
Penumbra.CollectionManager.CollectionChanged -= CollectionChangeClear;
|
||||
Penumbra.TempMods.CollectionChanged -= CollectionChangeClear;
|
||||
DalamudServices.ClientState.TerritoryChanged -= TerritoryClear;
|
||||
_communicator.CollectionChange.Event -= CollectionChangeClear;
|
||||
DalamudServices.ClientState.TerritoryChanged -= TerritoryClear;
|
||||
_events.CharacterDestructor -= OnCharacterDestruct;
|
||||
_enabled = false;
|
||||
}
|
||||
|
||||
public ResolveData Set( ModCollection collection, ActorIdentifier identifier, GameObject* data )
|
||||
public ResolveData Set(ModCollection collection, ActorIdentifier identifier, GameObject* data)
|
||||
{
|
||||
if( _dirty )
|
||||
if (_dirty)
|
||||
{
|
||||
_dirty = false;
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
_cache[ ( IntPtr )data ] = ( identifier, collection );
|
||||
return collection.ToResolveData( data );
|
||||
_cache[(IntPtr)data] = (identifier, collection);
|
||||
return collection.ToResolveData(data);
|
||||
}
|
||||
|
||||
public bool TryGetValue( GameObject* gameObject, out ResolveData resolve )
|
||||
public bool TryGetValue(GameObject* gameObject, out ResolveData resolve)
|
||||
{
|
||||
if( _dirty )
|
||||
if (_dirty)
|
||||
{
|
||||
_dirty = false;
|
||||
_cache.Clear();
|
||||
}
|
||||
else if( _cache.TryGetValue( ( IntPtr )gameObject, out var p ) )
|
||||
else if (_cache.TryGetValue((IntPtr)gameObject, out var p))
|
||||
{
|
||||
resolve = p.Item2.ToResolveData( gameObject );
|
||||
resolve = p.Item2.ToResolveData(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -81,19 +77,17 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
|
|||
public void Dispose()
|
||||
{
|
||||
Disable();
|
||||
GC.SuppressFinalize( this );
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public IEnumerator< (IntPtr Address, ActorIdentifier Identifier, ModCollection Collection) > GetEnumerator()
|
||||
public IEnumerator<(IntPtr Address, ActorIdentifier Identifier, ModCollection Collection)> GetEnumerator()
|
||||
{
|
||||
foreach( var (address, (identifier, collection)) in _cache )
|
||||
foreach (var (address, (identifier, collection)) in _cache)
|
||||
{
|
||||
if( _dirty )
|
||||
{
|
||||
if (_dirty)
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return ( address, identifier, collection );
|
||||
yield return (address, identifier, collection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,17 +97,15 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPt
|
|||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
private void CollectionChangeClear( CollectionType type, ModCollection? _1, ModCollection? _2, string _3 )
|
||||
private void CollectionChangeClear(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
||||
{
|
||||
if( type is not (CollectionType.Current or CollectionType.Interface or CollectionType.Inactive) )
|
||||
{
|
||||
if (type is not (CollectionType.Current or CollectionType.Interface or CollectionType.Inactive))
|
||||
_dirty = _cache.Count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void TerritoryClear( object? _1, ushort _2 )
|
||||
private void TerritoryClear(object? _1, ushort _2)
|
||||
=> _dirty = _cache.Count > 0;
|
||||
|
||||
private void OnCharacterDestruct( Character* character )
|
||||
=> _cache.Remove( ( IntPtr )character );
|
||||
}
|
||||
private void OnCharacterDestruct(Character* character)
|
||||
=> _cache.Remove((IntPtr)character);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,43 +12,42 @@ using Penumbra.GameData;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
using Penumbra.Services;
|
||||
|
||||
using Penumbra.Services;
|
||||
|
||||
namespace Penumbra.Interop.Resolver;
|
||||
|
||||
public unsafe partial class PathResolver
|
||||
{
|
||||
public class DrawObjectState
|
||||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
public static event CreatingCharacterBaseDelegate? CreatingCharacterBase;
|
||||
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||
public static event CreatedCharacterBaseDelegate? CreatedCharacterBase;
|
||||
|
||||
public IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjects
|
||||
public IEnumerable<KeyValuePair<IntPtr, (ResolveData, int)>> DrawObjects
|
||||
=> _drawObjectToObject;
|
||||
|
||||
public int Count
|
||||
=> _drawObjectToObject.Count;
|
||||
|
||||
public bool TryGetValue( IntPtr drawObject, out (ResolveData, int) value, out GameObject* gameObject )
|
||||
public bool TryGetValue(IntPtr drawObject, out (ResolveData, int) value, out GameObject* gameObject)
|
||||
{
|
||||
gameObject = null;
|
||||
if( !_drawObjectToObject.TryGetValue( drawObject, out value ) )
|
||||
{
|
||||
if (!_drawObjectToObject.TryGetValue(drawObject, out value))
|
||||
return false;
|
||||
}
|
||||
|
||||
var gameObjectIdx = value.Item2;
|
||||
return VerifyEntry( drawObject, gameObjectIdx, out gameObject );
|
||||
return VerifyEntry(drawObject, gameObjectIdx, out gameObject);
|
||||
}
|
||||
|
||||
|
||||
// Set and update a parent object if it exists and a last game object is set.
|
||||
public ResolveData CheckParentDrawObject( IntPtr drawObject, IntPtr parentObject )
|
||||
public ResolveData CheckParentDrawObject(IntPtr drawObject, IntPtr parentObject)
|
||||
{
|
||||
if( parentObject == IntPtr.Zero && LastGameObject != null )
|
||||
if (parentObject == IntPtr.Zero && LastGameObject != null)
|
||||
{
|
||||
var collection = IdentifyCollection( LastGameObject, true );
|
||||
_drawObjectToObject[ drawObject ] = ( collection, LastGameObject->ObjectIndex );
|
||||
var collection = IdentifyCollection(LastGameObject, true);
|
||||
_drawObjectToObject[drawObject] = (collection, LastGameObject->ObjectIndex);
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
|
@ -56,11 +55,11 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
|
||||
public bool HandleDecalFile( ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData )
|
||||
public bool HandleDecalFile(ResourceType type, Utf8GamePath gamePath, out ResolveData resolveData)
|
||||
{
|
||||
if( type == ResourceType.Tex
|
||||
&& LastCreatedCollection.Valid
|
||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( "decal"u8 ) )
|
||||
if (type == ResourceType.Tex
|
||||
&& LastCreatedCollection.Valid
|
||||
&& gamePath.Path.Substring("chara/common/texture/".Length).StartsWith("decal"u8))
|
||||
{
|
||||
resolveData = LastCreatedCollection;
|
||||
return true;
|
||||
|
|
@ -76,9 +75,10 @@ public unsafe partial class PathResolver
|
|||
|
||||
public GameObject* LastGameObject { get; private set; }
|
||||
|
||||
public DrawObjectState()
|
||||
public DrawObjectState(CommunicatorService communicator)
|
||||
{
|
||||
SignatureHelper.Initialise( this );
|
||||
SignatureHelper.Initialise(this);
|
||||
_communicator = communicator;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
|
|
@ -88,8 +88,7 @@ public unsafe partial class PathResolver
|
|||
_enableDrawHook.Enable();
|
||||
_weaponReloadHook.Enable();
|
||||
InitializeDrawObjects();
|
||||
Penumbra.CollectionManager.CollectionChanged += CheckCollections;
|
||||
Penumbra.TempMods.CollectionChanged += CheckCollections;
|
||||
_communicator.CollectionChange.Event += CheckCollections;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
|
|
@ -98,8 +97,7 @@ public unsafe partial class PathResolver
|
|||
_characterBaseDestructorHook.Disable();
|
||||
_enableDrawHook.Disable();
|
||||
_weaponReloadHook.Disable();
|
||||
Penumbra.CollectionManager.CollectionChanged -= CheckCollections;
|
||||
Penumbra.TempMods.CollectionChanged -= CheckCollections;
|
||||
_communicator.CollectionChange.Event -= CheckCollections;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -112,63 +110,61 @@ public unsafe partial class PathResolver
|
|||
}
|
||||
|
||||
// Check that a linked DrawObject still corresponds to the correct actor and that it still exists, otherwise remove it.
|
||||
private bool VerifyEntry( IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject )
|
||||
private bool VerifyEntry(IntPtr drawObject, int gameObjectIdx, out GameObject* gameObject)
|
||||
{
|
||||
gameObject = ( GameObject* )DalamudServices.Objects.GetObjectAddress( gameObjectIdx );
|
||||
var draw = ( DrawObject* )drawObject;
|
||||
if( gameObject != null
|
||||
&& ( gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject ) )
|
||||
{
|
||||
gameObject = (GameObject*)DalamudServices.Objects.GetObjectAddress(gameObjectIdx);
|
||||
var draw = (DrawObject*)drawObject;
|
||||
if (gameObject != null
|
||||
&& (gameObject->DrawObject == draw || draw != null && gameObject->DrawObject == draw->Object.ParentObject))
|
||||
return true;
|
||||
}
|
||||
|
||||
gameObject = null;
|
||||
_drawObjectToObject.Remove( drawObject );
|
||||
_drawObjectToObject.Remove(drawObject);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This map links DrawObjects directly to Actors (by ObjectTable index) and their collections.
|
||||
// It contains any DrawObjects that correspond to a human actor, even those without specific collections.
|
||||
private readonly Dictionary< IntPtr, (ResolveData, int) > _drawObjectToObject = new();
|
||||
private ResolveData _lastCreatedCollection = ResolveData.Invalid;
|
||||
private readonly Dictionary<IntPtr, (ResolveData, int)> _drawObjectToObject = new();
|
||||
private ResolveData _lastCreatedCollection = ResolveData.Invalid;
|
||||
|
||||
// Keep track of created DrawObjects that are CharacterBase,
|
||||
// and use the last game object that called EnableDraw to link them.
|
||||
private delegate IntPtr CharacterBaseCreateDelegate( uint a, IntPtr b, IntPtr c, byte d );
|
||||
private delegate IntPtr CharacterBaseCreateDelegate(uint a, IntPtr b, IntPtr c, byte d);
|
||||
|
||||
[Signature( Sigs.CharacterBaseCreate, DetourName = nameof( CharacterBaseCreateDetour ) )]
|
||||
private readonly Hook< CharacterBaseCreateDelegate > _characterBaseCreateHook = null!;
|
||||
[Signature(Sigs.CharacterBaseCreate, DetourName = nameof(CharacterBaseCreateDetour))]
|
||||
private readonly Hook<CharacterBaseCreateDelegate> _characterBaseCreateHook = null!;
|
||||
|
||||
private IntPtr CharacterBaseCreateDetour( uint a, IntPtr b, IntPtr c, byte d )
|
||||
private IntPtr CharacterBaseCreateDetour(uint a, IntPtr b, IntPtr c, byte d)
|
||||
{
|
||||
using var performance = Penumbra.Performance.Measure( PerformanceType.CharacterBaseCreate );
|
||||
using var performance = Penumbra.Performance.Measure(PerformanceType.CharacterBaseCreate);
|
||||
|
||||
var meta = DisposableContainer.Empty;
|
||||
if( LastGameObject != null )
|
||||
if (LastGameObject != null)
|
||||
{
|
||||
_lastCreatedCollection = IdentifyCollection( LastGameObject, false );
|
||||
_lastCreatedCollection = IdentifyCollection(LastGameObject, false);
|
||||
// Change the transparent or 1.0 Decal if necessary.
|
||||
var decal = new CharacterUtility.DecalReverter( _lastCreatedCollection.ModCollection, UsesDecal( a, c ) );
|
||||
var decal = new CharacterUtility.DecalReverter(_lastCreatedCollection.ModCollection, UsesDecal(a, c));
|
||||
// Change the rsp parameters.
|
||||
meta = new DisposableContainer( _lastCreatedCollection.ModCollection.TemporarilySetCmpFile(), decal );
|
||||
meta = new DisposableContainer(_lastCreatedCollection.ModCollection.TemporarilySetCmpFile(), decal);
|
||||
try
|
||||
{
|
||||
var modelPtr = &a;
|
||||
CreatingCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection.Name, ( IntPtr )modelPtr, b, c );
|
||||
CreatingCharacterBase?.Invoke((IntPtr)LastGameObject, _lastCreatedCollection!.ModCollection.Name, (IntPtr)modelPtr, b, c);
|
||||
}
|
||||
catch( Exception e )
|
||||
catch (Exception e)
|
||||
{
|
||||
Penumbra.Log.Error( $"Unknown Error during CreatingCharacterBase:\n{e}" );
|
||||
Penumbra.Log.Error($"Unknown Error during CreatingCharacterBase:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
var ret = _characterBaseCreateHook.Original( a, b, c, d );
|
||||
var ret = _characterBaseCreateHook.Original(a, b, c, d);
|
||||
try
|
||||
{
|
||||
if( LastGameObject != null && ret != IntPtr.Zero )
|
||||
if (LastGameObject != null && ret != IntPtr.Zero)
|
||||
{
|
||||
_drawObjectToObject[ ret ] = ( _lastCreatedCollection!, LastGameObject->ObjectIndex );
|
||||
CreatedCharacterBase?.Invoke( ( IntPtr )LastGameObject, _lastCreatedCollection!.ModCollection.Name, ret );
|
||||
_drawObjectToObject[ret] = (_lastCreatedCollection!, LastGameObject->ObjectIndex);
|
||||
CreatedCharacterBase?.Invoke((IntPtr)LastGameObject, _lastCreatedCollection!.ModCollection.Name, ret);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
|
@ -181,70 +177,66 @@ public unsafe partial class PathResolver
|
|||
|
||||
// Check the customize array for the FaceCustomization byte and the last bit of that.
|
||||
// Also check for humans.
|
||||
public static bool UsesDecal( uint modelId, IntPtr customizeData )
|
||||
=> modelId == 0 && ( ( byte* )customizeData )[ 12 ] > 0x7F;
|
||||
public static bool UsesDecal(uint modelId, IntPtr customizeData)
|
||||
=> modelId == 0 && ((byte*)customizeData)[12] > 0x7F;
|
||||
|
||||
|
||||
// Remove DrawObjects from the list when they are destroyed.
|
||||
private delegate void CharacterBaseDestructorDelegate( IntPtr drawBase );
|
||||
private delegate void CharacterBaseDestructorDelegate(IntPtr drawBase);
|
||||
|
||||
[Signature( Sigs.CharacterBaseDestructor, DetourName = nameof( CharacterBaseDestructorDetour ) )]
|
||||
private readonly Hook< CharacterBaseDestructorDelegate > _characterBaseDestructorHook = null!;
|
||||
[Signature(Sigs.CharacterBaseDestructor, DetourName = nameof(CharacterBaseDestructorDetour))]
|
||||
private readonly Hook<CharacterBaseDestructorDelegate> _characterBaseDestructorHook = null!;
|
||||
|
||||
private void CharacterBaseDestructorDetour( IntPtr drawBase )
|
||||
private void CharacterBaseDestructorDetour(IntPtr drawBase)
|
||||
{
|
||||
_drawObjectToObject.Remove( drawBase );
|
||||
_characterBaseDestructorHook!.Original.Invoke( drawBase );
|
||||
_drawObjectToObject.Remove(drawBase);
|
||||
_characterBaseDestructorHook!.Original.Invoke(drawBase);
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
private delegate void EnableDrawDelegate( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d );
|
||||
private delegate void EnableDrawDelegate(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d);
|
||||
|
||||
[Signature( Sigs.EnableDraw, DetourName = nameof( EnableDrawDetour ) )]
|
||||
private readonly Hook< EnableDrawDelegate > _enableDrawHook = null!;
|
||||
[Signature(Sigs.EnableDraw, DetourName = nameof(EnableDrawDetour))]
|
||||
private readonly Hook<EnableDrawDelegate> _enableDrawHook = null!;
|
||||
|
||||
private void EnableDrawDetour( IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d )
|
||||
private void EnableDrawDetour(IntPtr gameObject, IntPtr b, IntPtr c, IntPtr d)
|
||||
{
|
||||
var oldObject = LastGameObject;
|
||||
LastGameObject = ( GameObject* )gameObject;
|
||||
_enableDrawHook!.Original.Invoke( gameObject, b, c, d );
|
||||
LastGameObject = (GameObject*)gameObject;
|
||||
_enableDrawHook!.Original.Invoke(gameObject, b, c, d);
|
||||
LastGameObject = oldObject;
|
||||
}
|
||||
|
||||
// Not fully understood. The game object the weapon is loaded for is seemingly found at a1 + 8,
|
||||
// so we use that.
|
||||
private delegate void WeaponReloadFunc( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 );
|
||||
private delegate void WeaponReloadFunc(IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7);
|
||||
|
||||
[Signature( Sigs.WeaponReload, DetourName = nameof( WeaponReloadDetour ) )]
|
||||
private readonly Hook< WeaponReloadFunc > _weaponReloadHook = null!;
|
||||
[Signature(Sigs.WeaponReload, DetourName = nameof(WeaponReloadDetour))]
|
||||
private readonly Hook<WeaponReloadFunc> _weaponReloadHook = null!;
|
||||
|
||||
public void WeaponReloadDetour( IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7 )
|
||||
public void WeaponReloadDetour(IntPtr a1, uint a2, IntPtr a3, byte a4, byte a5, byte a6, byte a7)
|
||||
{
|
||||
var oldGame = LastGameObject;
|
||||
LastGameObject = *( GameObject** )( a1 + 8 );
|
||||
_weaponReloadHook!.Original( a1, a2, a3, a4, a5, a6, a7 );
|
||||
LastGameObject = *(GameObject**)(a1 + 8);
|
||||
_weaponReloadHook!.Original(a1, a2, a3, a4, a5, a6, a7);
|
||||
LastGameObject = oldGame;
|
||||
}
|
||||
|
||||
// Update collections linked to Game/DrawObjects due to a change in collection configuration.
|
||||
private void CheckCollections( CollectionType type, ModCollection? _1, ModCollection? _2, string _3 )
|
||||
private void CheckCollections(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
||||
{
|
||||
if( type is CollectionType.Inactive or CollectionType.Current or CollectionType.Interface )
|
||||
{
|
||||
if (type is CollectionType.Inactive or CollectionType.Current or CollectionType.Interface)
|
||||
return;
|
||||
}
|
||||
|
||||
foreach( var (key, (_, idx)) in _drawObjectToObject.ToArray() )
|
||||
foreach (var (key, (_, idx)) in _drawObjectToObject.ToArray())
|
||||
{
|
||||
if( !VerifyEntry( key, idx, out var obj ) )
|
||||
{
|
||||
_drawObjectToObject.Remove( key );
|
||||
}
|
||||
if (!VerifyEntry(key, idx, out var obj))
|
||||
_drawObjectToObject.Remove(key);
|
||||
|
||||
var newCollection = IdentifyCollection( obj, false );
|
||||
_drawObjectToObject[ key ] = ( newCollection, idx );
|
||||
var newCollection = IdentifyCollection(obj, false);
|
||||
_drawObjectToObject[key] = (newCollection, idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -252,14 +244,12 @@ public unsafe partial class PathResolver
|
|||
// We do not iterate the Dalamud table because it does not work when not logged in.
|
||||
private void InitializeDrawObjects()
|
||||
{
|
||||
for( var i = 0; i < DalamudServices.Objects.Length; ++i )
|
||||
for (var i = 0; i < DalamudServices.Objects.Length; ++i)
|
||||
{
|
||||
var ptr = ( GameObject* )DalamudServices.Objects.GetObjectAddress( i );
|
||||
if( ptr != null && ptr->IsCharacter() && ptr->DrawObject != null )
|
||||
{
|
||||
_drawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr, false ), ptr->ObjectIndex );
|
||||
}
|
||||
var ptr = (GameObject*)DalamudServices.Objects.GetObjectAddress(i);
|
||||
if (ptr != null && ptr->IsCharacter() && ptr->DrawObject != null)
|
||||
_drawObjectToObject[(IntPtr)ptr->DrawObject] = (IdentifyCollection(ptr, false), ptr->ObjectIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public unsafe partial class PathResolver
|
|||
|
||||
// Check both temporary and permanent character collections. Temporary first.
|
||||
private static ModCollection? CollectionByIdentifier( ActorIdentifier identifier )
|
||||
=> Penumbra.TempMods.Collections.TryGetCollection( identifier, out var collection )
|
||||
=> Penumbra.TempCollections.Collections.TryGetCollection( identifier, out var collection )
|
||||
|| Penumbra.CollectionManager.Individuals.TryGetCollection( identifier, out collection )
|
||||
? collection
|
||||
: null;
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ public partial class PathResolver
|
|||
}
|
||||
|
||||
var parentObject = ( IntPtr )( ( DrawObject* )drawObject )->Object.ParentObject;
|
||||
var parentCollection = DrawObjects.CheckParentDrawObject( drawObject, parentObject );
|
||||
var parentCollection = _drawObjects.CheckParentDrawObject( drawObject, parentObject );
|
||||
if( parentCollection.Valid )
|
||||
{
|
||||
return _parent._paths.ResolvePath( ( IntPtr )FindParent( parentObject, out _ ), parentCollection.ModCollection, path );
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ using Dalamud.Utility.Signatures;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Loader;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.Services;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.Util;
|
||||
|
|
@ -24,70 +25,70 @@ public partial class PathResolver : IDisposable
|
|||
{
|
||||
public bool Enabled { get; private set; }
|
||||
|
||||
private readonly ResourceLoader _loader;
|
||||
private static readonly CutsceneCharacters Cutscenes = new(Penumbra.GameEvents);
|
||||
private static readonly DrawObjectState DrawObjects = new();
|
||||
private static readonly BitArray ValidHumanModels;
|
||||
internal static readonly IdentifiedCollectionCache IdentifiedCache = new(Penumbra.GameEvents);
|
||||
private readonly AnimationState _animations;
|
||||
private readonly PathState _paths;
|
||||
private readonly MetaState _meta;
|
||||
private readonly SubfileHelper _subFiles;
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly ResourceLoader _loader;
|
||||
private static readonly CutsceneCharacters Cutscenes = new(DalamudServices.Objects, Penumbra.GameEvents); // TODO
|
||||
private static DrawObjectState _drawObjects = null!; // TODO
|
||||
private static readonly BitArray ValidHumanModels;
|
||||
internal static IdentifiedCollectionCache IdentifiedCache = null!; // TODO
|
||||
private readonly AnimationState _animations;
|
||||
private readonly PathState _paths;
|
||||
private readonly MetaState _meta;
|
||||
private readonly SubfileHelper _subFiles;
|
||||
|
||||
static PathResolver()
|
||||
=> ValidHumanModels = GetValidHumanModels( DalamudServices.GameData );
|
||||
=> ValidHumanModels = GetValidHumanModels(DalamudServices.GameData);
|
||||
|
||||
public unsafe PathResolver( ResourceLoader loader )
|
||||
public unsafe PathResolver(StartTracker timer, CommunicatorService communicator, GameEventManager events, ResourceLoader loader)
|
||||
{
|
||||
using var tApi = Penumbra.StartTimer.Measure( StartTimeType.PathResolver );
|
||||
SignatureHelper.Initialise( this );
|
||||
using var tApi = timer.Measure(StartTimeType.PathResolver);
|
||||
_communicator = communicator;
|
||||
IdentifiedCache = new IdentifiedCollectionCache(communicator, events);
|
||||
SignatureHelper.Initialise(this);
|
||||
_drawObjects = new DrawObjectState(_communicator);
|
||||
_loader = loader;
|
||||
_animations = new AnimationState( DrawObjects );
|
||||
_paths = new PathState( this );
|
||||
_meta = new MetaState( _paths.HumanVTable );
|
||||
_subFiles = new SubfileHelper( _loader, Penumbra.GameEvents );
|
||||
_animations = new AnimationState(_drawObjects);
|
||||
_paths = new PathState(this);
|
||||
_meta = new MetaState(_paths.HumanVTable);
|
||||
_subFiles = new SubfileHelper(_loader, Penumbra.GameEvents);
|
||||
}
|
||||
|
||||
// The modified resolver that handles game path resolving.
|
||||
private bool CharacterResolver( Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data )
|
||||
private bool CharacterResolver(Utf8GamePath gamePath, ResourceCategory _1, ResourceType type, int _2, out (FullPath?, ResolveData) data)
|
||||
{
|
||||
using var performance = Penumbra.Performance.Measure( PerformanceType.CharacterResolver );
|
||||
using var performance = Penumbra.Performance.Measure(PerformanceType.CharacterResolver);
|
||||
// Check if the path was marked for a specific collection,
|
||||
// or if it is a file loaded by a material, and if we are currently in a material load,
|
||||
// or if it is a face decal path and the current mod collection is set.
|
||||
// If not use the default collection.
|
||||
// We can remove paths after they have actually been loaded.
|
||||
// A potential next request will add the path anew.
|
||||
var nonDefault = _subFiles.HandleSubFiles( type, out var resolveData )
|
||||
|| _paths.Consume( gamePath.Path, out resolveData )
|
||||
|| _animations.HandleFiles( type, gamePath, out resolveData )
|
||||
|| DrawObjects.HandleDecalFile( type, gamePath, out resolveData );
|
||||
if( !nonDefault || !resolveData.Valid )
|
||||
{
|
||||
var nonDefault = _subFiles.HandleSubFiles(type, out var resolveData)
|
||||
|| _paths.Consume(gamePath.Path, out resolveData)
|
||||
|| _animations.HandleFiles(type, gamePath, out resolveData)
|
||||
|| _drawObjects.HandleDecalFile(type, gamePath, out resolveData);
|
||||
if (!nonDefault || !resolveData.Valid)
|
||||
resolveData = Penumbra.CollectionManager.Default.ToResolveData();
|
||||
}
|
||||
|
||||
// Resolve using character/default collection first, otherwise forced, as usual.
|
||||
var resolved = resolveData.ModCollection.ResolvePath( gamePath );
|
||||
var resolved = resolveData.ModCollection.ResolvePath(gamePath);
|
||||
|
||||
// Since mtrl files load their files separately, we need to add the new, resolved path
|
||||
// so that the functions loading tex and shpk can find that path and use its collection.
|
||||
// We also need to handle defaulted materials against a non-default collection.
|
||||
var path = resolved == null ? gamePath.Path : resolved.Value.InternalName;
|
||||
SubfileHelper.HandleCollection( resolveData, path, nonDefault, type, resolved, out data );
|
||||
SubfileHelper.HandleCollection(resolveData, path, nonDefault, type, resolved, out data);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if( Enabled )
|
||||
{
|
||||
if (Enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
Enabled = true;
|
||||
Cutscenes.Enable();
|
||||
DrawObjects.Enable();
|
||||
_drawObjects.Enable();
|
||||
IdentifiedCache.Enable();
|
||||
_animations.Enable();
|
||||
_paths.Enable();
|
||||
|
|
@ -95,19 +96,17 @@ public partial class PathResolver : IDisposable
|
|||
_subFiles.Enable();
|
||||
|
||||
_loader.ResolvePathCustomization += CharacterResolver;
|
||||
Penumbra.Log.Debug( "Character Path Resolver enabled." );
|
||||
Penumbra.Log.Debug("Character Path Resolver enabled.");
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if( !Enabled )
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
}
|
||||
|
||||
Enabled = false;
|
||||
_animations.Disable();
|
||||
DrawObjects.Disable();
|
||||
_drawObjects.Disable();
|
||||
Cutscenes.Disable();
|
||||
IdentifiedCache.Disable();
|
||||
_paths.Disable();
|
||||
|
|
@ -115,7 +114,7 @@ public partial class PathResolver : IDisposable
|
|||
_subFiles.Disable();
|
||||
|
||||
_loader.ResolvePathCustomization -= CharacterResolver;
|
||||
Penumbra.Log.Debug( "Character Path Resolver disabled." );
|
||||
Penumbra.Log.Debug("Character Path Resolver disabled.");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -123,58 +122,58 @@ public partial class PathResolver : IDisposable
|
|||
Disable();
|
||||
_paths.Dispose();
|
||||
_animations.Dispose();
|
||||
DrawObjects.Dispose();
|
||||
_drawObjects.Dispose();
|
||||
Cutscenes.Dispose();
|
||||
IdentifiedCache.Dispose();
|
||||
_meta.Dispose();
|
||||
_subFiles.Dispose();
|
||||
}
|
||||
|
||||
public static unsafe (IntPtr, ResolveData) IdentifyDrawObject( IntPtr drawObject )
|
||||
public static unsafe (IntPtr, ResolveData) IdentifyDrawObject(IntPtr drawObject)
|
||||
{
|
||||
var parent = FindParent( drawObject, out var resolveData );
|
||||
return ( ( IntPtr )parent, resolveData );
|
||||
var parent = FindParent(drawObject, out var resolveData);
|
||||
return ((IntPtr)parent, resolveData);
|
||||
}
|
||||
|
||||
public int CutsceneActor( int idx )
|
||||
=> Cutscenes.GetParentIndex( idx );
|
||||
public int CutsceneActor(int idx)
|
||||
=> Cutscenes.GetParentIndex(idx);
|
||||
|
||||
// Use the stored information to find the GameObject and Collection linked to a DrawObject.
|
||||
public static unsafe GameObject* FindParent( IntPtr drawObject, out ResolveData resolveData )
|
||||
public static unsafe GameObject* FindParent(IntPtr drawObject, out ResolveData resolveData)
|
||||
{
|
||||
if( DrawObjects.TryGetValue( drawObject, out var data, out var gameObject ) )
|
||||
if (_drawObjects.TryGetValue(drawObject, out var data, out var gameObject))
|
||||
{
|
||||
resolveData = data.Item1;
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
if( DrawObjects.LastGameObject != null
|
||||
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
|
||||
if (_drawObjects.LastGameObject != null
|
||||
&& (_drawObjects.LastGameObject->DrawObject == null || _drawObjects.LastGameObject->DrawObject == (DrawObject*)drawObject))
|
||||
{
|
||||
resolveData = IdentifyCollection( DrawObjects.LastGameObject, true );
|
||||
return DrawObjects.LastGameObject;
|
||||
resolveData = IdentifyCollection(_drawObjects.LastGameObject, true);
|
||||
return _drawObjects.LastGameObject;
|
||||
}
|
||||
|
||||
resolveData = IdentifyCollection( null, true );
|
||||
resolveData = IdentifyCollection(null, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static unsafe ResolveData GetResolveData( IntPtr drawObject )
|
||||
private static unsafe ResolveData GetResolveData(IntPtr drawObject)
|
||||
{
|
||||
var _ = FindParent( drawObject, out var resolveData );
|
||||
var _ = FindParent(drawObject, out var resolveData);
|
||||
return resolveData;
|
||||
}
|
||||
|
||||
internal IEnumerable< KeyValuePair< ByteString, ResolveData > > PathCollections
|
||||
internal IEnumerable<KeyValuePair<ByteString, ResolveData>> PathCollections
|
||||
=> _paths.Paths;
|
||||
|
||||
internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
|
||||
=> DrawObjects.DrawObjects;
|
||||
internal IEnumerable<KeyValuePair<IntPtr, (ResolveData, int)>> DrawObjectMap
|
||||
=> _drawObjects.DrawObjects;
|
||||
|
||||
internal IEnumerable< KeyValuePair< int, global::Dalamud.Game.ClientState.Objects.Types.GameObject > > CutsceneActors
|
||||
internal IEnumerable<KeyValuePair<int, global::Dalamud.Game.ClientState.Objects.Types.GameObject>> CutsceneActors
|
||||
=> Cutscenes.Actors;
|
||||
|
||||
internal IEnumerable< KeyValuePair< IntPtr, ResolveData > > ResourceCollections
|
||||
internal IEnumerable<KeyValuePair<IntPtr, ResolveData>> ResourceCollections
|
||||
=> _subFiles;
|
||||
|
||||
internal int SubfileCount
|
||||
|
|
@ -187,8 +186,8 @@ public partial class PathResolver : IDisposable
|
|||
=> _subFiles.AvfxData;
|
||||
|
||||
internal ResolveData LastGameObjectData
|
||||
=> DrawObjects.LastCreatedCollection;
|
||||
=> _drawObjects.LastCreatedCollection;
|
||||
|
||||
internal unsafe nint LastGameObject
|
||||
=> (nint) DrawObjects.LastGameObject;
|
||||
}
|
||||
=> (nint)_drawObjects.LastGameObject;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue