ResourceTree improvements + IPC

- Moves ResourceType enum out of GameData as discussed on Discord ;
- Adds new color coding for local player and non-networked objects on On-Screen ;
- Adds ResourceTree-related IPC ;
- Fixes #342.
This commit is contained in:
Exter-N 2023-09-17 22:25:31 +02:00 committed by Ottermandias
parent 2b4a01df06
commit d7205344eb
32 changed files with 826 additions and 80 deletions

View file

@ -4,8 +4,8 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.Collections;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.String.Classes;

View file

@ -3,6 +3,7 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes;
using Penumbra.Collections;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;

View file

@ -3,7 +3,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Structs;
using Penumbra.String;

View file

@ -1,8 +1,8 @@
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.Interop.Services;
using Penumbra.Interop.Structs;

View file

@ -1,6 +1,6 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.String.Classes;

View file

@ -3,8 +3,8 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.Interop;
using FFXIVClientStructs.STD;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
namespace Penumbra.Interop.ResourceLoading;

View file

@ -1,9 +1,9 @@
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.String.Classes;

View file

@ -1,8 +1,8 @@
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.String.Classes;
namespace Penumbra.Interop.ResourceLoading;

View file

@ -1,6 +1,7 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData;
using Penumbra.GameData.Enums;

View file

@ -1,4 +1,4 @@
using Penumbra.GameData.Enums;
using Penumbra.Api.Enums;
using Penumbra.String.Classes;
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;

View file

@ -12,9 +12,12 @@ namespace Penumbra.Interop.ResourceTree;
public class ResourceTree
{
public readonly string Name;
public readonly int GameObjectIndex;
public readonly nint GameObjectAddress;
public readonly nint DrawObjectAddress;
public readonly bool LocalPlayerRelated;
public readonly bool PlayerRelated;
public readonly bool Networked;
public readonly string CollectionName;
public readonly List<ResourceNode> Nodes;
public readonly HashSet<ResourceNode> FlatNodes;
@ -23,15 +26,18 @@ public class ResourceTree
public CustomizeData CustomizeData;
public GenderRace RaceCode;
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
public ResourceTree(string name, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, bool localPlayerRelated, bool playerRelated, bool networked, string collectionName)
{
Name = name;
GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress;
PlayerRelated = playerRelated;
CollectionName = collectionName;
Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>();
Name = name;
GameObjectIndex = gameObjectIndex;
GameObjectAddress = gameObjectAddress;
DrawObjectAddress = drawObjectAddress;
LocalPlayerRelated = localPlayerRelated;
Networked = networked;
PlayerRelated = playerRelated;
CollectionName = collectionName;
Nodes = new List<ResourceNode>();
FlatNodes = new HashSet<ResourceNode>();
}
internal unsafe void LoadResources(GlobalResolveContext globalContext)
@ -64,7 +70,7 @@ public class ResourceTree
AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
if (model->GetModelType() == CharacterBase.ModelType.Human)
AddHumanResources(globalContext, (HumanExt*)model);
}

View file

@ -0,0 +1,104 @@
using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.Api.Enums;
using Penumbra.UI;
namespace Penumbra.Interop.ResourceTree;
internal static class ResourceTreeApiHelper
{
public static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionaries(IEnumerable<(Character, ResourceTree)> resourceTrees,
bool mergeSameCollection)
=> mergeSameCollection ? GetResourcePathDictionariesMerged(resourceTrees) : GetResourcePathDictionariesUnmerged(resourceTrees);
private static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionariesMerged(IEnumerable<(Character, ResourceTree)> resourceTrees)
{
var collections = new Dictionary<ushort, string>(4);
var pathDictionaries = new Dictionary<string, Dictionary<string, HashSet<string>>>(4);
foreach (var (gameObject, resourceTree) in resourceTrees)
{
if (collections.ContainsKey(gameObject.ObjectIndex))
continue;
collections.Add(gameObject.ObjectIndex, resourceTree.CollectionName);
if (!pathDictionaries.TryGetValue(resourceTree.CollectionName, out var pathDictionary))
{
pathDictionary = new();
pathDictionaries.Add(resourceTree.CollectionName, pathDictionary);
}
CollectResourcePaths(pathDictionary, resourceTree);
}
var pathRODictionaries = pathDictionaries.ToDictionary(pair => pair.Key,
pair => (IReadOnlyDictionary<string, string[]>)pair.Value.ToDictionary(pair => pair.Key, pair => pair.Value.ToArray()).AsReadOnly());
return collections.ToDictionary(pair => pair.Key, pair => pathRODictionaries[pair.Value]);
}
private static Dictionary<ushort, IReadOnlyDictionary<string, string[]>> GetResourcePathDictionariesUnmerged(IEnumerable<(Character, ResourceTree)> resourceTrees)
{
var pathDictionaries = new Dictionary<ushort, Dictionary<string, HashSet<string>>>(4);
foreach (var (gameObject, resourceTree) in resourceTrees)
{
if (pathDictionaries.ContainsKey(gameObject.ObjectIndex))
continue;
var pathDictionary = new Dictionary<string, HashSet<string>>();
pathDictionaries.Add(gameObject.ObjectIndex, pathDictionary);
CollectResourcePaths(pathDictionary, resourceTree);
}
return pathDictionaries.ToDictionary(pair => pair.Key,
pair => (IReadOnlyDictionary<string, string[]>)pair.Value.ToDictionary(pair => pair.Key, pair => pair.Value.ToArray()).AsReadOnly());
}
private static void CollectResourcePaths(Dictionary<string, HashSet<string>> pathDictionary, ResourceTree resourceTree)
{
foreach (var node in resourceTree.FlatNodes)
{
if (node.PossibleGamePaths.Length == 0)
continue;
var fullPath = node.FullPath.ToPath();
if (!pathDictionary.TryGetValue(fullPath, out var gamePaths))
{
gamePaths = new();
pathDictionary.Add(fullPath, gamePaths);
}
foreach (var gamePath in node.PossibleGamePaths)
gamePaths.Add(gamePath.ToString());
}
}
public static Dictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetResourcesOfType(IEnumerable<(Character, ResourceTree)> resourceTrees,
ResourceType type)
{
var resDictionaries = new Dictionary<ushort, Dictionary<nint, (string, string, ChangedItemIcon)>>(4);
foreach (var (gameObject, resourceTree) in resourceTrees)
{
if (resDictionaries.ContainsKey(gameObject.ObjectIndex))
continue;
var resDictionary = new Dictionary<nint, (string, string, ChangedItemIcon)>();
resDictionaries.Add(gameObject.ObjectIndex, resDictionary);
foreach (var node in resourceTree.FlatNodes)
{
if (node.Type != type)
continue;
if (resDictionary.ContainsKey(node.ResourceHandle))
continue;
var fullPath = node.FullPath.ToPath();
resDictionary.Add(node.ResourceHandle, (fullPath, node.Name ?? string.Empty, ChangedItemDrawer.ToApiIcon(node.Icon)));
}
}
return resDictionaries.ToDictionary(pair => pair.Key,
pair => (IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>)pair.Value.AsReadOnly());
}
}

