mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-23 00:07:53 +01:00
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:
parent
2b4a01df06
commit
d7205344eb
32 changed files with 826 additions and 80 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.String.Classes;
|
||||
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
104
Penumbra/Interop/ResourceTree/ResourceTreeApiHelper.cs
Normal file
104
Penumbra/Interop/ResourceTree/ResourceTreeApiHelper.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue