This is going rather well.

This commit is contained in:
Ottermandias 2023-03-11 17:50:32 +01:00
parent 73e2793da6
commit bdaff7b781
48 changed files with 2944 additions and 2952 deletions

View file

@ -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);
}
}
}
}

View file

@ -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);
}

View file

@ -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);
}
}
}
}
}

View file

@ -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;

View file

@ -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 );

View file

@ -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;
}