View file

@ -27,21 +27,35 @@ public class ResourceTreeFactory
_actors = actors;
}
public ResourceTree[] FromObjectTable(bool withNames = true, bool redactExternalPaths = true)
{
var cache = new TreeBuildCache(_objects, _gameData);
private TreeBuildCache CreateTreeBuildCache()
=> new(_objects, _gameData, _actors);
return cache.Characters
.Select(c => FromCharacter(c, cache, withNames, redactExternalPaths))
.OfType<ResourceTree>()
.ToArray();
public IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> GetLocalPlayerRelatedCharacters()
{
var cache = CreateTreeBuildCache();
return cache.Characters.Where(cache.IsLocalPlayerRelated);
}
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromObjectTable(
bool localPlayerRelatedOnly = false, bool withUIData = true, bool redactExternalPaths = true)
{
var cache = CreateTreeBuildCache();
var characters = localPlayerRelatedOnly ? cache.Characters.Where(cache.IsLocalPlayerRelated) : cache.Characters;
foreach (var character in characters)
{
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
if (tree != null)
yield return (character, tree);
}
}
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters(
IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters,
bool withUIData = true, bool redactExternalPaths = true)
{
var cache = new TreeBuildCache(_objects, _gameData);
var cache = CreateTreeBuildCache();
foreach (var character in characters)
{
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
@ -52,7 +66,7 @@ public class ResourceTreeFactory
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withUIData = true,
bool redactExternalPaths = true)
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withUIData, redactExternalPaths);
=> FromCharacter(character, CreateTreeBuildCache(), withUIData, redactExternalPaths);
private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache,
bool withUIData = true, bool redactExternalPaths = true)
@ -69,8 +83,10 @@ public class ResourceTreeFactory
if (!collectionResolveData.Valid)
return null;
var (name, related) = GetCharacterName(character, cache);
var tree = new ResourceTree(name, (nint)gameObjStruct, (nint)drawObjStruct, related, collectionResolveData.ModCollection.Name);
var localPlayerRelated = cache.IsLocalPlayerRelated(character);
var (name, related) = GetCharacterName(character, cache);
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related, networked, collectionResolveData.ModCollection.Name);
var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withUIData, redactExternalPaths);
tree.LoadResources(globalContext);

View file

@ -1,6 +1,8 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin.Services;
using Penumbra.GameData.Files;
using Penumbra.Interop.Services;
using Penumbra.Services;
using Penumbra.String.Classes;
namespace Penumbra.Interop.ResourceTree;
@ -8,18 +10,38 @@ namespace Penumbra.Interop.ResourceTree;
internal class TreeBuildCache
{
private readonly IDataManager _dataManager;
private readonly ActorService _actors;
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = new();
private readonly uint _localPlayerId;
public readonly List<Character> Characters;
public readonly Dictionary<uint, Character> CharactersById;
public TreeBuildCache(IObjectTable objects, IDataManager dataManager)
public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors)
{
_dataManager = dataManager;
Characters = objects.Where(c => c is Character ch && ch.IsValid()).Cast<Character>().ToList();
_dataManager = dataManager;
_actors = actors;
Characters = objects.OfType<Character>().Where(ch => ch.IsValid()).ToList();
CharactersById = Characters
.Where(c => c.ObjectId != GameObject.InvalidGameObjectId)
.GroupBy(c => c.ObjectId)
.ToDictionary(c => c.Key, c => c.First());
_localPlayerId = Characters.Count > 0 && Characters[0].ObjectIndex == 0 ? Characters[0].ObjectId : GameObject.InvalidGameObjectId;
}
public unsafe bool IsLocalPlayerRelated(Character character)
{
if (_localPlayerId == GameObject.InvalidGameObjectId)
return false;
// Index 0 is the local player, index 1 is the mount/minion/accessory.
if (character.ObjectIndex < 2 || character.ObjectIndex == RedrawService.GPosePlayerIdx)
return true;
if (!_actors.AwaitedService.FromObject(character, out var owner, true, false, false).IsValid)
return false;
// Check for SMN/SCH pet, chocobo and other owned NPCs.
return owner != null && owner->ObjectID == _localPlayerId;
}
/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>

View file

@ -1,6 +1,6 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.GameData.Enums;
using Penumbra.Interop.ResourceLoading;
using Penumbra.String.Classes;

View file

@ -1,8 +1,8 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.String;
using Penumbra.String.Classes;