mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-14 04:34:19 +01:00
Merge remote-tracking branch 'upstream/master' into mdl-export
This commit is contained in:
commit
f71d922198
87 changed files with 904 additions and 890 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7098e9577117a3555f5f6181edae6cd306a4b5d4
|
Subproject commit 197d23eee167c232000f22ef40a7a2bded913b6c
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit b6a68ab60be6a46f8ede63425cd0716dedf693a3
|
Subproject commit db421413a15c48c63eb883dbfc2ac863c579d4c6
|
||||||
|
|
@ -40,23 +40,24 @@ public class IpcTester : IDisposable
|
||||||
private readonly Temporary _temporary;
|
private readonly Temporary _temporary;
|
||||||
private readonly ResourceTree _resourceTree;
|
private readonly ResourceTree _resourceTree;
|
||||||
|
|
||||||
public IpcTester(Configuration config, DalamudServices dalamud, PenumbraIpcProviders ipcProviders, ModManager modManager,
|
public IpcTester(Configuration config, DalamudPluginInterface pi, IObjectTable objects, IClientState clientState,
|
||||||
CollectionManager collections, TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService)
|
PenumbraIpcProviders ipcProviders, ModManager modManager, CollectionManager collections, TempModManager tempMods,
|
||||||
|
TempCollectionManager tempCollections, SaveService saveService)
|
||||||
{
|
{
|
||||||
_ipcProviders = ipcProviders;
|
_ipcProviders = ipcProviders;
|
||||||
_pluginState = new PluginState(dalamud.PluginInterface);
|
_pluginState = new PluginState(pi);
|
||||||
_ipcConfiguration = new IpcConfiguration(dalamud.PluginInterface);
|
_ipcConfiguration = new IpcConfiguration(pi);
|
||||||
_ui = new Ui(dalamud.PluginInterface);
|
_ui = new Ui(pi);
|
||||||
_redrawing = new Redrawing(dalamud);
|
_redrawing = new Redrawing(pi, objects, clientState);
|
||||||
_gameState = new GameState(dalamud.PluginInterface);
|
_gameState = new GameState(pi);
|
||||||
_resolve = new Resolve(dalamud.PluginInterface);
|
_resolve = new Resolve(pi);
|
||||||
_collections = new Collections(dalamud.PluginInterface);
|
_collections = new Collections(pi);
|
||||||
_meta = new Meta(dalamud.PluginInterface);
|
_meta = new Meta(pi);
|
||||||
_mods = new Mods(dalamud.PluginInterface);
|
_mods = new Mods(pi);
|
||||||
_modSettings = new ModSettings(dalamud.PluginInterface);
|
_modSettings = new ModSettings(pi);
|
||||||
_editing = new Editing(dalamud.PluginInterface);
|
_editing = new Editing(pi);
|
||||||
_temporary = new Temporary(dalamud.PluginInterface, modManager, collections, tempMods, tempCollections, saveService, config);
|
_temporary = new Temporary(pi, modManager, collections, tempMods, tempCollections, saveService, config);
|
||||||
_resourceTree = new ResourceTree(dalamud.PluginInterface, dalamud.Objects);
|
_resourceTree = new ResourceTree(pi, objects);
|
||||||
UnsubscribeEvents();
|
UnsubscribeEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,17 +399,21 @@ public class IpcTester : IDisposable
|
||||||
|
|
||||||
private class Redrawing
|
private class Redrawing
|
||||||
{
|
{
|
||||||
private readonly DalamudServices _dalamud;
|
private readonly DalamudPluginInterface _pi;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly IObjectTable _objects;
|
||||||
public readonly EventSubscriber<IntPtr, int> Redrawn;
|
public readonly EventSubscriber<IntPtr, int> Redrawn;
|
||||||
|
|
||||||
private string _redrawName = string.Empty;
|
private string _redrawName = string.Empty;
|
||||||
private int _redrawIndex = 0;
|
private int _redrawIndex = 0;
|
||||||
private string _lastRedrawnString = "None";
|
private string _lastRedrawnString = "None";
|
||||||
|
|
||||||
public Redrawing(DalamudServices dalamud)
|
public Redrawing(DalamudPluginInterface pi, IObjectTable objects, IClientState clientState)
|
||||||
{
|
{
|
||||||
_dalamud = dalamud;
|
_pi = pi;
|
||||||
Redrawn = Ipc.GameObjectRedrawn.Subscriber(_dalamud.PluginInterface, SetLastRedrawn);
|
_objects = objects;
|
||||||
|
_clientState = clientState;
|
||||||
|
Redrawn = Ipc.GameObjectRedrawn.Subscriber(_pi, SetLastRedrawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
@ -426,25 +431,25 @@ public class IpcTester : IDisposable
|
||||||
ImGui.InputTextWithHint("##redrawName", "Name...", ref _redrawName, 32);
|
ImGui.InputTextWithHint("##redrawName", "Name...", ref _redrawName, 32);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Redraw##Name"))
|
if (ImGui.Button("Redraw##Name"))
|
||||||
Ipc.RedrawObjectByName.Subscriber(_dalamud.PluginInterface).Invoke(_redrawName, RedrawType.Redraw);
|
Ipc.RedrawObjectByName.Subscriber(_pi).Invoke(_redrawName, RedrawType.Redraw);
|
||||||
|
|
||||||
DrawIntro(Ipc.RedrawObject.Label, "Redraw Player Character");
|
DrawIntro(Ipc.RedrawObject.Label, "Redraw Player Character");
|
||||||
if (ImGui.Button("Redraw##pc") && _dalamud.ClientState.LocalPlayer != null)
|
if (ImGui.Button("Redraw##pc") && _clientState.LocalPlayer != null)
|
||||||
Ipc.RedrawObject.Subscriber(_dalamud.PluginInterface).Invoke(_dalamud.ClientState.LocalPlayer, RedrawType.Redraw);
|
Ipc.RedrawObject.Subscriber(_pi).Invoke(_clientState.LocalPlayer, RedrawType.Redraw);
|
||||||
|
|
||||||
DrawIntro(Ipc.RedrawObjectByIndex.Label, "Redraw by Index");
|
DrawIntro(Ipc.RedrawObjectByIndex.Label, "Redraw by Index");
|
||||||
var tmp = _redrawIndex;
|
var tmp = _redrawIndex;
|
||||||
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
|
ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
|
||||||
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _dalamud.Objects.Length))
|
if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.Length))
|
||||||
_redrawIndex = Math.Clamp(tmp, 0, _dalamud.Objects.Length);
|
_redrawIndex = Math.Clamp(tmp, 0, _objects.Length);
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Redraw##Index"))
|
if (ImGui.Button("Redraw##Index"))
|
||||||
Ipc.RedrawObjectByIndex.Subscriber(_dalamud.PluginInterface).Invoke(_redrawIndex, RedrawType.Redraw);
|
Ipc.RedrawObjectByIndex.Subscriber(_pi).Invoke(_redrawIndex, RedrawType.Redraw);
|
||||||
|
|
||||||
DrawIntro(Ipc.RedrawAll.Label, "Redraw All");
|
DrawIntro(Ipc.RedrawAll.Label, "Redraw All");
|
||||||
if (ImGui.Button("Redraw##All"))
|
if (ImGui.Button("Redraw##All"))
|
||||||
Ipc.RedrawAll.Subscriber(_dalamud.PluginInterface).Invoke(RedrawType.Redraw);
|
Ipc.RedrawAll.Subscriber(_pi).Invoke(RedrawType.Redraw);
|
||||||
|
|
||||||
DrawIntro(Ipc.GameObjectRedrawn.Label, "Last Redrawn Object:");
|
DrawIntro(Ipc.GameObjectRedrawn.Label, "Last Redrawn Object:");
|
||||||
ImGui.TextUnformatted(_lastRedrawnString);
|
ImGui.TextUnformatted(_lastRedrawnString);
|
||||||
|
|
@ -453,12 +458,12 @@ public class IpcTester : IDisposable
|
||||||
private void SetLastRedrawn(IntPtr address, int index)
|
private void SetLastRedrawn(IntPtr address, int index)
|
||||||
{
|
{
|
||||||
if (index < 0
|
if (index < 0
|
||||||
|| index > _dalamud.Objects.Length
|
|| index > _objects.Length
|
||||||
|| address == IntPtr.Zero
|
|| address == IntPtr.Zero
|
||||||
|| _dalamud.Objects[index]?.Address != address)
|
|| _objects[index]?.Address != address)
|
||||||
_lastRedrawnString = "Invalid";
|
_lastRedrawnString = "Invalid";
|
||||||
|
|
||||||
_lastRedrawnString = $"{_dalamud.Objects[index]!.Name} (0x{address:X}, {index})";
|
_lastRedrawnString = $"{_objects[index]!.Name} (0x{address:X}, {index})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1695,6 +1700,7 @@ public class IpcTester : IDisposable
|
||||||
{
|
{
|
||||||
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||||
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
|
||||||
ImGui.TableSetupColumn("Object Address", ImGuiTableColumnFlags.WidthStretch, 0.2f);
|
ImGui.TableSetupColumn("Object Address", ImGuiTableColumnFlags.WidthStretch, 0.2f);
|
||||||
|
|
@ -1707,10 +1713,10 @@ public class IpcTester : IDisposable
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var hasChildren = node.Children.Any();
|
var hasChildren = node.Children.Any();
|
||||||
using var treeNode = ImRaii.TreeNode(
|
using var treeNode = ImRaii.TreeNode(
|
||||||
$"{(_withUIData ? (node.Name ?? "Unknown") : node.Type)}##{node.ObjectAddress:X8}",
|
$"{(_withUIData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
|
||||||
hasChildren ?
|
hasChildren
|
||||||
ImGuiTreeNodeFlags.SpanFullWidth :
|
? ImGuiTreeNodeFlags.SpanFullWidth
|
||||||
(ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen));
|
: ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen);
|
||||||
if (_withUIData)
|
if (_withUIData)
|
||||||
{
|
{
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -1718,6 +1724,7 @@ public class IpcTester : IDisposable
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
TextUnformattedMono(node.Icon.ToString());
|
TextUnformattedMono(node.Icon.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted(node.GamePath ?? "Unknown");
|
ImGui.TextUnformatted(node.GamePath ?? "Unknown");
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
|
|
@ -1728,11 +1735,9 @@ public class IpcTester : IDisposable
|
||||||
TextUnformattedMono($"0x{node.ResourceHandle:X8}");
|
TextUnformattedMono($"0x{node.ResourceHandle:X8}");
|
||||||
|
|
||||||
if (treeNode)
|
if (treeNode)
|
||||||
{
|
|
||||||
foreach (var child in node.Children)
|
foreach (var child in node.Children)
|
||||||
DrawNode(child);
|
DrawNode(child);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var node in tree.Nodes)
|
foreach (var node in tree.Nodes)
|
||||||
DrawNode(node);
|
DrawNode(node);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -88,14 +89,16 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private CommunicatorService _communicator;
|
private CommunicatorService _communicator;
|
||||||
private Lumina.GameData? _lumina;
|
private Lumina.GameData? _lumina;
|
||||||
|
|
||||||
|
private IDataManager _gameData;
|
||||||
|
private IFramework _framework;
|
||||||
|
private IObjectTable _objects;
|
||||||
private ModManager _modManager;
|
private ModManager _modManager;
|
||||||
private ResourceLoader _resourceLoader;
|
private ResourceLoader _resourceLoader;
|
||||||
private Configuration _config;
|
private Configuration _config;
|
||||||
private CollectionManager _collectionManager;
|
private CollectionManager _collectionManager;
|
||||||
private DalamudServices _dalamud;
|
|
||||||
private TempCollectionManager _tempCollections;
|
private TempCollectionManager _tempCollections;
|
||||||
private TempModManager _tempMods;
|
private TempModManager _tempMods;
|
||||||
private ActorService _actors;
|
private ActorManager _actors;
|
||||||
private CollectionResolver _collectionResolver;
|
private CollectionResolver _collectionResolver;
|
||||||
private CutsceneService _cutsceneService;
|
private CutsceneService _cutsceneService;
|
||||||
private ModImportManager _modImportManager;
|
private ModImportManager _modImportManager;
|
||||||
|
|
@ -106,18 +109,20 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private TextureManager _textureManager;
|
private TextureManager _textureManager;
|
||||||
private ResourceTreeFactory _resourceTreeFactory;
|
private ResourceTreeFactory _resourceTreeFactory;
|
||||||
|
|
||||||
public unsafe PenumbraApi(CommunicatorService communicator, ModManager modManager, ResourceLoader resourceLoader,
|
public unsafe PenumbraApi(CommunicatorService communicator, IDataManager gameData, IFramework framework, IObjectTable objects,
|
||||||
Configuration config, CollectionManager collectionManager, DalamudServices dalamud, TempCollectionManager tempCollections,
|
ModManager modManager, ResourceLoader resourceLoader, Configuration config, CollectionManager collectionManager,
|
||||||
TempModManager tempMods, ActorService actors, CollectionResolver collectionResolver, CutsceneService cutsceneService,
|
TempCollectionManager tempCollections, TempModManager tempMods, ActorManager actors, CollectionResolver collectionResolver,
|
||||||
ModImportManager modImportManager, CollectionEditor collectionEditor, RedrawService redrawService, ModFileSystem modFileSystem,
|
CutsceneService cutsceneService, ModImportManager modImportManager, CollectionEditor collectionEditor, RedrawService redrawService,
|
||||||
ConfigWindow configWindow, TextureManager textureManager, ResourceTreeFactory resourceTreeFactory)
|
ModFileSystem modFileSystem, ConfigWindow configWindow, TextureManager textureManager, ResourceTreeFactory resourceTreeFactory)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
|
_gameData = gameData;
|
||||||
|
_framework = framework;
|
||||||
|
_objects = objects;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_resourceLoader = resourceLoader;
|
_resourceLoader = resourceLoader;
|
||||||
_config = config;
|
_config = config;
|
||||||
_collectionManager = collectionManager;
|
_collectionManager = collectionManager;
|
||||||
_dalamud = dalamud;
|
|
||||||
_tempCollections = tempCollections;
|
_tempCollections = tempCollections;
|
||||||
_tempMods = tempMods;
|
_tempMods = tempMods;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
|
|
@ -130,7 +135,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
_configWindow = configWindow;
|
_configWindow = configWindow;
|
||||||
_textureManager = textureManager;
|
_textureManager = textureManager;
|
||||||
_resourceTreeFactory = resourceTreeFactory;
|
_resourceTreeFactory = resourceTreeFactory;
|
||||||
_lumina = _dalamud.GameData.GameData;
|
_lumina = gameData.GameData;
|
||||||
|
|
||||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||||
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
|
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
|
||||||
|
|
@ -153,7 +158,6 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
_resourceLoader = null!;
|
_resourceLoader = null!;
|
||||||
_config = null!;
|
_config = null!;
|
||||||
_collectionManager = null!;
|
_collectionManager = null!;
|
||||||
_dalamud = null!;
|
|
||||||
_tempCollections = null!;
|
_tempCollections = null!;
|
||||||
_tempMods = null!;
|
_tempMods = null!;
|
||||||
_actors = null!;
|
_actors = null!;
|
||||||
|
|
@ -166,6 +170,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
_configWindow = null!;
|
_configWindow = null!;
|
||||||
_textureManager = null!;
|
_textureManager = null!;
|
||||||
_resourceTreeFactory = null!;
|
_resourceTreeFactory = null!;
|
||||||
|
_framework = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public event ChangedItemClick? ChangedItemClicked
|
public event ChangedItemClick? ChangedItemClicked
|
||||||
|
|
@ -399,7 +404,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
return await Task.Run(async () =>
|
return await Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var playerCollection = await _dalamud.Framework.RunOnFrameworkThread(_collectionResolver.PlayerCollection).ConfigureAwait(false);
|
var playerCollection = await _framework.RunOnFrameworkThread(_collectionResolver.PlayerCollection).ConfigureAwait(false);
|
||||||
var forwardTask = Task.Run(() =>
|
var forwardTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
var forwardRet = new string[forward.Length];
|
var forwardRet = new string[forward.Length];
|
||||||
|
|
@ -851,7 +856,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
||||||
if (!ActorManager.VerifyPlayerName(character.AsSpan()) || tag.Length == 0)
|
if (!ActorIdentifierFactory.VerifyPlayerName(character.AsSpan()) || tag.Length == 0)
|
||||||
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
return (PenumbraApiEc.InvalidArgument, string.Empty);
|
||||||
|
|
||||||
var identifier = NameToIdentifier(character, ushort.MaxValue);
|
var identifier = NameToIdentifier(character, ushort.MaxValue);
|
||||||
|
|
@ -889,13 +894,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
{
|
{
|
||||||
CheckInitialized();
|
CheckInitialized();
|
||||||
|
|
||||||
if (!_actors.Valid)
|
if (actorIndex < 0 || actorIndex >= _objects.Length)
|
||||||
return PenumbraApiEc.SystemDisposed;
|
|
||||||
|
|
||||||
if (actorIndex < 0 || actorIndex >= _dalamud.Objects.Length)
|
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
|
|
||||||
var identifier = _actors.AwaitedService.FromObject(_dalamud.Objects[actorIndex], false, false, true);
|
var identifier = _actors.FromObject(_objects[actorIndex], false, false, true);
|
||||||
if (!identifier.IsValid)
|
if (!identifier.IsValid)
|
||||||
return PenumbraApiEc.InvalidArgument;
|
return PenumbraApiEc.InvalidArgument;
|
||||||
|
|
||||||
|
|
@ -1040,7 +1042,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects)
|
public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects)
|
||||||
{
|
{
|
||||||
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
|
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
|
||||||
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, 0);
|
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, 0);
|
||||||
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
|
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
|
||||||
|
|
||||||
|
|
@ -1058,7 +1060,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ResourceType type, bool withUIData,
|
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ResourceType type, bool withUIData,
|
||||||
params ushort[] gameObjects)
|
params ushort[] gameObjects)
|
||||||
{
|
{
|
||||||
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
|
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
|
||||||
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
||||||
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
|
||||||
|
|
||||||
|
|
@ -1077,7 +1079,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
|
|
||||||
public Ipc.ResourceTree?[] GetGameObjectResourceTrees(bool withUIData, params ushort[] gameObjects)
|
public Ipc.ResourceTree?[] GetGameObjectResourceTrees(bool withUIData, params ushort[] gameObjects)
|
||||||
{
|
{
|
||||||
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
|
var characters = gameObjects.Select(index => _objects[index]).OfType<Character>();
|
||||||
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUiData : 0);
|
||||||
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
|
var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
|
||||||
|
|
||||||
|
|
@ -1129,10 +1131,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
private unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
|
private unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
|
||||||
{
|
{
|
||||||
collection = _collectionManager.Active.Default;
|
collection = _collectionManager.Active.Default;
|
||||||
if (gameObjectIdx < 0 || gameObjectIdx >= _dalamud.Objects.Length)
|
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Length)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_dalamud.Objects.GetObjectAddress(gameObjectIdx);
|
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(gameObjectIdx);
|
||||||
var data = _collectionResolver.IdentifyCollection(ptr, false);
|
var data = _collectionResolver.IdentifyCollection(ptr, false);
|
||||||
if (data.Valid)
|
if (data.Valid)
|
||||||
collection = data.ModCollection;
|
collection = data.ModCollection;
|
||||||
|
|
@ -1143,11 +1145,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
private unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
|
private unsafe ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
|
||||||
{
|
{
|
||||||
if (gameObjectIdx < 0 || gameObjectIdx >= _dalamud.Objects.Length || !_actors.Valid)
|
if (gameObjectIdx < 0 || gameObjectIdx >= _objects.Length)
|
||||||
return ActorIdentifier.Invalid;
|
return ActorIdentifier.Invalid;
|
||||||
|
|
||||||
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_dalamud.Objects.GetObjectAddress(gameObjectIdx);
|
var ptr = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)_objects.GetObjectAddress(gameObjectIdx);
|
||||||
return _actors.AwaitedService.FromObject(ptr, out _, false, true, true);
|
return _actors.FromObject(ptr, out _, false, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve a path given by string for a specific collection.
|
// Resolve a path given by string for a specific collection.
|
||||||
|
|
@ -1171,7 +1173,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
if (Path.IsPathRooted(resolvedPath))
|
if (Path.IsPathRooted(resolvedPath))
|
||||||
return _lumina?.GetFileFromDisk<T>(resolvedPath);
|
return _lumina?.GetFileFromDisk<T>(resolvedPath);
|
||||||
|
|
||||||
return _dalamud.GameData.GetFile<T>(resolvedPath);
|
return _gameData.GetFile<T>(resolvedPath);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -1241,12 +1243,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi
|
||||||
// TODO: replace all usages with ActorIdentifier stuff when incrementing API
|
// TODO: replace all usages with ActorIdentifier stuff when incrementing API
|
||||||
private ActorIdentifier NameToIdentifier(string name, ushort worldId)
|
private ActorIdentifier NameToIdentifier(string name, ushort worldId)
|
||||||
{
|
{
|
||||||
if (!_actors.Valid)
|
|
||||||
return ActorIdentifier.Invalid;
|
|
||||||
|
|
||||||
// Verified to be valid name beforehand.
|
// Verified to be valid name beforehand.
|
||||||
var b = ByteString.FromStringUnsafe(name, false);
|
var b = ByteString.FromStringUnsafe(name, false);
|
||||||
return _actors.AwaitedService.CreatePlayer(b, worldId);
|
return _actors.CreatePlayer(b, worldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited)
|
private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, int _1, int _2, bool inherited)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ using CurrentSettings = ValueTuple<PenumbraApiEc, (bool, int, IDictionary<string
|
||||||
public class PenumbraIpcProviders : IDisposable
|
public class PenumbraIpcProviders : IDisposable
|
||||||
{
|
{
|
||||||
internal readonly IPenumbraApi Api;
|
internal readonly IPenumbraApi Api;
|
||||||
internal readonly IpcTester Tester;
|
|
||||||
|
|
||||||
// Plugin State
|
// Plugin State
|
||||||
internal readonly EventProvider Initialized;
|
internal readonly EventProvider Initialized;
|
||||||
|
|
@ -133,10 +132,9 @@ public class PenumbraIpcProviders : IDisposable
|
||||||
internal readonly FuncProvider<bool, ushort[], Ipc.ResourceTree?[]> GetGameObjectResourceTrees;
|
internal readonly FuncProvider<bool, ushort[], Ipc.ResourceTree?[]> GetGameObjectResourceTrees;
|
||||||
internal readonly FuncProvider<bool, IReadOnlyDictionary<ushort, Ipc.ResourceTree>> GetPlayerResourceTrees;
|
internal readonly FuncProvider<bool, IReadOnlyDictionary<ushort, Ipc.ResourceTree>> GetPlayerResourceTrees;
|
||||||
|
|
||||||
public PenumbraIpcProviders(DalamudServices dalamud, IPenumbraApi api, ModManager modManager, CollectionManager collections,
|
public PenumbraIpcProviders(DalamudPluginInterface pi, IPenumbraApi api, ModManager modManager, CollectionManager collections,
|
||||||
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService, Configuration config)
|
TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService, Configuration config)
|
||||||
{
|
{
|
||||||
var pi = dalamud.PluginInterface;
|
|
||||||
Api = api;
|
Api = api;
|
||||||
|
|
||||||
// Plugin State
|
// Plugin State
|
||||||
|
|
@ -260,15 +258,11 @@ public class PenumbraIpcProviders : IDisposable
|
||||||
GetGameObjectResourceTrees = Ipc.GetGameObjectResourceTrees.Provider(pi, Api.GetGameObjectResourceTrees);
|
GetGameObjectResourceTrees = Ipc.GetGameObjectResourceTrees.Provider(pi, Api.GetGameObjectResourceTrees);
|
||||||
GetPlayerResourceTrees = Ipc.GetPlayerResourceTrees.Provider(pi, Api.GetPlayerResourceTrees);
|
GetPlayerResourceTrees = Ipc.GetPlayerResourceTrees.Provider(pi, Api.GetPlayerResourceTrees);
|
||||||
|
|
||||||
Tester = new IpcTester(config, dalamud, this, modManager, collections, tempMods, tempCollections, saveService);
|
|
||||||
|
|
||||||
Initialized.Invoke();
|
Initialized.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Tester.Dispose();
|
|
||||||
|
|
||||||
// Plugin State
|
// Plugin State
|
||||||
Initialized.Dispose();
|
Initialized.Dispose();
|
||||||
ApiVersion.Dispose();
|
ApiVersion.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ public class CollectionCache : IDisposable
|
||||||
private readonly CollectionCacheManager _manager;
|
private readonly CollectionCacheManager _manager;
|
||||||
private readonly ModCollection _collection;
|
private readonly ModCollection _collection;
|
||||||
public readonly CollectionModData ModData = new();
|
public readonly CollectionModData ModData = new();
|
||||||
public readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = new();
|
private readonly SortedList<string, (SingleArray<IMod>, object?)> _changedItems = [];
|
||||||
public readonly ConcurrentDictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
|
public readonly ConcurrentDictionary<Utf8GamePath, ModPath> ResolvedFiles = new();
|
||||||
public readonly MetaCache Meta;
|
public readonly MetaCache Meta;
|
||||||
public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
|
public readonly Dictionary<IMod, SingleArray<ModConflicts>> ConflictDict = [];
|
||||||
|
|
||||||
public int Calculating = -1;
|
public int Calculating = -1;
|
||||||
|
|
||||||
|
|
@ -33,10 +33,10 @@ public class CollectionCache : IDisposable
|
||||||
=> _collection.AnonymizedName;
|
=> _collection.AnonymizedName;
|
||||||
|
|
||||||
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
|
public IEnumerable<SingleArray<ModConflicts>> AllConflicts
|
||||||
=> _conflicts.Values;
|
=> ConflictDict.Values;
|
||||||
|
|
||||||
public SingleArray<ModConflicts> Conflicts(IMod mod)
|
public SingleArray<ModConflicts> Conflicts(IMod mod)
|
||||||
=> _conflicts.TryGetValue(mod, out var c) ? c : new SingleArray<ModConflicts>();
|
=> ConflictDict.TryGetValue(mod, out SingleArray<ModConflicts> c) ? c : new SingleArray<ModConflicts>();
|
||||||
|
|
||||||
private int _changedItemsSaveCounter = -1;
|
private int _changedItemsSaveCounter = -1;
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ public class CollectionCache : IDisposable
|
||||||
$"Invalid mod state, removing {mod.Name} and associated manipulation {manipulation} returned current mod {mp.Name}.");
|
$"Invalid mod state, removing {mod.Name} and associated manipulation {manipulation} returned current mod {mp.Name}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_conflicts.Remove(mod);
|
ConflictDict.Remove(mod);
|
||||||
foreach (var conflict in conflicts)
|
foreach (var conflict in conflicts)
|
||||||
{
|
{
|
||||||
if (conflict.HasPriority)
|
if (conflict.HasPriority)
|
||||||
|
|
@ -206,9 +206,9 @@ public class CollectionCache : IDisposable
|
||||||
{
|
{
|
||||||
var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod);
|
var newConflicts = Conflicts(conflict.Mod2).Remove(c => c.Mod2 == mod);
|
||||||
if (newConflicts.Count > 0)
|
if (newConflicts.Count > 0)
|
||||||
_conflicts[conflict.Mod2] = newConflicts;
|
ConflictDict[conflict.Mod2] = newConflicts;
|
||||||
else
|
else
|
||||||
_conflicts.Remove(conflict.Mod2);
|
ConflictDict.Remove(conflict.Mod2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,9 +336,9 @@ public class CollectionCache : IDisposable
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
if (changedConflicts.Count == 0)
|
if (changedConflicts.Count == 0)
|
||||||
_conflicts.Remove(mod);
|
ConflictDict.Remove(mod);
|
||||||
else
|
else
|
||||||
_conflicts[mod] = changedConflicts;
|
ConflictDict[mod] = changedConflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new conflict between the added mod and the existing mod.
|
// Add a new conflict between the added mod and the existing mod.
|
||||||
|
|
@ -373,9 +373,9 @@ public class CollectionCache : IDisposable
|
||||||
{
|
{
|
||||||
// Add the same conflict list to both conflict directions.
|
// Add the same conflict list to both conflict directions.
|
||||||
var conflictList = new List<object> { data };
|
var conflictList = new List<object> { data };
|
||||||
_conflicts[addedMod] = addedConflicts.Append(new ModConflicts(existingMod, conflictList, existingPriority < addedPriority,
|
ConflictDict[addedMod] = addedConflicts.Append(new ModConflicts(existingMod, conflictList, existingPriority < addedPriority,
|
||||||
existingPriority != addedPriority));
|
existingPriority != addedPriority));
|
||||||
_conflicts[existingMod] = existingConflicts.Append(new ModConflicts(addedMod, conflictList,
|
ConflictDict[existingMod] = existingConflicts.Append(new ModConflicts(addedMod, conflictList,
|
||||||
existingPriority >= addedPriority,
|
existingPriority >= addedPriority,
|
||||||
existingPriority != addedPriority));
|
existingPriority != addedPriority));
|
||||||
}
|
}
|
||||||
|
|
@ -426,7 +426,7 @@ public class CollectionCache : IDisposable
|
||||||
_changedItems.Clear();
|
_changedItems.Clear();
|
||||||
// Skip IMCs because they would result in far too many false-positive items,
|
// Skip IMCs because they would result in far too many false-positive items,
|
||||||
// since they are per set instead of per item-slot/item/variant.
|
// since they are per set instead of per item-slot/item/variant.
|
||||||
var identifier = _manager.MetaFileManager.Identifier.AwaitedService;
|
var identifier = _manager.MetaFileManager.Identifier;
|
||||||
var items = new SortedList<string, object?>(512);
|
var items = new SortedList<string, object?>(512);
|
||||||
|
|
||||||
void AddItems(IMod mod)
|
void AddItems(IMod mod)
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ public class CollectionCacheManager : IDisposable
|
||||||
null);
|
null);
|
||||||
cache.ResolvedFiles.Clear();
|
cache.ResolvedFiles.Clear();
|
||||||
cache.Meta.Reset();
|
cache.Meta.Reset();
|
||||||
cache._conflicts.Clear();
|
cache.ConflictDict.Clear();
|
||||||
|
|
||||||
// Add all forced redirects.
|
// Add all forced redirects.
|
||||||
foreach (var tempMod in _tempMods.ModsForAllCollections
|
foreach (var tempMod in _tempMods.ModsForAllCollections
|
||||||
|
|
@ -372,7 +372,7 @@ public class CollectionCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
collection._cache!.ResolvedFiles.Clear();
|
collection._cache!.ResolvedFiles.Clear();
|
||||||
collection._cache!.Meta.Reset();
|
collection._cache!.Meta.Reset();
|
||||||
collection._cache!._conflicts.Clear();
|
collection._cache!.ConflictDict.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ public readonly struct EqdpCache : IDisposable
|
||||||
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
|
foreach (var file in _eqdpFiles.OfType<ExpandedEqdpFile>())
|
||||||
{
|
{
|
||||||
var relevant = CharacterUtility.RelevantIndices[file.Index.Value];
|
var relevant = CharacterUtility.RelevantIndices[file.Index.Value];
|
||||||
file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (SetId)m.SetId));
|
file.Reset(_eqdpManipulations.Where(m => m.FileIndex() == relevant).Select(m => (PrimaryId)m.SetId));
|
||||||
}
|
}
|
||||||
|
|
||||||
_eqdpManipulations.Clear();
|
_eqdpManipulations.Clear();
|
||||||
|
|
|
||||||
|
|
@ -74,12 +74,12 @@ public struct EstCache : IDisposable
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ushort GetEstEntry(MetaFileManager manager, EstManipulation.EstType type, GenderRace genderRace, SetId setId)
|
internal ushort GetEstEntry(MetaFileManager manager, EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var file = GetEstFile(type);
|
var file = GetEstFile(type);
|
||||||
return file != null
|
return file != null
|
||||||
? file[genderRace, setId.Id]
|
? file[genderRace, primaryId.Id]
|
||||||
: EstFile.GetDefault(manager, type, genderRace, setId);
|
: EstFile.GetDefault(manager, type, genderRace, primaryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
|
|
|
||||||
|
|
@ -187,17 +187,17 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
|
||||||
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
|
public bool GetImcFile(Utf8GamePath path, [NotNullWhen(true)] out Meta.Files.ImcFile? file)
|
||||||
=> _imcCache.GetImcFile(path, out file);
|
=> _imcCache.GetImcFile(path, out file);
|
||||||
|
|
||||||
internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, SetId setId)
|
internal EqdpEntry GetEqdpEntry(GenderRace race, bool accessory, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var eqdpFile = _eqdpCache.EqdpFile(race, accessory);
|
var eqdpFile = _eqdpCache.EqdpFile(race, accessory);
|
||||||
if (eqdpFile != null)
|
if (eqdpFile != null)
|
||||||
return setId.Id < eqdpFile.Count ? eqdpFile[setId] : default;
|
return primaryId.Id < eqdpFile.Count ? eqdpFile[primaryId] : default;
|
||||||
else
|
else
|
||||||
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, setId);
|
return Meta.Files.ExpandedEqdpFile.GetDefault(_manager, race, accessory, primaryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, SetId setId)
|
internal ushort GetEstEntry(EstManipulation.EstType type, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> _estCache.GetEstEntry(_manager, type, genderRace, setId);
|
=> _estCache.GetEstEntry(_manager, type, genderRace, primaryId);
|
||||||
|
|
||||||
/// <summary> Use this when CharacterUtility becomes ready. </summary>
|
/// <summary> Use this when CharacterUtility becomes ready. </summary>
|
||||||
private void ApplyStoredManipulations()
|
private void ApplyStoredManipulations()
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,9 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly ActiveCollectionData _data;
|
private readonly ActiveCollectionData _data;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
|
|
||||||
public ActiveCollections(Configuration config, CollectionStorage storage, ActorService actors, CommunicatorService communicator,
|
public ActiveCollections(Configuration config, CollectionStorage storage, ActorManager actors, CommunicatorService communicator,
|
||||||
SaveService saveService, ActiveCollectionData data)
|
SaveService saveService, ActiveCollectionData data)
|
||||||
{
|
{
|
||||||
_storage = storage;
|
_storage = storage;
|
||||||
|
|
@ -475,7 +475,7 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
{
|
{
|
||||||
case IdentifierType.Player when id.HomeWorld != ushort.MaxValue:
|
case IdentifierType.Player when id.HomeWorld != ushort.MaxValue:
|
||||||
{
|
{
|
||||||
var global = ByType(CollectionType.Individual, _actors.AwaitedService.CreatePlayer(id.PlayerName, ushort.MaxValue));
|
var global = ByType(CollectionType.Individual, _actors.CreatePlayer(id.PlayerName, ushort.MaxValue));
|
||||||
return global?.Index == checkAssignment.Index
|
return global?.Index == checkAssignment.Index
|
||||||
? "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it."
|
? "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
@ -484,12 +484,12 @@ public class ActiveCollections : ISavable, IDisposable
|
||||||
if (id.HomeWorld != ushort.MaxValue)
|
if (id.HomeWorld != ushort.MaxValue)
|
||||||
{
|
{
|
||||||
var global = ByType(CollectionType.Individual,
|
var global = ByType(CollectionType.Individual,
|
||||||
_actors.AwaitedService.CreateOwned(id.PlayerName, ushort.MaxValue, id.Kind, id.DataId));
|
_actors.CreateOwned(id.PlayerName, ushort.MaxValue, id.Kind, id.DataId));
|
||||||
if (global?.Index == checkAssignment.Index)
|
if (global?.Index == checkAssignment.Index)
|
||||||
return "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it.";
|
return "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it.";
|
||||||
}
|
}
|
||||||
|
|
||||||
var unowned = ByType(CollectionType.Individual, _actors.AwaitedService.CreateNpc(id.Kind, id.DataId));
|
var unowned = ByType(CollectionType.Individual, _actors.CreateNpc(id.Kind, id.DataId));
|
||||||
return unowned?.Index == checkAssignment.Index
|
return unowned?.Index == checkAssignment.Index
|
||||||
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
|
? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it."
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.Collections.Manager;
|
namespace Penumbra.Collections.Manager;
|
||||||
|
|
@ -36,7 +37,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && _config.UseOwnerNameForCharacterCollection)
|
if (identifier.Retainer is not ActorIdentifier.RetainerType.Mannequin && _config.UseOwnerNameForCharacterCollection)
|
||||||
return CheckWorlds(_actorService.AwaitedService.GetCurrentPlayer(), out collection);
|
return CheckWorlds(_actors.GetCurrentPlayer(), out collection);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +47,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Handle generic NPC
|
// Handle generic NPC
|
||||||
var npcIdentifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
var npcIdentifier = _actors.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
||||||
ushort.MaxValue,
|
ushort.MaxValue,
|
||||||
identifier.Kind, identifier.DataId);
|
identifier.Kind, identifier.DataId);
|
||||||
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
||||||
|
|
@ -56,7 +57,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
if (!_config.UseOwnerNameForCharacterCollection)
|
if (!_config.UseOwnerNameForCharacterCollection)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
identifier = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
||||||
identifier.HomeWorld.Id,
|
identifier.HomeWorld.Id,
|
||||||
ObjectKind.None, uint.MaxValue);
|
ObjectKind.None, uint.MaxValue);
|
||||||
return CheckWorlds(identifier, out collection);
|
return CheckWorlds(identifier, out collection);
|
||||||
|
|
@ -89,37 +90,37 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
if (identifier.Type != IdentifierType.Special)
|
if (identifier.Type != IdentifierType.Special)
|
||||||
return (identifier, SpecialResult.Invalid);
|
return (identifier, SpecialResult.Invalid);
|
||||||
|
|
||||||
if (_actorService.AwaitedService.ResolvePartyBannerPlayer(identifier.Special, out var id))
|
if (_actors.ResolvePartyBannerPlayer(identifier.Special, out var id))
|
||||||
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.PartyBanner) : (identifier, SpecialResult.Invalid);
|
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.PartyBanner) : (identifier, SpecialResult.Invalid);
|
||||||
|
|
||||||
if (_actorService.AwaitedService.ResolvePvPBannerPlayer(identifier.Special, out id))
|
if (_actors.ResolvePvPBannerPlayer(identifier.Special, out id))
|
||||||
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.PvPBanner) : (identifier, SpecialResult.Invalid);
|
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.PvPBanner) : (identifier, SpecialResult.Invalid);
|
||||||
|
|
||||||
if (_actorService.AwaitedService.ResolveMahjongPlayer(identifier.Special, out id))
|
if (_actors.ResolveMahjongPlayer(identifier.Special, out id))
|
||||||
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.Mahjong) : (identifier, SpecialResult.Invalid);
|
return _config.UseCharacterCollectionsInCards ? (id, SpecialResult.Mahjong) : (identifier, SpecialResult.Invalid);
|
||||||
|
|
||||||
switch (identifier.Special)
|
switch (identifier.Special)
|
||||||
{
|
{
|
||||||
case ScreenActor.CharacterScreen when _config.UseCharacterCollectionInMainWindow:
|
case ScreenActor.CharacterScreen when _config.UseCharacterCollectionInMainWindow:
|
||||||
return (_actorService.AwaitedService.GetCurrentPlayer(), SpecialResult.CharacterScreen);
|
return (_actors.GetCurrentPlayer(), SpecialResult.CharacterScreen);
|
||||||
case ScreenActor.FittingRoom when _config.UseCharacterCollectionInTryOn:
|
case ScreenActor.FittingRoom when _config.UseCharacterCollectionInTryOn:
|
||||||
return (_actorService.AwaitedService.GetCurrentPlayer(), SpecialResult.FittingRoom);
|
return (_actors.GetCurrentPlayer(), SpecialResult.FittingRoom);
|
||||||
case ScreenActor.DyePreview when _config.UseCharacterCollectionInTryOn:
|
case ScreenActor.DyePreview when _config.UseCharacterCollectionInTryOn:
|
||||||
return (_actorService.AwaitedService.GetCurrentPlayer(), SpecialResult.DyePreview);
|
return (_actors.GetCurrentPlayer(), SpecialResult.DyePreview);
|
||||||
case ScreenActor.Portrait when _config.UseCharacterCollectionsInCards:
|
case ScreenActor.Portrait when _config.UseCharacterCollectionsInCards:
|
||||||
return (_actorService.AwaitedService.GetCurrentPlayer(), SpecialResult.Portrait);
|
return (_actors.GetCurrentPlayer(), SpecialResult.Portrait);
|
||||||
case ScreenActor.ExamineScreen:
|
case ScreenActor.ExamineScreen:
|
||||||
{
|
{
|
||||||
identifier = _actorService.AwaitedService.GetInspectPlayer();
|
identifier = _actors.GetInspectPlayer();
|
||||||
if (identifier.IsValid)
|
if (identifier.IsValid)
|
||||||
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect);
|
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Inspect);
|
||||||
|
|
||||||
identifier = _actorService.AwaitedService.GetCardPlayer();
|
identifier = _actors.GetCardPlayer();
|
||||||
if (identifier.IsValid)
|
if (identifier.IsValid)
|
||||||
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card);
|
return (_config.UseCharacterCollectionInInspect ? identifier : ActorIdentifier.Invalid, SpecialResult.Card);
|
||||||
|
|
||||||
return _config.UseCharacterCollectionInTryOn
|
return _config.UseCharacterCollectionInTryOn
|
||||||
? (_actorService.AwaitedService.GetGlamourPlayer(), SpecialResult.Glamour)
|
? (_actors.GetGlamourPlayer(), SpecialResult.Glamour)
|
||||||
: (identifier, SpecialResult.Invalid);
|
: (identifier, SpecialResult.Invalid);
|
||||||
}
|
}
|
||||||
default: return (identifier, SpecialResult.Invalid);
|
default: return (identifier, SpecialResult.Invalid);
|
||||||
|
|
@ -127,10 +128,10 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetCollection(GameObject? gameObject, out ModCollection? collection)
|
public bool TryGetCollection(GameObject? gameObject, out ModCollection? collection)
|
||||||
=> TryGetCollection(_actorService.AwaitedService.FromObject(gameObject, true, false, false), out collection);
|
=> TryGetCollection(_actors.FromObject(gameObject, true, false, false), out collection);
|
||||||
|
|
||||||
public unsafe bool TryGetCollection(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection)
|
public unsafe bool TryGetCollection(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* gameObject, out ModCollection? collection)
|
||||||
=> TryGetCollection(_actorService.AwaitedService.FromObject(gameObject, out _, true, false, false), out collection);
|
=> TryGetCollection(_actors.FromObject(gameObject, out _, true, false, false), out collection);
|
||||||
|
|
||||||
private bool CheckWorlds(ActorIdentifier identifier, out ModCollection? collection)
|
private bool CheckWorlds(ActorIdentifier identifier, out ModCollection? collection)
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +144,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
if (_individuals.TryGetValue(identifier, out collection))
|
if (_individuals.TryGetValue(identifier, out collection))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
identifier = _actorService.AwaitedService.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
identifier = _actors.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, ushort.MaxValue,
|
||||||
identifier.Kind,
|
identifier.Kind,
|
||||||
identifier.DataId);
|
identifier.DataId);
|
||||||
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
|
if (identifier.IsValid && _individuals.TryGetValue(identifier, out collection))
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ using Dalamud.Interface.Internal.Notifications;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.DataContainers.Bases;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
|
|
@ -26,23 +28,20 @@ public partial class IndividualCollections
|
||||||
|
|
||||||
public bool ReadJObject(SaveService saver, ActiveCollections parent, JArray? obj, CollectionStorage storage)
|
public bool ReadJObject(SaveService saver, ActiveCollections parent, JArray? obj, CollectionStorage storage)
|
||||||
{
|
{
|
||||||
if (_actorService.Valid)
|
if (_actors.Awaiter.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
var ret = ReadJObjectInternal(obj, storage);
|
var ret = ReadJObjectInternal(obj, storage);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Func()
|
Penumbra.Log.Debug("[Collections] Delayed reading individual assignments until actor service is ready...");
|
||||||
|
_actors.Awaiter.ContinueWith(_ =>
|
||||||
{
|
{
|
||||||
if (ReadJObjectInternal(obj, storage))
|
if (ReadJObjectInternal(obj, storage))
|
||||||
saver.ImmediateSave(parent);
|
saver.ImmediateSave(parent);
|
||||||
IsLoaded = true;
|
IsLoaded = true;
|
||||||
Loaded.Invoke();
|
Loaded.Invoke();
|
||||||
_actorService.FinishedCreation -= Func;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Penumbra.Log.Debug("[Collections] Delayed reading individual assignments until actor service is ready...");
|
|
||||||
_actorService.FinishedCreation += Func;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +59,7 @@ public partial class IndividualCollections
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var identifier = _actorService.AwaitedService.FromJson(data as JObject);
|
var identifier = _actors.FromJson(data as JObject);
|
||||||
var group = GetGroup(identifier);
|
var group = GetGroup(identifier);
|
||||||
if (group.Length == 0 || group.Any(i => !i.IsValid))
|
if (group.Length == 0 || group.Any(i => !i.IsValid))
|
||||||
{
|
{
|
||||||
|
|
@ -101,10 +100,10 @@ public partial class IndividualCollections
|
||||||
|
|
||||||
internal void Migrate0To1(Dictionary<string, ModCollection> old)
|
internal void Migrate0To1(Dictionary<string, ModCollection> old)
|
||||||
{
|
{
|
||||||
static bool FindDataId(string name, IReadOnlyDictionary<uint, string> data, out uint dataId)
|
static bool FindDataId(string name, NameDictionary data, out NpcId dataId)
|
||||||
{
|
{
|
||||||
var kvp = data.FirstOrDefault(kvp => kvp.Value.Equals(name, StringComparison.OrdinalIgnoreCase),
|
var kvp = data.FirstOrDefault(kvp => kvp.Value.Equals(name, StringComparison.OrdinalIgnoreCase),
|
||||||
new KeyValuePair<uint, string>(uint.MaxValue, string.Empty));
|
new KeyValuePair<NpcId, string>(uint.MaxValue, string.Empty));
|
||||||
dataId = kvp.Key;
|
dataId = kvp.Key;
|
||||||
return kvp.Value.Length > 0;
|
return kvp.Value.Length > 0;
|
||||||
}
|
}
|
||||||
|
|
@ -114,22 +113,22 @@ public partial class IndividualCollections
|
||||||
var kind = ObjectKind.None;
|
var kind = ObjectKind.None;
|
||||||
var lowerName = name.ToLowerInvariant();
|
var lowerName = name.ToLowerInvariant();
|
||||||
// Prefer matching NPC names, fewer false positives than preferring players.
|
// Prefer matching NPC names, fewer false positives than preferring players.
|
||||||
if (FindDataId(lowerName, _actorService.AwaitedService.Data.Companions, out var dataId))
|
if (FindDataId(lowerName, _actors.Data.Companions, out var dataId))
|
||||||
kind = ObjectKind.Companion;
|
kind = ObjectKind.Companion;
|
||||||
else if (FindDataId(lowerName, _actorService.AwaitedService.Data.Mounts, out dataId))
|
else if (FindDataId(lowerName, _actors.Data.Mounts, out dataId))
|
||||||
kind = ObjectKind.MountType;
|
kind = ObjectKind.MountType;
|
||||||
else if (FindDataId(lowerName, _actorService.AwaitedService.Data.BNpcs, out dataId))
|
else if (FindDataId(lowerName, _actors.Data.BNpcs, out dataId))
|
||||||
kind = ObjectKind.BattleNpc;
|
kind = ObjectKind.BattleNpc;
|
||||||
else if (FindDataId(lowerName, _actorService.AwaitedService.Data.ENpcs, out dataId))
|
else if (FindDataId(lowerName, _actors.Data.ENpcs, out dataId))
|
||||||
kind = ObjectKind.EventNpc;
|
kind = ObjectKind.EventNpc;
|
||||||
|
|
||||||
var identifier = _actorService.AwaitedService.CreateNpc(kind, dataId);
|
var identifier = _actors.CreateNpc(kind, dataId);
|
||||||
if (identifier.IsValid)
|
if (identifier.IsValid)
|
||||||
{
|
{
|
||||||
// If the name corresponds to a valid npc, add it as a group. If this fails, notify users.
|
// If the name corresponds to a valid npc, add it as a group. If this fails, notify users.
|
||||||
var group = GetGroup(identifier);
|
var group = GetGroup(identifier);
|
||||||
var ids = string.Join(", ", group.Select(i => i.DataId.ToString()));
|
var ids = string.Join(", ", group.Select(i => i.DataId.ToString()));
|
||||||
if (Add($"{_actorService.AwaitedService.Data.ToName(kind, dataId)} ({kind.ToName()})", group, collection))
|
if (Add($"{_actors.Data.ToName(kind, dataId)} ({kind.ToName()})", group, collection))
|
||||||
Penumbra.Log.Information($"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}].");
|
Penumbra.Log.Information($"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}].");
|
||||||
else
|
else
|
||||||
Penumbra.Messager.NotificationMessage(
|
Penumbra.Messager.NotificationMessage(
|
||||||
|
|
@ -137,15 +136,12 @@ public partial class IndividualCollections
|
||||||
NotificationType.Error);
|
NotificationType.Error);
|
||||||
}
|
}
|
||||||
// If it is not a valid NPC name, check if it can be a player name.
|
// If it is not a valid NPC name, check if it can be a player name.
|
||||||
else if (ActorManager.VerifyPlayerName(name))
|
else if (ActorIdentifierFactory.VerifyPlayerName(name))
|
||||||
{
|
{
|
||||||
identifier = _actorService.AwaitedService.CreatePlayer(ByteString.FromStringUnsafe(name, false), ushort.MaxValue);
|
identifier = _actors.CreatePlayer(ByteString.FromStringUnsafe(name, false), ushort.MaxValue);
|
||||||
var shortName = string.Join(" ", name.Split().Select(n => $"{n[0]}."));
|
var shortName = string.Join(" ", name.Split().Select(n => $"{n[0]}."));
|
||||||
// Try to migrate the player name without logging full names.
|
// Try to migrate the player name without logging full names.
|
||||||
if (Add($"{name} ({_actorService.AwaitedService.Data.ToWorldName(identifier.HomeWorld)})", new[]
|
if (Add($"{name} ({_actors.Data.ToWorldName(identifier.HomeWorld)})", [identifier], collection))
|
||||||
{
|
|
||||||
identifier,
|
|
||||||
}, collection))
|
|
||||||
Penumbra.Log.Information($"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier.");
|
Penumbra.Log.Information($"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier.");
|
||||||
else
|
else
|
||||||
Penumbra.Messager.NotificationMessage(
|
Penumbra.Messager.NotificationMessage(
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.Services;
|
using Penumbra.GameData.DataContainers.Bases;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.Collections.Manager;
|
namespace Penumbra.Collections.Manager;
|
||||||
|
|
@ -11,9 +13,9 @@ public sealed partial class IndividualCollections
|
||||||
public record struct IndividualAssignment(string DisplayName, IReadOnlyList<ActorIdentifier> Identifiers, ModCollection Collection);
|
public record struct IndividualAssignment(string DisplayName, IReadOnlyList<ActorIdentifier> Identifiers, ModCollection Collection);
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly ActorService _actorService;
|
private readonly ActorManager _actors;
|
||||||
private readonly Dictionary<ActorIdentifier, ModCollection> _individuals = new();
|
private readonly Dictionary<ActorIdentifier, ModCollection> _individuals = [];
|
||||||
private readonly List<IndividualAssignment> _assignments = new();
|
private readonly List<IndividualAssignment> _assignments = [];
|
||||||
|
|
||||||
public event Action Loaded;
|
public event Action Loaded;
|
||||||
public bool IsLoaded { get; private set; }
|
public bool IsLoaded { get; private set; }
|
||||||
|
|
@ -21,10 +23,10 @@ public sealed partial class IndividualCollections
|
||||||
public IReadOnlyList<IndividualAssignment> Assignments
|
public IReadOnlyList<IndividualAssignment> Assignments
|
||||||
=> _assignments;
|
=> _assignments;
|
||||||
|
|
||||||
public IndividualCollections(ActorService actorService, Configuration config, bool temporary)
|
public IndividualCollections(ActorManager actors, Configuration config, bool temporary)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_actorService = actorService;
|
_actors = actors;
|
||||||
IsLoaded = temporary;
|
IsLoaded = temporary;
|
||||||
Loaded += () => Penumbra.Log.Information($"{_assignments.Count} Individual Assignments loaded after delay.");
|
Loaded += () => Penumbra.Log.Information($"{_assignments.Count} Individual Assignments loaded after delay.");
|
||||||
}
|
}
|
||||||
|
|
@ -69,44 +71,34 @@ public sealed partial class IndividualCollections
|
||||||
return set ? AddResult.AlreadySet : AddResult.Valid;
|
return set ? AddResult.AlreadySet : AddResult.Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddResult CanAdd(IdentifierType type, string name, ushort homeWorld, ObjectKind kind, IEnumerable<uint> dataIds,
|
public AddResult CanAdd(IdentifierType type, string name, WorldId homeWorld, ObjectKind kind, IEnumerable<NpcId> dataIds,
|
||||||
out ActorIdentifier[] identifiers)
|
out ActorIdentifier[] identifiers)
|
||||||
{
|
{
|
||||||
identifiers = Array.Empty<ActorIdentifier>();
|
identifiers = [];
|
||||||
|
|
||||||
var manager = _actorService.AwaitedService;
|
|
||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case IdentifierType.Player:
|
case IdentifierType.Player:
|
||||||
if (!ByteString.FromString(name, out var playerName))
|
if (!ByteString.FromString(name, out var playerName))
|
||||||
return AddResult.Invalid;
|
return AddResult.Invalid;
|
||||||
|
|
||||||
identifiers = new[]
|
identifiers = [_actors.CreatePlayer(playerName, homeWorld)];
|
||||||
{
|
|
||||||
manager.CreatePlayer(playerName, homeWorld),
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case IdentifierType.Retainer:
|
case IdentifierType.Retainer:
|
||||||
if (!ByteString.FromString(name, out var retainerName))
|
if (!ByteString.FromString(name, out var retainerName))
|
||||||
return AddResult.Invalid;
|
return AddResult.Invalid;
|
||||||
|
|
||||||
identifiers = new[]
|
identifiers = [_actors.CreateRetainer(retainerName, ActorIdentifier.RetainerType.Both)];
|
||||||
{
|
|
||||||
manager.CreateRetainer(retainerName, ActorIdentifier.RetainerType.Both),
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
case IdentifierType.Owned:
|
case IdentifierType.Owned:
|
||||||
if (!ByteString.FromString(name, out var ownerName))
|
if (!ByteString.FromString(name, out var ownerName))
|
||||||
return AddResult.Invalid;
|
return AddResult.Invalid;
|
||||||
|
|
||||||
identifiers = dataIds.Select(id => manager.CreateOwned(ownerName, homeWorld, kind, id)).ToArray();
|
identifiers = dataIds.Select(id => _actors.CreateOwned(ownerName, homeWorld, kind, id)).ToArray();
|
||||||
break;
|
break;
|
||||||
case IdentifierType.Npc:
|
case IdentifierType.Npc:
|
||||||
identifiers = dataIds
|
identifiers = dataIds
|
||||||
.Select(id => manager.CreateIndividual(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id)).ToArray();
|
.Select(id => _actors.CreateIndividual(IdentifierType.Npc, ByteString.Empty, ushort.MaxValue, kind, id)).ToArray();
|
||||||
break;
|
|
||||||
default:
|
|
||||||
identifiers = Array.Empty<ActorIdentifier>();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,12 +108,22 @@ public sealed partial class IndividualCollections
|
||||||
public ActorIdentifier[] GetGroup(ActorIdentifier identifier)
|
public ActorIdentifier[] GetGroup(ActorIdentifier identifier)
|
||||||
{
|
{
|
||||||
if (!identifier.IsValid)
|
if (!identifier.IsValid)
|
||||||
return Array.Empty<ActorIdentifier>();
|
return [];
|
||||||
|
|
||||||
|
return identifier.Type switch
|
||||||
|
{
|
||||||
|
IdentifierType.Player => [identifier.CreatePermanent()],
|
||||||
|
IdentifierType.Special => [identifier],
|
||||||
|
IdentifierType.Retainer => [identifier.CreatePermanent()],
|
||||||
|
IdentifierType.Owned => CreateNpcs(_actors, identifier.CreatePermanent()),
|
||||||
|
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||||
|
_ => [],
|
||||||
|
};
|
||||||
|
|
||||||
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
||||||
{
|
{
|
||||||
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
|
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
|
||||||
var table = identifier.Kind switch
|
NameDictionary table = identifier.Kind switch
|
||||||
{
|
{
|
||||||
ObjectKind.BattleNpc => manager.Data.BNpcs,
|
ObjectKind.BattleNpc => manager.Data.BNpcs,
|
||||||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||||
|
|
@ -134,25 +136,6 @@ public sealed partial class IndividualCollections
|
||||||
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||||
identifier.Kind, kvp.Key)).ToArray();
|
identifier.Kind, kvp.Key)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return identifier.Type switch
|
|
||||||
{
|
|
||||||
IdentifierType.Player => new[]
|
|
||||||
{
|
|
||||||
identifier.CreatePermanent(),
|
|
||||||
},
|
|
||||||
IdentifierType.Special => new[]
|
|
||||||
{
|
|
||||||
identifier,
|
|
||||||
},
|
|
||||||
IdentifierType.Retainer => new[]
|
|
||||||
{
|
|
||||||
identifier.CreatePermanent(),
|
|
||||||
},
|
|
||||||
IdentifierType.Owned => CreateNpcs(_actorService.AwaitedService, identifier.CreatePermanent()),
|
|
||||||
IdentifierType.Npc => CreateNpcs(_actorService.AwaitedService, identifier),
|
|
||||||
_ => Array.Empty<ActorIdentifier>(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool Add(ActorIdentifier[] identifiers, ModCollection collection)
|
internal bool Add(ActorIdentifier[] identifiers, ModCollection collection)
|
||||||
|
|
@ -241,12 +224,12 @@ public sealed partial class IndividualCollections
|
||||||
{
|
{
|
||||||
return identifier.Type switch
|
return identifier.Type switch
|
||||||
{
|
{
|
||||||
IdentifierType.Player => $"{identifier.PlayerName} ({_actorService.AwaitedService.Data.ToWorldName(identifier.HomeWorld)})",
|
IdentifierType.Player => $"{identifier.PlayerName} ({_actors.Data.ToWorldName(identifier.HomeWorld)})",
|
||||||
IdentifierType.Retainer => $"{identifier.PlayerName} (Retainer)",
|
IdentifierType.Retainer => $"{identifier.PlayerName} (Retainer)",
|
||||||
IdentifierType.Owned =>
|
IdentifierType.Owned =>
|
||||||
$"{identifier.PlayerName} ({_actorService.AwaitedService.Data.ToWorldName(identifier.HomeWorld)})'s {_actorService.AwaitedService.Data.ToName(identifier.Kind, identifier.DataId)}",
|
$"{identifier.PlayerName} ({_actors.Data.ToWorldName(identifier.HomeWorld)})'s {_actors.Data.ToName(identifier.Kind, identifier.DataId)}",
|
||||||
IdentifierType.Npc =>
|
IdentifierType.Npc =>
|
||||||
$"{_actorService.AwaitedService.Data.ToName(identifier.Kind, identifier.DataId)} ({identifier.Kind.ToName()})",
|
$"{_actors.Data.ToName(identifier.Kind, identifier.DataId)} ({identifier.Kind.ToName()})",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ public class TempCollectionManager : IDisposable
|
||||||
|
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly CollectionStorage _storage;
|
private readonly CollectionStorage _storage;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly Dictionary<string, ModCollection> _customCollections = new();
|
private readonly Dictionary<string, ModCollection> _customCollections = new();
|
||||||
|
|
||||||
public TempCollectionManager(Configuration config, CommunicatorService communicator, ActorService actors, CollectionStorage storage)
|
public TempCollectionManager(Configuration config, CommunicatorService communicator, ActorManager actors, CollectionStorage storage)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
|
|
@ -111,7 +111,7 @@ public class TempCollectionManager : IDisposable
|
||||||
if (!ByteString.FromString(characterName, out var byteString, false))
|
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var identifier = _actors.AwaitedService.CreatePlayer(byteString, worldId);
|
var identifier = _actors.CreatePlayer(byteString, worldId);
|
||||||
if (!identifier.IsValid)
|
if (!identifier.IsValid)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ public class TempCollectionManager : IDisposable
|
||||||
if (!ByteString.FromString(characterName, out var byteString, false))
|
if (!ByteString.FromString(characterName, out var byteString, false))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var identifier = _actors.AwaitedService.CreatePlayer(byteString, worldId);
|
var identifier = _actors.CreatePlayer(byteString, worldId);
|
||||||
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name);
|
return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ public class CommandHandler : IDisposable
|
||||||
|
|
||||||
public CommandHandler(IFramework framework, ICommandManager commandManager, IChatGui chat, RedrawService redrawService,
|
public CommandHandler(IFramework framework, ICommandManager commandManager, IChatGui chat, RedrawService redrawService,
|
||||||
Configuration config,
|
Configuration config,
|
||||||
ConfigWindow configWindow, ModManager modManager, CollectionManager collectionManager, ActorService actors, Penumbra penumbra,
|
ConfigWindow configWindow, ModManager modManager, CollectionManager collectionManager, ActorManager actors, Penumbra penumbra,
|
||||||
CollectionEditor collectionEditor)
|
CollectionEditor collectionEditor)
|
||||||
{
|
{
|
||||||
_commandManager = commandManager;
|
_commandManager = commandManager;
|
||||||
|
|
@ -41,7 +41,7 @@ public class CommandHandler : IDisposable
|
||||||
_configWindow = configWindow;
|
_configWindow = configWindow;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_collectionManager = collectionManager;
|
_collectionManager = collectionManager;
|
||||||
_actors = actors.AwaitedService;
|
_actors = actors;
|
||||||
_chat = chat;
|
_chat = chat;
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_collectionEditor = collectionEditor;
|
_collectionEditor = collectionEditor;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
|
||||||
namespace Penumbra.Import.Structs;
|
namespace Penumbra.Import.Structs;
|
||||||
|
|
||||||
|
|
@ -47,7 +48,7 @@ public partial struct MetaFileInfo
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
public MetaFileInfo(IGamePathParser parser, string fileName)
|
public MetaFileInfo(GamePathParser parser, string fileName)
|
||||||
{
|
{
|
||||||
// Set the primary type from the gamePath start.
|
// Set the primary type from the gamePath start.
|
||||||
PrimaryType = parser.PathToObjectType(fileName);
|
PrimaryType = parser.PathToObjectType(fileName);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -28,7 +29,7 @@ public partial class TexToolsMeta
|
||||||
|
|
||||||
private readonly MetaFileManager _metaFileManager;
|
private readonly MetaFileManager _metaFileManager;
|
||||||
|
|
||||||
public TexToolsMeta(MetaFileManager metaFileManager, IGamePathParser parser, byte[] data, bool keepDefault)
|
public TexToolsMeta(MetaFileManager metaFileManager, GamePathParser parser, byte[] data, bool keepDefault)
|
||||||
{
|
{
|
||||||
_metaFileManager = metaFileManager;
|
_metaFileManager = metaFileManager;
|
||||||
_keepDefault = keepDefault;
|
_keepDefault = keepDefault;
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ public static class TexFileParser
|
||||||
if (offset == 0)
|
if (offset == 0)
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
var requiredSize = width * height * bits / 8;
|
var Size = width * height * bits / 8;
|
||||||
if (offset + requiredSize > data.Length)
|
if (offset + Size > data.Length)
|
||||||
return i;
|
return i;
|
||||||
|
|
||||||
var diff = offset - lastOffset;
|
var diff = offset - lastOffset;
|
||||||
|
|
@ -55,7 +55,7 @@ public static class TexFileParser
|
||||||
width = Math.Max(width / 2, minSize);
|
width = Math.Max(width / 2, minSize);
|
||||||
height = Math.Max(height / 2, minSize);
|
height = Math.Max(height / 2, minSize);
|
||||||
lastOffset = offset;
|
lastOffset = offset;
|
||||||
lastSize = requiredSize;
|
lastSize = Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 13;
|
return 13;
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_unkParasolAnimationHook.Enable();
|
_unkParasolAnimationHook.Enable();
|
||||||
_dismountHook.Enable();
|
_dismountHook.Enable();
|
||||||
_apricotListenerSoundPlayHook.Enable();
|
_apricotListenerSoundPlayHook.Enable();
|
||||||
|
_footStepHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HandleFiles(ResourceType type, Utf8GamePath _, out ResolveData resolveData)
|
public bool HandleFiles(ResourceType type, Utf8GamePath _, out ResolveData resolveData)
|
||||||
|
|
@ -113,6 +114,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_unkParasolAnimationHook.Dispose();
|
_unkParasolAnimationHook.Dispose();
|
||||||
_dismountHook.Dispose();
|
_dismountHook.Dispose();
|
||||||
_apricotListenerSoundPlayHook.Dispose();
|
_apricotListenerSoundPlayHook.Dispose();
|
||||||
|
_footStepHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
/// <summary> Characters load some of their voice lines or whatever with this function. </summary>
|
||||||
|
|
@ -324,7 +326,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
|
|
||||||
private delegate void UnkMountAnimationDelegate(DrawObject* drawObject, uint unk1, byte unk2, uint unk3);
|
private delegate void UnkMountAnimationDelegate(DrawObject* drawObject, uint unk1, byte unk2, uint unk3);
|
||||||
|
|
||||||
[Signature("48 89 5C 24 ?? 48 89 6C 24 ?? 89 54 24", DetourName = nameof(UnkMountAnimationDetour))]
|
[Signature(Sigs.UnkMountAnimation, DetourName = nameof(UnkMountAnimationDetour))]
|
||||||
private readonly Hook<UnkMountAnimationDelegate> _unkMountAnimationHook = null!;
|
private readonly Hook<UnkMountAnimationDelegate> _unkMountAnimationHook = null!;
|
||||||
|
|
||||||
private void UnkMountAnimationDetour(DrawObject* drawObject, uint unk1, byte unk2, uint unk3)
|
private void UnkMountAnimationDetour(DrawObject* drawObject, uint unk1, byte unk2, uint unk3)
|
||||||
|
|
@ -335,10 +337,9 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_animationLoadData.Value = last;
|
_animationLoadData.Value = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private delegate void UnkParasolAnimationDelegate(DrawObject* drawObject, int unk1);
|
private delegate void UnkParasolAnimationDelegate(DrawObject* drawObject, int unk1);
|
||||||
|
|
||||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 89 54 24 ?? 57 48 83 EC ?? 48 8B F9", DetourName = nameof(UnkParasolAnimationDetour))]
|
[Signature(Sigs.UnkParasolAnimation, DetourName = nameof(UnkParasolAnimationDetour))]
|
||||||
private readonly Hook<UnkParasolAnimationDelegate> _unkParasolAnimationHook = null!;
|
private readonly Hook<UnkParasolAnimationDelegate> _unkParasolAnimationHook = null!;
|
||||||
|
|
||||||
private void UnkParasolAnimationDetour(DrawObject* drawObject, int unk1)
|
private void UnkParasolAnimationDetour(DrawObject* drawObject, int unk1)
|
||||||
|
|
@ -349,7 +350,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_animationLoadData.Value = last;
|
_animationLoadData.Value = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? F6 43 ?? ?? 74 ?? 48 8B CB", DetourName = nameof(DismountDetour))]
|
[Signature(Sigs.Dismount, DetourName = nameof(DismountDetour))]
|
||||||
private readonly Hook<DismountDelegate> _dismountHook = null!;
|
private readonly Hook<DismountDelegate> _dismountHook = null!;
|
||||||
|
|
||||||
private delegate void DismountDelegate(nint a1, nint a2);
|
private delegate void DismountDelegate(nint a1, nint a2);
|
||||||
|
|
@ -375,7 +376,7 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_animationLoadData.Value = last;
|
_animationLoadData.Value = last;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Signature("48 89 6C 24 ?? 41 54 41 56 41 57 48 81 EC", DetourName = nameof(ApricotListenerSoundPlayDetour))]
|
[Signature(Sigs.ApricotListenerSoundPlay, DetourName = nameof(ApricotListenerSoundPlayDetour))]
|
||||||
private readonly Hook<ApricotListenerSoundPlayDelegate> _apricotListenerSoundPlayHook = null!;
|
private readonly Hook<ApricotListenerSoundPlayDelegate> _apricotListenerSoundPlayHook = null!;
|
||||||
|
|
||||||
private delegate nint ApricotListenerSoundPlayDelegate(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
|
private delegate nint ApricotListenerSoundPlayDelegate(nint a1, nint a2, nint a3, nint a4, nint a5, nint a6);
|
||||||
|
|
@ -406,4 +407,17 @@ public unsafe class AnimationHookService : IDisposable
|
||||||
_animationLoadData.Value = last;
|
_animationLoadData.Value = last;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private delegate void FootStepDelegate(GameObject* gameObject, int id, int unk);
|
||||||
|
|
||||||
|
[Signature(Sigs.FootStepSound, DetourName = nameof(FootStepDetour))]
|
||||||
|
private readonly Hook<FootStepDelegate> _footStepHook = null!;
|
||||||
|
|
||||||
|
private void FootStepDetour(GameObject* gameObject, int id, int unk)
|
||||||
|
{
|
||||||
|
var last = _animationLoadData.Value;
|
||||||
|
_animationLoadData.Value = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||||
|
_footStepHook.Original(gameObject, id, unk);
|
||||||
|
_animationLoadData.Value = last;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
@ -21,7 +21,7 @@ public unsafe class CollectionResolver
|
||||||
|
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly IGameGui _gameGui;
|
private readonly IGameGui _gameGui;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly CutsceneService _cutscenes;
|
private readonly CutsceneService _cutscenes;
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
|
@ -30,7 +30,7 @@ public unsafe class CollectionResolver
|
||||||
private readonly DrawObjectState _drawObjectState;
|
private readonly DrawObjectState _drawObjectState;
|
||||||
|
|
||||||
public CollectionResolver(PerformanceTracker performance, IdentifiedCollectionCache cache, IClientState clientState, IGameGui gameGui,
|
public CollectionResolver(PerformanceTracker performance, IdentifiedCollectionCache cache, IClientState clientState, IGameGui gameGui,
|
||||||
ActorService actors, CutsceneService cutscenes, Configuration config, CollectionManager collectionManager,
|
ActorManager actors, CutsceneService cutscenes, Configuration config, CollectionManager collectionManager,
|
||||||
TempCollectionManager tempCollections, DrawObjectState drawObjectState, HumanModelList humanModels)
|
TempCollectionManager tempCollections, DrawObjectState drawObjectState, HumanModelList humanModels)
|
||||||
{
|
{
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
|
|
@ -58,7 +58,7 @@ public unsafe class CollectionResolver
|
||||||
return _collectionManager.Active.ByType(CollectionType.Yourself)
|
return _collectionManager.Active.ByType(CollectionType.Yourself)
|
||||||
?? _collectionManager.Active.Default;
|
?? _collectionManager.Active.Default;
|
||||||
|
|
||||||
var player = _actors.AwaitedService.GetCurrentPlayer();
|
var player = _actors.GetCurrentPlayer();
|
||||||
var _ = false;
|
var _ = false;
|
||||||
return CollectionByIdentifier(player)
|
return CollectionByIdentifier(player)
|
||||||
?? CheckYourself(player, gameObject)
|
?? CheckYourself(player, gameObject)
|
||||||
|
|
@ -147,7 +147,7 @@ public unsafe class CollectionResolver
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var player = _actors.AwaitedService.GetCurrentPlayer();
|
var player = _actors.GetCurrentPlayer();
|
||||||
var notYetReady = false;
|
var notYetReady = false;
|
||||||
var collection = (player.IsValid ? CollectionByIdentifier(player) : null)
|
var collection = (player.IsValid ? CollectionByIdentifier(player) : null)
|
||||||
?? _collectionManager.Active.ByType(CollectionType.Yourself)
|
?? _collectionManager.Active.ByType(CollectionType.Yourself)
|
||||||
|
|
@ -163,7 +163,7 @@ public unsafe class CollectionResolver
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ResolveData DefaultState(GameObject* gameObject)
|
private ResolveData DefaultState(GameObject* gameObject)
|
||||||
{
|
{
|
||||||
var identifier = _actors.AwaitedService.FromObject(gameObject, out var owner, true, false, false);
|
var identifier = _actors.FromObject(gameObject, out var owner, true, false, false);
|
||||||
if (identifier.Type is IdentifierType.Special)
|
if (identifier.Type is IdentifierType.Special)
|
||||||
{
|
{
|
||||||
(identifier, var type) = _collectionManager.Active.Individuals.ConvertSpecialIdentifier(identifier);
|
(identifier, var type) = _collectionManager.Active.Individuals.ConvertSpecialIdentifier(identifier);
|
||||||
|
|
@ -193,7 +193,7 @@ public unsafe class CollectionResolver
|
||||||
{
|
{
|
||||||
if (actor->ObjectIndex == 0
|
if (actor->ObjectIndex == 0
|
||||||
|| _cutscenes.GetParentIndex(actor->ObjectIndex) == 0
|
|| _cutscenes.GetParentIndex(actor->ObjectIndex) == 0
|
||||||
|| identifier.Equals(_actors.AwaitedService.GetCurrentPlayer()))
|
|| identifier.Equals(_actors.GetCurrentPlayer()))
|
||||||
return _collectionManager.Active.ByType(CollectionType.Yourself);
|
return _collectionManager.Active.ByType(CollectionType.Yourself);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -242,7 +242,7 @@ public unsafe class CollectionResolver
|
||||||
if (identifier.Type != IdentifierType.Owned || !_config.UseOwnerNameForCharacterCollection || owner == null)
|
if (identifier.Type != IdentifierType.Owned || !_config.UseOwnerNameForCharacterCollection || owner == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var id = _actors.AwaitedService.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
|
var id = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName, identifier.HomeWorld.Id,
|
||||||
ObjectKind.None,
|
ObjectKind.None,
|
||||||
uint.MaxValue);
|
uint.MaxValue);
|
||||||
return CheckYourself(id, owner)
|
return CheckYourself(id, owner)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
|
|
||||||
namespace Penumbra.Interop.PathResolving;
|
namespace Penumbra.Interop.PathResolving;
|
||||||
|
|
@ -45,6 +45,9 @@ public class CutsceneService : IDisposable
|
||||||
|
|
||||||
/// <summary> Return the currently set index of a parent or -1 if none is set or the index is invalid. </summary>
|
/// <summary> Return the currently set index of a parent or -1 if none is set or the index is invalid. </summary>
|
||||||
public int GetParentIndex(int idx)
|
public int GetParentIndex(int idx)
|
||||||
|
=> GetParentIndex((ushort)idx);
|
||||||
|
|
||||||
|
public short GetParentIndex(ushort idx)
|
||||||
{
|
{
|
||||||
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
if (idx is >= CutsceneStartIdx and < CutsceneEndIdx)
|
||||||
return _copiedCharacters[idx - CutsceneStartIdx];
|
return _copiedCharacters[idx - CutsceneStartIdx];
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ namespace Penumbra.Interop.ResourceLoading;
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class CreateFileWHook : IDisposable
|
public unsafe class CreateFileWHook : IDisposable
|
||||||
{
|
{
|
||||||
public const int RequiredSize = 28;
|
public const int Size = 28;
|
||||||
|
|
||||||
public CreateFileWHook(IGameInteropProvider interop)
|
public CreateFileWHook(IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
|
|
@ -57,8 +57,8 @@ public unsafe class CreateFileWHook : IDisposable
|
||||||
ptr[22] = (byte)(l >> 16);
|
ptr[22] = (byte)(l >> 16);
|
||||||
ptr[24] = (byte)(l >> 24);
|
ptr[24] = (byte)(l >> 24);
|
||||||
|
|
||||||
ptr[RequiredSize - 2] = 0;
|
ptr[Size - 2] = 0;
|
||||||
ptr[RequiredSize - 1] = 0;
|
ptr[Size - 1] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
|
|
@ -37,7 +36,7 @@ internal partial record ResolveContext
|
||||||
private unsafe GenderRace ResolveModelRaceCode()
|
private unsafe GenderRace ResolveModelRaceCode()
|
||||||
=> ResolveEqdpRaceCode(Slot, Equipment.Set);
|
=> ResolveEqdpRaceCode(Slot, Equipment.Set);
|
||||||
|
|
||||||
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, SetId setId)
|
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var slotIndex = slot.ToIndex();
|
var slotIndex = slot.ToIndex();
|
||||||
if (slotIndex >= 10 || ModelType != ModelType.Human)
|
if (slotIndex >= 10 || ModelType != ModelType.Human)
|
||||||
|
|
@ -55,7 +54,7 @@ internal partial record ResolveContext
|
||||||
if (metaCache == null)
|
if (metaCache == null)
|
||||||
return GenderRace.MidlanderMale;
|
return GenderRace.MidlanderMale;
|
||||||
|
|
||||||
var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, setId);
|
var entry = metaCache.GetEqdpEntry(characterRaceCode, accessory, primaryId);
|
||||||
if (entry.ToBits(slot).Item2)
|
if (entry.ToBits(slot).Item2)
|
||||||
return characterRaceCode;
|
return characterRaceCode;
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ internal partial record ResolveContext
|
||||||
if (fallbackRaceCode == GenderRace.MidlanderMale)
|
if (fallbackRaceCode == GenderRace.MidlanderMale)
|
||||||
return GenderRace.MidlanderMale;
|
return GenderRace.MidlanderMale;
|
||||||
|
|
||||||
entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, setId);
|
entry = metaCache.GetEqdpEntry(fallbackRaceCode, accessory, primaryId);
|
||||||
if (entry.ToBits(slot).Item2)
|
if (entry.ToBits(slot).Item2)
|
||||||
return fallbackRaceCode;
|
return fallbackRaceCode;
|
||||||
|
|
||||||
|
|
@ -229,7 +228,7 @@ internal partial record ResolveContext
|
||||||
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanSkeletonData(uint partialSkeletonIndex)
|
private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanSkeletonData(uint partialSkeletonIndex)
|
||||||
{
|
{
|
||||||
var human = (Human*)CharacterBase.Value;
|
var human = (Human*)CharacterBase.Value;
|
||||||
var characterRaceCode = (GenderRace)human->RaceSexId;
|
var characterRaceCode = (GenderRace)human->RaceSexId;
|
||||||
|
|
@ -239,8 +238,8 @@ internal partial record ResolveContext
|
||||||
return (characterRaceCode, "base", 1);
|
return (characterRaceCode, "base", 1);
|
||||||
case 1:
|
case 1:
|
||||||
var faceId = human->FaceId;
|
var faceId = human->FaceId;
|
||||||
var tribe = human->Customize[(int)CustomizeIndex.Tribe];
|
var tribe = human->Customize[(int)Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex.Tribe];
|
||||||
var modelType = human->Customize[(int)CustomizeIndex.ModelType];
|
var modelType = human->Customize[(int)Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex.ModelType];
|
||||||
if (faceId < 201)
|
if (faceId < 201)
|
||||||
{
|
{
|
||||||
faceId -= tribe switch
|
faceId -= tribe switch
|
||||||
|
|
@ -262,17 +261,17 @@ internal partial record ResolveContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstManipulation.EstType type)
|
private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanEquipmentSkeletonData(EquipSlot slot, EstManipulation.EstType type)
|
||||||
{
|
{
|
||||||
var human = (Human*)CharacterBase.Value;
|
var human = (Human*)CharacterBase.Value;
|
||||||
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
||||||
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
|
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe (GenderRace RaceCode, string Slot, SetId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstManipulation.EstType type, SetId set)
|
private unsafe (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstManipulation.EstType type, PrimaryId primary)
|
||||||
{
|
{
|
||||||
var metaCache = Global.Collection.MetaCache;
|
var metaCache = Global.Collection.MetaCache;
|
||||||
var skeletonSet = metaCache == null ? default : metaCache.GetEstEntry(type, raceCode, set);
|
var skeletonSet = metaCache == null ? default : metaCache.GetEstEntry(type, raceCode, primary);
|
||||||
return (raceCode, EstManipulation.ToName(type), skeletonSet);
|
return (raceCode, EstManipulation.ToName(type), skeletonSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using OtterGui;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
|
|
@ -19,17 +20,22 @@ using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.M
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
internal record GlobalResolveContext(IObjectIdentifier Identifier, ModCollection Collection, TreeBuildCache TreeBuildCache, bool WithUiData)
|
internal record GlobalResolveContext(ObjectIdentification Identifier, ModCollection Collection, TreeBuildCache TreeBuildCache, bool WithUiData)
|
||||||
{
|
{
|
||||||
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
||||||
|
|
||||||
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
|
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex = 0xFFFFFFFFu,
|
||||||
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, WeaponType weaponType = default)
|
EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default)
|
||||||
=> new(this, characterBase, slotIndex, slot, equipment, weaponType);
|
=> new(this, characterBase, slotIndex, slot, equipment, secondaryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial record ResolveContext(GlobalResolveContext Global, Pointer<CharacterBase> CharacterBase, uint SlotIndex,
|
internal partial record ResolveContext(
|
||||||
EquipSlot Slot, CharacterArmor Equipment, WeaponType WeaponType)
|
GlobalResolveContext Global,
|
||||||
|
Pointer<CharacterBase> CharacterBase,
|
||||||
|
uint SlotIndex,
|
||||||
|
EquipSlot Slot,
|
||||||
|
CharacterArmor Equipment,
|
||||||
|
SecondaryId SecondaryId)
|
||||||
{
|
{
|
||||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
||||||
|
|
||||||
|
|
@ -152,6 +158,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
|
||||||
{
|
{
|
||||||
if (mdl == null || mdl->ModelResourceHandle == null)
|
if (mdl == null || mdl->ModelResourceHandle == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var mdlResource = mdl->ModelResourceHandle;
|
var mdlResource = mdl->ModelResourceHandle;
|
||||||
|
|
||||||
var path = ResolveModelPath();
|
var path = ResolveModelPath();
|
||||||
|
|
@ -224,6 +231,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
|
||||||
shpkNode.Name = "Shader Package";
|
shpkNode.Name = "Shader Package";
|
||||||
node.Children.Add(shpkNode);
|
node.Children.Add(shpkNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
var shpkFile = Global.WithUiData && shpkNode != null ? Global.TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
|
var shpkFile = Global.WithUiData && shpkNode != null ? Global.TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
|
||||||
var shpk = Global.WithUiData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;
|
var shpk = Global.WithUiData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;
|
||||||
|
|
||||||
|
|
@ -310,13 +318,13 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ResourceNode.UiData GuessModelUIData(Utf8GamePath gamePath)
|
internal ResourceNode.UiData GuessModelUiData(Utf8GamePath gamePath)
|
||||||
{
|
{
|
||||||
var path = gamePath.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries);
|
var path = gamePath.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
// Weapons intentionally left out.
|
// Weapons intentionally left out.
|
||||||
var isEquipment = SafeGet(path, 0) == "chara" && SafeGet(path, 1) is "accessory" or "equipment";
|
var isEquipment = SafeGet(path, 0) == "chara" && SafeGet(path, 1) is "accessory" or "equipment";
|
||||||
if (isEquipment)
|
if (isEquipment)
|
||||||
foreach (var item in Global.Identifier.Identify(Equipment.Set, Equipment.Variant, Slot.ToSlot()))
|
foreach (var item in Global.Identifier.Identify(Equipment.Set, 0, Equipment.Variant, Slot.ToSlot()))
|
||||||
{
|
{
|
||||||
var name = Slot switch
|
var name = Slot switch
|
||||||
{
|
{
|
||||||
|
|
@ -324,11 +332,11 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
|
||||||
EquipSlot.LFinger => "L: ",
|
EquipSlot.LFinger => "L: ",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
}
|
}
|
||||||
+ item.Name.ToString();
|
+ item.Name;
|
||||||
return new ResourceNode.UiData(name, ChangedItemDrawer.GetCategoryIcon(item.Name, item));
|
return new ResourceNode.UiData(name, ChangedItemDrawer.GetCategoryIcon(item.Name, item));
|
||||||
}
|
}
|
||||||
|
|
||||||
var dataFromPath = GuessUIDataFromPath(gamePath);
|
var dataFromPath = GuessUiDataFromPath(gamePath);
|
||||||
if (dataFromPath.Name != null)
|
if (dataFromPath.Name != null)
|
||||||
return dataFromPath;
|
return dataFromPath;
|
||||||
|
|
||||||
|
|
@ -337,7 +345,7 @@ internal partial record ResolveContext(GlobalResolveContext Global, Pointer<Char
|
||||||
: new ResourceNode.UiData(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
|
: new ResourceNode.UiData(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ResourceNode.UiData GuessUIDataFromPath(Utf8GamePath gamePath)
|
internal ResourceNode.UiData GuessUiDataFromPath(Utf8GamePath gamePath)
|
||||||
{
|
{
|
||||||
foreach (var obj in Global.Identifier.Identify(gamePath.ToString()))
|
foreach (var obj in Global.Identifier.Identify(gamePath.ToString()))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.String.Classes;
|
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData;
|
||||||
|
using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Services;
|
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
@ -14,13 +15,13 @@ public class ResourceTreeFactory
|
||||||
private readonly IDataManager _gameData;
|
private readonly IDataManager _gameData;
|
||||||
private readonly IObjectTable _objects;
|
private readonly IObjectTable _objects;
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly IdentifierService _identifier;
|
private readonly ObjectIdentification _identifier;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly PathState _pathState;
|
private readonly PathState _pathState;
|
||||||
|
|
||||||
public ResourceTreeFactory(IDataManager gameData, IObjectTable objects, CollectionResolver resolver, IdentifierService identifier,
|
public ResourceTreeFactory(IDataManager gameData, IObjectTable objects, CollectionResolver resolver, ObjectIdentification identifier,
|
||||||
Configuration config, ActorService actors, PathState pathState)
|
Configuration config, ActorManager actors, PathState pathState)
|
||||||
{
|
{
|
||||||
_gameData = gameData;
|
_gameData = gameData;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
|
|
@ -88,12 +89,13 @@ public class ResourceTreeFactory
|
||||||
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
|
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
|
||||||
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related,
|
||||||
networked, collectionResolveData.ModCollection.Name);
|
networked, collectionResolveData.ModCollection.Name);
|
||||||
var globalContext = new GlobalResolveContext(_identifier.AwaitedService, collectionResolveData.ModCollection,
|
var globalContext = new GlobalResolveContext(_identifier, collectionResolveData.ModCollection,
|
||||||
cache, (flags & Flags.WithUiData) != 0);
|
cache, (flags & Flags.WithUiData) != 0);
|
||||||
using (var _ = _pathState.EnterInternalResolve())
|
using (var _ = _pathState.EnterInternalResolve())
|
||||||
{
|
{
|
||||||
tree.LoadResources(globalContext);
|
tree.LoadResources(globalContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
|
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
|
||||||
tree.ProcessPostfix((node, _) => tree.FlatNodes.Add(node));
|
tree.ProcessPostfix((node, _) => tree.FlatNodes.Add(node));
|
||||||
|
|
||||||
|
|
@ -161,9 +163,9 @@ public class ResourceTreeFactory
|
||||||
var gamePath = node.PossibleGamePaths[0];
|
var gamePath = node.PossibleGamePaths[0];
|
||||||
node.SetUiData(node.Type switch
|
node.SetUiData(node.Type switch
|
||||||
{
|
{
|
||||||
ResourceType.Imc => node.ResolveContext!.GuessModelUIData(gamePath).PrependName("IMC: "),
|
ResourceType.Imc => node.ResolveContext!.GuessModelUiData(gamePath).PrependName("IMC: "),
|
||||||
ResourceType.Mdl => node.ResolveContext!.GuessModelUIData(gamePath),
|
ResourceType.Mdl => node.ResolveContext!.GuessModelUiData(gamePath),
|
||||||
_ => node.ResolveContext!.GuessUIDataFromPath(gamePath),
|
_ => node.ResolveContext!.GuessUiDataFromPath(gamePath),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,7 +217,7 @@ public class ResourceTreeFactory
|
||||||
private unsafe (string Name, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character,
|
private unsafe (string Name, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character,
|
||||||
TreeBuildCache cache)
|
TreeBuildCache cache)
|
||||||
{
|
{
|
||||||
var identifier = _actors.AwaitedService.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
var identifier = _actors.FromObject((GameObject*)character.Address, out var owner, true, false, false);
|
||||||
switch (identifier.Type)
|
switch (identifier.Type)
|
||||||
{
|
{
|
||||||
case IdentifierType.Player: return (identifier.PlayerName.ToString(), true);
|
case IdentifierType.Player: return (identifier.PlayerName.ToString(), true);
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,26 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Services;
|
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
||||||
internal readonly struct TreeBuildCache
|
internal readonly struct TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorManager actors)
|
||||||
{
|
{
|
||||||
private readonly IDataManager _dataManager;
|
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = [];
|
||||||
private readonly ActorService _actors;
|
|
||||||
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = new();
|
|
||||||
private readonly IObjectTable _objects;
|
|
||||||
|
|
||||||
public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors)
|
|
||||||
{
|
|
||||||
_dataManager = dataManager;
|
|
||||||
_objects = objects;
|
|
||||||
_actors = actors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool IsLocalPlayerRelated(Character character)
|
public unsafe bool IsLocalPlayerRelated(Character character)
|
||||||
{
|
{
|
||||||
var player = _objects[0];
|
var player = objects[0];
|
||||||
if (player == null)
|
if (player == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)character.Address;
|
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)character.Address;
|
||||||
var parent = _actors.AwaitedService.ToCutsceneParent(gameObject->ObjectIndex);
|
var parent = actors.ToCutsceneParent(gameObject->ObjectIndex);
|
||||||
var actualIndex = parent >= 0 ? (ushort)parent : gameObject->ObjectIndex;
|
var actualIndex = parent >= 0 ? (ushort)parent : gameObject->ObjectIndex;
|
||||||
return actualIndex switch
|
return actualIndex switch
|
||||||
{
|
{
|
||||||
|
|
@ -41,38 +31,38 @@ internal readonly struct TreeBuildCache
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Character> GetCharacters()
|
public IEnumerable<Character> GetCharacters()
|
||||||
=> _objects.OfType<Character>();
|
=> objects.OfType<Character>();
|
||||||
|
|
||||||
public IEnumerable<Character> GetLocalPlayerRelatedCharacters()
|
public IEnumerable<Character> GetLocalPlayerRelatedCharacters()
|
||||||
{
|
{
|
||||||
var player = _objects[0];
|
var player = objects[0];
|
||||||
if (player == null)
|
if (player == null)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
yield return (Character)player;
|
yield return (Character)player;
|
||||||
|
|
||||||
var minion = _objects[1];
|
var minion = objects[1];
|
||||||
if (minion != null)
|
if (minion != null)
|
||||||
yield return (Character)minion;
|
yield return (Character)minion;
|
||||||
|
|
||||||
var playerId = player.ObjectId;
|
var playerId = player.ObjectId;
|
||||||
for (var i = 2; i < ObjectIndex.CutsceneStart.Index; i += 2)
|
for (var i = 2; i < ObjectIndex.CutsceneStart.Index; i += 2)
|
||||||
{
|
{
|
||||||
if (_objects[i] is Character owned && owned.OwnerId == playerId)
|
if (objects[i] is Character owned && owned.OwnerId == playerId)
|
||||||
yield return owned;
|
yield return owned;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = ObjectIndex.CutsceneStart.Index; i < ObjectIndex.CharacterScreen.Index; ++i)
|
for (var i = ObjectIndex.CutsceneStart.Index; i < ObjectIndex.CharacterScreen.Index; ++i)
|
||||||
{
|
{
|
||||||
var character = _objects[i] as Character;
|
var character = objects[i] as Character;
|
||||||
if (character == null)
|
if (character == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var parent = _actors.AwaitedService.ToCutsceneParent(i);
|
var parent = actors.ToCutsceneParent(i);
|
||||||
if (parent < 0)
|
if (parent < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (parent is 0 or 1 || _objects[parent]?.OwnerId == playerId)
|
if (parent is 0 or 1 || objects[parent]?.OwnerId == playerId)
|
||||||
yield return character;
|
yield return character;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,11 +75,11 @@ internal readonly struct TreeBuildCache
|
||||||
|
|
||||||
private unsafe bool GetOwnedId(ByteString playerName, uint playerId, int idx, [NotNullWhen(true)] out Character? character)
|
private unsafe bool GetOwnedId(ByteString playerName, uint playerId, int idx, [NotNullWhen(true)] out Character? character)
|
||||||
{
|
{
|
||||||
character = _objects[idx] as Character;
|
character = objects[idx] as Character;
|
||||||
if (character == null)
|
if (character == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var actorId = _actors.AwaitedService.FromObject(character, out var owner, true, true, true);
|
var actorId = actors.FromObject(character, out var owner, true, true, true);
|
||||||
if (!actorId.IsValid)
|
if (!actorId.IsValid)
|
||||||
return false;
|
return false;
|
||||||
if (owner != null && owner->OwnerID != playerId)
|
if (owner != null && owner->OwnerID != playerId)
|
||||||
|
|
@ -102,7 +92,7 @@ internal readonly struct TreeBuildCache
|
||||||
|
|
||||||
/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>
|
/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>
|
||||||
public ShpkFile? ReadShaderPackage(FullPath path)
|
public ShpkFile? ReadShaderPackage(FullPath path)
|
||||||
=> ReadFile(_dataManager, path, _shaderPackages, bytes => new ShpkFile(bytes));
|
=> ReadFile(dataManager, path, _shaderPackages, bytes => new ShpkFile(bytes));
|
||||||
|
|
||||||
private static T? ReadFile<T>(IDataManager dataManager, FullPath path, Dictionary<FullPath, T?> cache, Func<byte[], T> parseFile)
|
private static T? ReadFile<T>(IDataManager dataManager, FullPath path, Dictionary<FullPath, T?> cache, Func<byte[], T> parseFile)
|
||||||
where T : class
|
where T : class
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using FFXIVClientStructs.Interop;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Penumbra.GameData;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Structs;
|
namespace Penumbra.Interop.Structs;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,7 @@ public class ModelResourceHandleUtility
|
||||||
public ModelResourceHandleUtility(IGameInteropProvider interop)
|
public ModelResourceHandleUtility(IGameInteropProvider interop)
|
||||||
=> interop.InitializeFromAttributes(this);
|
=> interop.InitializeFromAttributes(this);
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 44 8B CD 48 89 44 24")]
|
[Signature(Sigs.GetMaterialFileNameBySlot)]
|
||||||
private static nint _getMaterialFileNameBySlot = nint.Zero;
|
private static nint _getMaterialFileNameBySlot = nint.Zero;
|
||||||
|
|
||||||
public static unsafe byte* GetMaterialFileNameBySlot(ModelResourceHandle* handle, uint slot)
|
public static unsafe byte* GetMaterialFileNameBySlot(ModelResourceHandle* handle, uint slot)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
public int Count
|
public int Count
|
||||||
=> (Length - DataOffset) / EqdpEntrySize;
|
=> (Length - DataOffset) / EqdpEntrySize;
|
||||||
|
|
||||||
public EqdpEntry this[SetId id]
|
public EqdpEntry this[PrimaryId id]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
|
@ -79,7 +79,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
|
MemoryUtility.MemSet(myDataPtr, 0, Length - (int)((byte*)myDataPtr - Data));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(IEnumerable<SetId> entries)
|
public void Reset(IEnumerable<PrimaryId> entries)
|
||||||
{
|
{
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
this[entry] = GetDefault(entry);
|
this[entry] = GetDefault(entry);
|
||||||
|
|
@ -101,18 +101,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public EqdpEntry GetDefault(SetId setId)
|
public EqdpEntry GetDefault(PrimaryId primaryId)
|
||||||
=> GetDefault(Manager, Index, setId);
|
=> GetDefault(Manager, Index, primaryId);
|
||||||
|
|
||||||
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, SetId setId)
|
public static EqdpEntry GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex idx, PrimaryId primaryId)
|
||||||
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, setId);
|
=> GetDefault((byte*)manager.CharacterUtility.DefaultResource(idx).Address, primaryId);
|
||||||
|
|
||||||
public static EqdpEntry GetDefault(byte* data, SetId setId)
|
public static EqdpEntry GetDefault(byte* data, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var blockSize = *(ushort*)(data + IdentifierSize);
|
var blockSize = *(ushort*)(data + IdentifierSize);
|
||||||
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
|
var totalBlockCount = *(ushort*)(data + IdentifierSize + 2);
|
||||||
|
|
||||||
var blockIdx = setId.Id / blockSize;
|
var blockIdx = primaryId.Id / blockSize;
|
||||||
if (blockIdx >= totalBlockCount)
|
if (blockIdx >= totalBlockCount)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -121,9 +121,9 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2);
|
var blockData = (ushort*)(data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block * 2);
|
||||||
return (EqdpEntry)(*(blockData + setId.Id % blockSize));
|
return (EqdpEntry)(*(blockData + primaryId.Id % blockSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, SetId setId)
|
public static EqdpEntry GetDefault(MetaFileManager manager, GenderRace raceCode, bool accessory, PrimaryId primaryId)
|
||||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], setId);
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)CharacterUtilityData.EqdpIdx(raceCode, accessory)], primaryId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
public ulong ControlBlock
|
public ulong ControlBlock
|
||||||
=> *(ulong*)Data;
|
=> *(ulong*)Data;
|
||||||
|
|
||||||
protected ulong GetInternal(SetId idx)
|
protected ulong GetInternal(PrimaryId idx)
|
||||||
{
|
{
|
||||||
return idx.Id switch
|
return idx.Id switch
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +34,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SetInternal(SetId idx, ulong value)
|
protected void SetInternal(PrimaryId idx, ulong value)
|
||||||
{
|
{
|
||||||
idx = idx.Id switch
|
idx = idx.Id switch
|
||||||
{
|
{
|
||||||
|
|
@ -81,13 +81,13 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, SetId setId, ulong def)
|
protected static ulong GetDefaultInternal(MetaFileManager manager, CharacterUtility.InternalIndex fileIndex, PrimaryId primaryId, ulong def)
|
||||||
{
|
{
|
||||||
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
|
var data = (byte*)manager.CharacterUtility.DefaultResource(fileIndex).Address;
|
||||||
if (setId == 0)
|
if (primaryId == 0)
|
||||||
setId = 1;
|
primaryId = 1;
|
||||||
|
|
||||||
var blockIdx = setId.Id / BlockSize;
|
var blockIdx = primaryId.Id / BlockSize;
|
||||||
if (blockIdx >= NumBlocks)
|
if (blockIdx >= NumBlocks)
|
||||||
return def;
|
return def;
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
return def;
|
return def;
|
||||||
|
|
||||||
var count = BitOperations.PopCount(control & (blockBit - 1));
|
var count = BitOperations.PopCount(control & (blockBit - 1));
|
||||||
var idx = setId.Id % BlockSize;
|
var idx = primaryId.Id % BlockSize;
|
||||||
var ptr = (ulong*)data + BlockSize * count + idx;
|
var ptr = (ulong*)data + BlockSize * count + idx;
|
||||||
return *ptr;
|
return *ptr;
|
||||||
}
|
}
|
||||||
|
|
@ -112,15 +112,15 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
||||||
: base(manager, false)
|
: base(manager, false)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public EqpEntry this[SetId idx]
|
public EqpEntry this[PrimaryId idx]
|
||||||
{
|
{
|
||||||
get => (EqpEntry)GetInternal(idx);
|
get => (EqpEntry)GetInternal(idx);
|
||||||
set => SetInternal(idx, (ulong)value);
|
set => SetInternal(idx, (ulong)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static EqpEntry GetDefault(MetaFileManager manager, SetId setIdx)
|
public static EqpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
|
||||||
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)Eqp.DefaultEntry);
|
=> (EqpEntry)GetDefaultInternal(manager, InternalIndex, primaryIdx, (ulong)Eqp.DefaultEntry);
|
||||||
|
|
||||||
protected override unsafe void SetEmptyBlock(int idx)
|
protected override unsafe void SetEmptyBlock(int idx)
|
||||||
{
|
{
|
||||||
|
|
@ -130,7 +130,7 @@ public sealed class ExpandedEqpFile : ExpandedEqpGmpBase, IEnumerable<EqpEntry>
|
||||||
*ptr = (ulong)Eqp.DefaultEntry;
|
*ptr = (ulong)Eqp.DefaultEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(IEnumerable<SetId> entries)
|
public void Reset(IEnumerable<PrimaryId> entries)
|
||||||
{
|
{
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
this[entry] = GetDefault(Manager, entry);
|
this[entry] = GetDefault(Manager, entry);
|
||||||
|
|
@ -155,16 +155,16 @@ public sealed class ExpandedGmpFile : ExpandedEqpGmpBase, IEnumerable<GmpEntry>
|
||||||
: base(manager, true)
|
: base(manager, true)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public GmpEntry this[SetId idx]
|
public GmpEntry this[PrimaryId idx]
|
||||||
{
|
{
|
||||||
get => (GmpEntry)GetInternal(idx);
|
get => (GmpEntry)GetInternal(idx);
|
||||||
set => SetInternal(idx, (ulong)value);
|
set => SetInternal(idx, (ulong)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GmpEntry GetDefault(MetaFileManager manager, SetId setIdx)
|
public static GmpEntry GetDefault(MetaFileManager manager, PrimaryId primaryIdx)
|
||||||
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, setIdx, (ulong)GmpEntry.Default);
|
=> (GmpEntry)GetDefaultInternal(manager, InternalIndex, primaryIdx, (ulong)GmpEntry.Default);
|
||||||
|
|
||||||
public void Reset(IEnumerable<SetId> entries)
|
public void Reset(IEnumerable<PrimaryId> entries)
|
||||||
{
|
{
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
this[entry] = GetDefault(Manager, entry);
|
this[entry] = GetDefault(Manager, entry);
|
||||||
|
|
|
||||||
|
|
@ -167,21 +167,21 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
public ushort GetDefault(GenderRace genderRace, ushort setId)
|
public ushort GetDefault(GenderRace genderRace, ushort setId)
|
||||||
=> GetDefault(Manager, Index, genderRace, setId);
|
=> GetDefault(Manager, Index, genderRace, setId);
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, SetId setId)
|
public static ushort GetDefault(MetaFileManager manager, CharacterUtility.InternalIndex index, GenderRace genderRace, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
|
var data = (byte*)manager.CharacterUtility.DefaultResource(index).Address;
|
||||||
var count = *(int*)data;
|
var count = *(int*)data;
|
||||||
var span = new ReadOnlySpan<Info>(data + 4, count);
|
var span = new ReadOnlySpan<Info>(data + 4, count);
|
||||||
var (idx, found) = FindEntry(span, genderRace, setId.Id);
|
var (idx, found) = FindEntry(span, genderRace, primaryId.Id);
|
||||||
if (!found)
|
if (!found)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
return *(ushort*)(data + 4 + count * EntryDescSize + idx * EntrySize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, SetId setId)
|
public static ushort GetDefault(MetaFileManager manager, MetaIndex metaIndex, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, setId);
|
=> GetDefault(manager, CharacterUtility.ReverseIndices[(int)metaIndex], genderRace, primaryId);
|
||||||
|
|
||||||
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, SetId setId)
|
public static ushort GetDefault(MetaFileManager manager, EstManipulation.EstType estType, GenderRace genderRace, PrimaryId primaryId)
|
||||||
=> GetDefault(manager, (MetaIndex)estType, genderRace, setId);
|
=> GetDefault(manager, (MetaIndex)estType, genderRace, primaryId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ModelRace Race { get; private init; }
|
public ModelRace Race { get; private init; }
|
||||||
|
|
||||||
public SetId SetId { get; private init; }
|
public PrimaryId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot Slot { get; private init; }
|
public EquipSlot Slot { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, SetId setId)
|
public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Gender = gender;
|
Gender = gender;
|
||||||
Race = race;
|
Race = race;
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,13 @@ public readonly struct EqpManipulation : IMetaManipulation<EqpManipulation>
|
||||||
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
[JsonConverter(typeof(ForceNumericFlagEnumConverter))]
|
||||||
public EqpEntry Entry { get; private init; }
|
public EqpEntry Entry { get; private init; }
|
||||||
|
|
||||||
public SetId SetId { get; private init; }
|
public PrimaryId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EquipSlot Slot { get; private init; }
|
public EquipSlot Slot { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EqpManipulation(EqpEntry entry, EquipSlot slot, SetId setId)
|
public EqpManipulation(EqpEntry entry, EquipSlot slot, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Slot = slot;
|
Slot = slot;
|
||||||
SetId = setId;
|
SetId = setId;
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,14 @@ public readonly struct EstManipulation : IMetaManipulation<EstManipulation>
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public ModelRace Race { get; private init; }
|
public ModelRace Race { get; private init; }
|
||||||
|
|
||||||
public SetId SetId { get; private init; }
|
public PrimaryId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public EstType Slot { get; private init; }
|
public EstType Slot { get; private init; }
|
||||||
|
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public EstManipulation(Gender gender, ModelRace race, EstType slot, SetId setId, ushort entry)
|
public EstManipulation(Gender gender, ModelRace race, EstType slot, PrimaryId setId, ushort entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
Gender = gender;
|
Gender = gender;
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
public readonly struct GmpManipulation : IMetaManipulation<GmpManipulation>
|
||||||
{
|
{
|
||||||
public GmpEntry Entry { get; private init; }
|
public GmpEntry Entry { get; private init; }
|
||||||
public SetId SetId { get; private init; }
|
public PrimaryId SetId { get; private init; }
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public GmpManipulation(GmpEntry entry, SetId setId)
|
public GmpManipulation(GmpEntry entry, PrimaryId setId)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
SetId = setId;
|
SetId = setId;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ namespace Penumbra.Meta.Manipulations;
|
||||||
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||||
{
|
{
|
||||||
public ImcEntry Entry { get; private init; }
|
public ImcEntry Entry { get; private init; }
|
||||||
public SetId PrimaryId { get; private init; }
|
public PrimaryId PrimaryId { get; private init; }
|
||||||
public SetId SecondaryId { get; private init; }
|
public PrimaryId SecondaryId { get; private init; }
|
||||||
public Variant Variant { get; private init; }
|
public Variant Variant { get; private init; }
|
||||||
|
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
|
@ -26,7 +26,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public BodySlot BodySlot { get; private init; }
|
public BodySlot BodySlot { get; private init; }
|
||||||
|
|
||||||
public ImcManipulation(EquipSlot equipSlot, ushort variant, SetId primaryId, ImcEntry entry)
|
public ImcManipulation(EquipSlot equipSlot, ushort variant, PrimaryId primaryId, ImcEntry entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
PrimaryId = primaryId;
|
PrimaryId = primaryId;
|
||||||
|
|
@ -42,7 +42,7 @@ public readonly struct ImcManipulation : IMetaManipulation<ImcManipulation>
|
||||||
// so we change the unused value to something nonsensical in that case, just so they do not compare equal,
|
// so we change the unused value to something nonsensical in that case, just so they do not compare equal,
|
||||||
// and clamp the variant to 255.
|
// and clamp the variant to 255.
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, SetId primaryId, SetId secondaryId, ushort variant,
|
internal ImcManipulation(ObjectType objectType, BodySlot bodySlot, PrimaryId primaryId, PrimaryId secondaryId, ushort variant,
|
||||||
EquipSlot equipSlot, ImcEntry entry)
|
EquipSlot equipSlot, ImcEntry entry)
|
||||||
{
|
{
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
|
||||||
|
|
@ -270,4 +270,22 @@ public readonly struct MetaManipulation : IEquatable<MetaManipulation>, ICompara
|
||||||
Type.Rsp => $"{Rsp.Entry}",
|
Type.Rsp => $"{Rsp.Entry}",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static bool operator ==(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> left.Equals(right);
|
||||||
|
|
||||||
|
public static bool operator !=(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> !(left == right);
|
||||||
|
|
||||||
|
public static bool operator <(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> left.CompareTo(right) < 0;
|
||||||
|
|
||||||
|
public static bool operator <=(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> left.CompareTo(right) <= 0;
|
||||||
|
|
||||||
|
public static bool operator >(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> left.CompareTo(right) > 0;
|
||||||
|
|
||||||
|
public static bool operator >=(MetaManipulation left, MetaManipulation right)
|
||||||
|
=> left.CompareTo(right) >= 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using OtterGui.Compression;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
using Penumbra.Interop.Services;
|
using Penumbra.Interop.Services;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
|
@ -24,11 +25,11 @@ public unsafe class MetaFileManager
|
||||||
internal readonly IDataManager GameData;
|
internal readonly IDataManager GameData;
|
||||||
internal readonly ActiveCollectionData ActiveCollections;
|
internal readonly ActiveCollectionData ActiveCollections;
|
||||||
internal readonly ValidityChecker ValidityChecker;
|
internal readonly ValidityChecker ValidityChecker;
|
||||||
internal readonly IdentifierService Identifier;
|
internal readonly ObjectIdentification Identifier;
|
||||||
internal readonly FileCompactor Compactor;
|
internal readonly FileCompactor Compactor;
|
||||||
|
|
||||||
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
|
public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData,
|
||||||
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, IdentifierService identifier,
|
ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier,
|
||||||
FileCompactor compactor, IGameInteropProvider interop)
|
FileCompactor compactor, IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
CharacterUtility = characterUtility;
|
CharacterUtility = characterUtility;
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,17 @@ namespace Penumbra.Mods.Editor;
|
||||||
|
|
||||||
public class DuplicateManager
|
public class DuplicateManager
|
||||||
{
|
{
|
||||||
|
private readonly Configuration _config;
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly SHA256 _hasher = SHA256.Create();
|
private readonly SHA256 _hasher = SHA256.Create();
|
||||||
private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = new();
|
private readonly List<(FullPath[] Paths, long Size, byte[] Hash)> _duplicates = new();
|
||||||
|
|
||||||
public DuplicateManager(ModManager modManager, SaveService saveService)
|
public DuplicateManager(ModManager modManager, SaveService saveService, Configuration config)
|
||||||
{
|
{
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
|
public IReadOnlyList<(FullPath[] Paths, long Size, byte[] Hash)> Duplicates
|
||||||
|
|
@ -82,7 +84,7 @@ public class DuplicateManager
|
||||||
{
|
{
|
||||||
var sub = (SubMod)subMod;
|
var sub = (SubMod)subMod;
|
||||||
sub.FileData = dict;
|
sub.FileData = dict;
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public static class CustomizationSwap
|
||||||
{
|
{
|
||||||
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
|
/// The .mdl file for customizations is unique per racecode, slot and id, thus the .mdl redirection itself is independent of the mode.
|
||||||
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
SetId idFrom, SetId idTo)
|
PrimaryId idFrom, PrimaryId idTo)
|
||||||
{
|
{
|
||||||
if (idFrom.Id > byte.MaxValue)
|
if (idFrom.Id > byte.MaxValue)
|
||||||
throw new Exception($"The Customization ID {idFrom} is too large for {slot}.");
|
throw new Exception($"The Customization ID {idFrom} is too large for {slot}.");
|
||||||
|
|
@ -43,10 +43,10 @@ public static class CustomizationSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
public static FileSwap CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
SetId idFrom, SetId idTo, byte variant,
|
PrimaryId idFrom, PrimaryId idTo, byte variant,
|
||||||
ref string fileName, ref bool dataWasChanged)
|
ref string fileName, ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
variant = slot is BodySlot.Face or BodySlot.Zear ? byte.MaxValue : variant;
|
variant = slot is BodySlot.Face or BodySlot.Ear ? byte.MaxValue : variant;
|
||||||
var mtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant);
|
var mtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant);
|
||||||
var mtrlToPath = GamePaths.Character.Mtrl.Path(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant);
|
var mtrlToPath = GamePaths.Character.Mtrl.Path(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant);
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ public static class CustomizationSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, BodySlot slot, GenderRace race,
|
||||||
SetId idFrom, ref MtrlFile.Texture texture,
|
PrimaryId idFrom, ref MtrlFile.Texture texture,
|
||||||
ref bool dataWasChanged)
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = texture.Path;
|
var path = texture.Path;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
|
@ -41,7 +40,7 @@ public static class EquipmentSwap
|
||||||
: Array.Empty<EquipSlot>();
|
: Array.Empty<EquipSlot>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EquipItem[] CreateTypeSwap(MetaFileManager manager, IObjectIdentifier identifier, List<Swap> swaps,
|
public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
|
||||||
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips,
|
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips,
|
||||||
EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo)
|
EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo)
|
||||||
{
|
{
|
||||||
|
|
@ -99,7 +98,7 @@ public static class EquipmentSwap
|
||||||
return affectedItems;
|
return affectedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EquipItem[] CreateItemSwap(MetaFileManager manager, IObjectIdentifier identifier, List<Swap> swaps,
|
public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List<Swap> swaps,
|
||||||
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipItem itemFrom,
|
Func<Utf8GamePath, FullPath> redirections, Func<MetaManipulation, MetaManipulation> manips, EquipItem itemFrom,
|
||||||
EquipItem itemTo, bool rFinger = true, bool lFinger = true)
|
EquipItem itemTo, bool rFinger = true, bool lFinger = true)
|
||||||
{
|
{
|
||||||
|
|
@ -186,13 +185,13 @@ public static class EquipmentSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, SetId idFrom,
|
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom,
|
||||||
SetId idTo, byte mtrlTo)
|
PrimaryId idTo, byte mtrlTo)
|
||||||
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
|
=> CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo);
|
||||||
|
|
||||||
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap? CreateEqdp(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, SetId idFrom,
|
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom,
|
||||||
SetId idTo, byte mtrlTo)
|
PrimaryId idTo, byte mtrlTo)
|
||||||
{
|
{
|
||||||
var (gender, race) = gr.Split();
|
var (gender, race) = gr.Split();
|
||||||
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender,
|
var eqdpFrom = new EqdpManipulation(ExpandedEqdpFile.GetDefault(manager, gr, slotFrom.IsAccessory(), idFrom), slotFrom, gender,
|
||||||
|
|
@ -215,11 +214,11 @@ public static class EquipmentSwap
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, GenderRace gr,
|
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, GenderRace gr,
|
||||||
SetId idFrom, SetId idTo, byte mtrlTo)
|
PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
|
||||||
=> CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo);
|
=> CreateMdl(manager, redirections, slot, slot, gr, idFrom, idTo, mtrlTo);
|
||||||
|
|
||||||
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
public static FileSwap CreateMdl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
||||||
GenderRace gr, SetId idFrom, SetId idTo, byte mtrlTo)
|
GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo)
|
||||||
{
|
{
|
||||||
var mdlPathFrom = slotFrom.IsAccessory()
|
var mdlPathFrom = slotFrom.IsAccessory()
|
||||||
? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom)
|
? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom)
|
||||||
|
|
@ -237,18 +236,18 @@ public static class EquipmentSwap
|
||||||
return mdl;
|
return mdl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LookupItem(EquipItem i, out EquipSlot slot, out SetId modelId, out Variant variant)
|
private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId modelId, out Variant variant)
|
||||||
{
|
{
|
||||||
slot = i.Type.ToSlot();
|
slot = i.Type.ToSlot();
|
||||||
if (!slot.IsEquipmentPiece())
|
if (!slot.IsEquipmentPiece())
|
||||||
throw new ItemSwap.InvalidItemTypeException();
|
throw new ItemSwap.InvalidItemTypeException();
|
||||||
|
|
||||||
modelId = i.ModelId;
|
modelId = i.PrimaryId;
|
||||||
variant = i.Variant;
|
variant = i.Variant;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, IObjectIdentifier identifier, EquipSlot slotFrom,
|
private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom,
|
||||||
SetId idFrom, SetId idTo, Variant variantFrom)
|
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom)
|
||||||
{
|
{
|
||||||
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
|
var entry = new ImcManipulation(slotFrom, variantFrom.Id, idFrom, default);
|
||||||
var imc = new ImcFile(manager, entry);
|
var imc = new ImcFile(manager, entry);
|
||||||
|
|
@ -256,11 +255,8 @@ public static class EquipmentSwap
|
||||||
Variant[] variants;
|
Variant[] variants;
|
||||||
if (idFrom == idTo)
|
if (idFrom == idTo)
|
||||||
{
|
{
|
||||||
items = identifier.Identify(idFrom, variantFrom, slotFrom).ToArray();
|
items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToArray();
|
||||||
variants = new[]
|
variants = [variantFrom];
|
||||||
{
|
|
||||||
variantFrom,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -274,8 +270,8 @@ public static class EquipmentSwap
|
||||||
return (imc, variants, items);
|
return (imc, variants, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
public static MetaSwap? CreateGmp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
|
||||||
SetId idTo)
|
PrimaryId idTo)
|
||||||
{
|
{
|
||||||
if (slot is not EquipSlot.Head)
|
if (slot is not EquipSlot.Head)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -287,12 +283,12 @@ public static class EquipmentSwap
|
||||||
|
|
||||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
|
Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot,
|
||||||
SetId idFrom, SetId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
PrimaryId idFrom, PrimaryId idTo, Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||||
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
|
=> CreateImc(manager, redirections, manips, slot, slot, idFrom, idTo, variantFrom, variantTo, imcFileFrom, imcFileTo);
|
||||||
|
|
||||||
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap CreateImc(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips,
|
Func<MetaManipulation, MetaManipulation> manips,
|
||||||
EquipSlot slotFrom, EquipSlot slotTo, SetId idFrom, SetId idTo,
|
EquipSlot slotFrom, EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo,
|
||||||
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo)
|
||||||
{
|
{
|
||||||
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
var entryFrom = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom);
|
||||||
|
|
@ -326,7 +322,7 @@ public static class EquipmentSwap
|
||||||
|
|
||||||
|
|
||||||
// Example: Abyssos Helm / Body
|
// Example: Abyssos Helm / Body
|
||||||
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, SetId idFrom, SetId idTo, byte vfxId)
|
public static FileSwap? CreateAvfx(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, PrimaryId idFrom, PrimaryId idTo, byte vfxId)
|
||||||
{
|
{
|
||||||
if (vfxId == 0)
|
if (vfxId == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -344,8 +340,8 @@ public static class EquipmentSwap
|
||||||
return avfx;
|
return avfx;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, SetId idFrom,
|
public static MetaSwap? CreateEqp(MetaFileManager manager, Func<MetaManipulation, MetaManipulation> manips, EquipSlot slot, PrimaryId idFrom,
|
||||||
SetId idTo)
|
PrimaryId idTo)
|
||||||
{
|
{
|
||||||
if (slot.IsAccessory())
|
if (slot.IsAccessory())
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -357,13 +353,13 @@ public static class EquipmentSwap
|
||||||
return new MetaSwap(manips, eqpFrom, eqpTo);
|
return new MetaSwap(manips, eqpFrom, eqpTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, SetId idFrom,
|
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slot, PrimaryId idFrom,
|
||||||
SetId idTo, byte variantTo, ref string fileName,
|
PrimaryId idTo, byte variantTo, ref string fileName,
|
||||||
ref bool dataWasChanged)
|
ref bool dataWasChanged)
|
||||||
=> CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged);
|
=> CreateMtrl(manager, redirections, slot, slot, idFrom, idTo, variantTo, ref fileName, ref dataWasChanged);
|
||||||
|
|
||||||
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
public static FileSwap? CreateMtrl(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, EquipSlot slotFrom, EquipSlot slotTo,
|
||||||
SetId idFrom, SetId idTo, byte variantTo, ref string fileName,
|
PrimaryId idFrom, PrimaryId idTo, byte variantTo, ref string fileName,
|
||||||
ref bool dataWasChanged)
|
ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var prefix = slotTo.IsAccessory() ? 'a' : 'e';
|
var prefix = slotTo.IsAccessory() ? 'a' : 'e';
|
||||||
|
|
@ -401,13 +397,13 @@ public static class EquipmentSwap
|
||||||
return mtrl;
|
return mtrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, SetId idFrom, SetId idTo,
|
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, PrimaryId idFrom, PrimaryId idTo,
|
||||||
ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||||
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
|
=> CreateTex(manager, redirections, prefix, EquipSlot.Unknown, EquipSlot.Unknown, idFrom, idTo, ref texture, ref dataWasChanged);
|
||||||
|
|
||||||
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
|
public static FileSwap CreateTex(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections, char prefix, EquipSlot slotFrom,
|
||||||
EquipSlot slotTo, SetId idFrom,
|
EquipSlot slotTo, PrimaryId idFrom,
|
||||||
SetId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged)
|
||||||
{
|
{
|
||||||
var path = texture.Path;
|
var path = texture.Path;
|
||||||
var addedDashes = false;
|
var addedDashes = false;
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ public static class ItemSwap
|
||||||
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
/// <remarks> metaChanges is not manipulated, but IReadOnlySet does not support TryGetValue. </remarks>
|
||||||
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
public static MetaSwap? CreateEst(MetaFileManager manager, Func<Utf8GamePath, FullPath> redirections,
|
||||||
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
|
Func<MetaManipulation, MetaManipulation> manips, EstManipulation.EstType type,
|
||||||
GenderRace genderRace, SetId idFrom, SetId idTo, bool ownMdl)
|
GenderRace genderRace, PrimaryId idFrom, PrimaryId idTo, bool ownMdl)
|
||||||
{
|
{
|
||||||
if (type == 0)
|
if (type == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -195,7 +195,7 @@ public static class ItemSwap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ReplaceAnyId(string path, char idType, SetId id, bool condition = true)
|
public static string ReplaceAnyId(string path, char idType, PrimaryId id, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
|
? Regex.Replace(path, $"{idType}\\d{{4}}", $"{idType}{id.Id:D4}")
|
||||||
: path;
|
: path;
|
||||||
|
|
@ -203,10 +203,10 @@ public static class ItemSwap
|
||||||
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
|
public static string ReplaceAnyRace(string path, GenderRace to, bool condition = true)
|
||||||
=> ReplaceAnyId(path, 'c', (ushort)to, condition);
|
=> ReplaceAnyId(path, 'c', (ushort)to, condition);
|
||||||
|
|
||||||
public static string ReplaceAnyBody(string path, BodySlot slot, SetId to, bool condition = true)
|
public static string ReplaceAnyBody(string path, BodySlot slot, PrimaryId to, bool condition = true)
|
||||||
=> ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
|
=> ReplaceAnyId(path, slot.ToAbbreviation(), to, condition);
|
||||||
|
|
||||||
public static string ReplaceId(string path, char type, SetId idFrom, SetId idTo, bool condition = true)
|
public static string ReplaceId(string path, char type, PrimaryId idFrom, PrimaryId idTo, bool condition = true)
|
||||||
=> condition
|
=> condition
|
||||||
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
|
? path.Replace($"{type}{idFrom.Id:D4}", $"{type}{idTo.Id:D4}")
|
||||||
: path;
|
: path;
|
||||||
|
|
@ -219,7 +219,7 @@ public static class ItemSwap
|
||||||
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
|
public static string ReplaceRace(string path, GenderRace from, GenderRace to, bool condition = true)
|
||||||
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
|
=> ReplaceId(path, 'c', (ushort)from, (ushort)to, condition);
|
||||||
|
|
||||||
public static string ReplaceBody(string path, BodySlot slot, SetId idFrom, SetId idTo, bool condition = true)
|
public static string ReplaceBody(string path, BodySlot slot, PrimaryId idFrom, PrimaryId idTo, bool condition = true)
|
||||||
=> ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
|
=> ReplaceId(path, slot.ToAbbreviation(), idFrom, idTo, condition);
|
||||||
|
|
||||||
public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
|
public static string AddSuffix(string path, string ext, string suffix, bool condition = true)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -7,17 +7,16 @@ using Penumbra.String.Classes;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Mods.Subclasses;
|
using Penumbra.Mods.Subclasses;
|
||||||
using Penumbra.Services;
|
|
||||||
|
|
||||||
namespace Penumbra.Mods.ItemSwap;
|
namespace Penumbra.Mods.ItemSwap;
|
||||||
|
|
||||||
public class ItemSwapContainer
|
public class ItemSwapContainer
|
||||||
{
|
{
|
||||||
private readonly MetaFileManager _manager;
|
private readonly MetaFileManager _manager;
|
||||||
private readonly IdentifierService _identifier;
|
private readonly ObjectIdentification _identifier;
|
||||||
|
|
||||||
private Dictionary<Utf8GamePath, FullPath> _modRedirections = new();
|
private Dictionary<Utf8GamePath, FullPath> _modRedirections = [];
|
||||||
private HashSet<MetaManipulation> _modManipulations = new();
|
private HashSet<MetaManipulation> _modManipulations = [];
|
||||||
|
|
||||||
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
|
public IReadOnlyDictionary<Utf8GamePath, FullPath> ModRedirections
|
||||||
=> _modRedirections;
|
=> _modRedirections;
|
||||||
|
|
@ -25,7 +24,7 @@ public class ItemSwapContainer
|
||||||
public IReadOnlySet<MetaManipulation> ModManipulations
|
public IReadOnlySet<MetaManipulation> ModManipulations
|
||||||
=> _modManipulations;
|
=> _modManipulations;
|
||||||
|
|
||||||
public readonly List<Swap> Swaps = new();
|
public readonly List<Swap> Swaps = [];
|
||||||
|
|
||||||
public bool Loaded { get; private set; }
|
public bool Loaded { get; private set; }
|
||||||
|
|
||||||
|
|
@ -107,7 +106,7 @@ public class ItemSwapContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemSwapContainer(MetaFileManager manager, IdentifierService identifier)
|
public ItemSwapContainer(MetaFileManager manager, ObjectIdentification identifier)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_identifier = identifier;
|
_identifier = identifier;
|
||||||
|
|
@ -130,7 +129,7 @@ public class ItemSwapContainer
|
||||||
{
|
{
|
||||||
Swaps.Clear();
|
Swaps.Clear();
|
||||||
Loaded = false;
|
Loaded = false;
|
||||||
var ret = EquipmentSwap.CreateItemSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
|
var ret = EquipmentSwap.CreateItemSwap(_manager, _identifier, Swaps, PathResolver(collection), MetaResolver(collection),
|
||||||
from, to, useRightRing, useLeftRing);
|
from, to, useRightRing, useLeftRing);
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -140,13 +139,13 @@ public class ItemSwapContainer
|
||||||
{
|
{
|
||||||
Swaps.Clear();
|
Swaps.Clear();
|
||||||
Loaded = false;
|
Loaded = false;
|
||||||
var ret = EquipmentSwap.CreateTypeSwap(_manager, _identifier.AwaitedService, Swaps, PathResolver(collection), MetaResolver(collection),
|
var ret = EquipmentSwap.CreateTypeSwap(_manager, _identifier, Swaps, PathResolver(collection), MetaResolver(collection),
|
||||||
slotFrom, from, slotTo, to);
|
slotFrom, from, slotTo, to);
|
||||||
Loaded = true;
|
Loaded = true;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, SetId from, SetId to,
|
public bool LoadCustomization(MetaFileManager manager, BodySlot slot, GenderRace race, PrimaryId from, PrimaryId to,
|
||||||
ModCollection? collection = null)
|
ModCollection? collection = null)
|
||||||
{
|
{
|
||||||
var pathResolver = PathResolver(collection);
|
var pathResolver = PathResolver(collection);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.GameData;
|
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -10,10 +9,11 @@ namespace Penumbra.Mods.Manager;
|
||||||
public class ModCacheManager : IDisposable
|
public class ModCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly IdentifierService _identifier;
|
private readonly ObjectIdentification _identifier;
|
||||||
private readonly ModStorage _modManager;
|
private readonly ModStorage _modManager;
|
||||||
|
private bool _updatingItems = false;
|
||||||
|
|
||||||
public ModCacheManager(CommunicatorService communicator, IdentifierService identifier, ModStorage modStorage)
|
public ModCacheManager(CommunicatorService communicator, ObjectIdentification identifier, ModStorage modStorage)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_identifier = identifier;
|
_identifier = identifier;
|
||||||
|
|
@ -23,8 +23,7 @@ public class ModCacheManager : IDisposable
|
||||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModCacheManager);
|
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModCacheManager);
|
||||||
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModCacheManager);
|
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModCacheManager);
|
||||||
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.ModCacheManager);
|
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.ModCacheManager);
|
||||||
if (!identifier.Valid)
|
identifier.Awaiter.ContinueWith(_ => OnIdentifierCreation());
|
||||||
identifier.FinishedCreation += OnIdentifierCreation;
|
|
||||||
OnModDiscoveryFinished();
|
OnModDiscoveryFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +36,7 @@ public class ModCacheManager : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. </summary>
|
/// <summary> Compute the items changed by a given meta manipulation and put them into the changedItems dictionary. </summary>
|
||||||
public static void ComputeChangedItems(IObjectIdentifier identifier, IDictionary<string, object?> changedItems, MetaManipulation manip)
|
public static void ComputeChangedItems(ObjectIdentification identifier, IDictionary<string, object?> changedItems, MetaManipulation manip)
|
||||||
{
|
{
|
||||||
switch (manip.ManipulationType)
|
switch (manip.ManipulationType)
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +139,7 @@ public class ModCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
case ModPathChangeType.Added:
|
case ModPathChangeType.Added:
|
||||||
case ModPathChangeType.Reloaded:
|
case ModPathChangeType.Reloaded:
|
||||||
Refresh(mod);
|
RefreshWithChangedItems(mod);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -152,12 +151,27 @@ public class ModCacheManager : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnModDiscoveryFinished()
|
private void OnModDiscoveryFinished()
|
||||||
=> Parallel.ForEach(_modManager, Refresh);
|
{
|
||||||
|
if (!_identifier.Awaiter.IsCompletedSuccessfully || _updatingItems)
|
||||||
|
{
|
||||||
|
Parallel.ForEach(_modManager, RefreshWithoutChangedItems);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_updatingItems = true;
|
||||||
|
Parallel.ForEach(_modManager, RefreshWithChangedItems);
|
||||||
|
_updatingItems = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnIdentifierCreation()
|
private void OnIdentifierCreation()
|
||||||
{
|
{
|
||||||
|
if (_updatingItems)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_updatingItems = true;
|
||||||
Parallel.ForEach(_modManager, UpdateChangedItems);
|
Parallel.ForEach(_modManager, UpdateChangedItems);
|
||||||
_identifier.FinishedCreation -= OnIdentifierCreation;
|
_updatingItems = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateFileCount(Mod mod)
|
private static void UpdateFileCount(Mod mod)
|
||||||
|
|
@ -179,14 +193,11 @@ public class ModCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
var changedItems = (SortedList<string, object?>)mod.ChangedItems;
|
||||||
changedItems.Clear();
|
changedItems.Clear();
|
||||||
if (!_identifier.Valid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
foreach (var gamePath in mod.AllSubMods.SelectMany(m => m.Files.Keys.Concat(m.FileSwaps.Keys)))
|
||||||
_identifier.AwaitedService.Identify(changedItems, gamePath.ToString());
|
_identifier.Identify(changedItems, gamePath.ToString());
|
||||||
|
|
||||||
foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations))
|
foreach (var manip in mod.AllSubMods.SelectMany(m => m.Manipulations))
|
||||||
ComputeChangedItems(_identifier.AwaitedService, changedItems, manip);
|
ComputeChangedItems(_identifier, changedItems, manip);
|
||||||
|
|
||||||
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant()));
|
||||||
}
|
}
|
||||||
|
|
@ -209,10 +220,16 @@ public class ModCacheManager : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Refresh(Mod mod)
|
private void RefreshWithChangedItems(Mod mod)
|
||||||
{
|
{
|
||||||
UpdateTags(mod);
|
UpdateTags(mod);
|
||||||
UpdateCounts(mod);
|
UpdateCounts(mod);
|
||||||
UpdateChangedItems(mod);
|
UpdateChangedItems(mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshWithoutChangedItems(Mod mod)
|
||||||
|
{
|
||||||
|
UpdateTags(mod);
|
||||||
|
UpdateCounts(mod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public static partial class ModMigration
|
||||||
|
|
||||||
creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true);
|
creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true);
|
||||||
foreach (var (_, index) in mod.Groups.WithIndex())
|
foreach (var (_, index) in mod.Groups.WithIndex())
|
||||||
saveService.ImmediateSave(new ModSaveGroup(mod, index));
|
saveService.ImmediateSave(new ModSaveGroup(mod, index, creator.Config.ReplaceNonAsciiOnImport));
|
||||||
|
|
||||||
// Delete meta files.
|
// Delete meta files.
|
||||||
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
foreach (var file in seenMetaFiles.Where(f => f.Exists))
|
||||||
|
|
@ -111,7 +111,7 @@ public static partial class ModMigration
|
||||||
}
|
}
|
||||||
|
|
||||||
fileVersion = 1;
|
fileVersion = 1;
|
||||||
saveService.ImmediateSave(new ModSaveGroup(mod, -1));
|
saveService.ImmediateSave(new ModSaveGroup(mod, -1, creator.Config.ReplaceNonAsciiOnImport));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,15 @@ public enum ModOptionChangeType
|
||||||
|
|
||||||
public class ModOptionEditor
|
public class ModOptionEditor
|
||||||
{
|
{
|
||||||
|
private readonly Configuration _config;
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly SaveService _saveService;
|
private readonly SaveService _saveService;
|
||||||
|
|
||||||
public ModOptionEditor(CommunicatorService communicator, SaveService saveService)
|
public ModOptionEditor(CommunicatorService communicator, SaveService saveService, Configuration config)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_saveService = saveService;
|
_saveService = saveService;
|
||||||
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
|
/// <summary> Change the type of a group given by mod and index to type, if possible. </summary>
|
||||||
|
|
@ -50,7 +52,7 @@ public class ModOptionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mod.Groups[groupIdx] = group.Convert(type);
|
mod.Groups[groupIdx] = group.Convert(type);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupTypeChanged, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +64,7 @@ public class ModOptionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
group.DefaultSettings = defaultOption;
|
group.DefaultSettings = defaultOption;
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ public class ModOptionEditor
|
||||||
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
if (oldName == newName || !VerifyFileName(mod, group, newName, true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_saveService.ImmediateDelete(new ModSaveGroup(mod, groupIdx));
|
_saveService.ImmediateDelete(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
var _ = group switch
|
var _ = group switch
|
||||||
{
|
{
|
||||||
SingleModGroup s => s.Name = newName,
|
SingleModGroup s => s.Name = newName,
|
||||||
|
|
@ -82,7 +84,7 @@ public class ModOptionEditor
|
||||||
_ => newName,
|
_ => newName,
|
||||||
};
|
};
|
||||||
|
|
||||||
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.ImmediateSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +107,7 @@ public class ModOptionEditor
|
||||||
Name = newName,
|
Name = newName,
|
||||||
Priority = maxPriority,
|
Priority = maxPriority,
|
||||||
});
|
});
|
||||||
_saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1));
|
_saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +131,7 @@ public class ModOptionEditor
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, -1, -1);
|
||||||
mod.Groups.RemoveAt(groupIdx);
|
mod.Groups.RemoveAt(groupIdx);
|
||||||
UpdateSubModPositions(mod, groupIdx);
|
UpdateSubModPositions(mod, groupIdx);
|
||||||
_saveService.SaveAllOptionGroups(mod, false);
|
_saveService.SaveAllOptionGroups(mod, false, _config.ReplaceNonAsciiOnImport);
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,7 +142,7 @@ public class ModOptionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
UpdateSubModPositions(mod, Math.Min(groupIdxFrom, groupIdxTo));
|
||||||
_saveService.SaveAllOptionGroups(mod, false);
|
_saveService.SaveAllOptionGroups(mod, false, _config.ReplaceNonAsciiOnImport);
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, groupIdxFrom, -1, groupIdxTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +159,7 @@ public class ModOptionEditor
|
||||||
MultiModGroup m => m.Description = newDescription,
|
MultiModGroup m => m.Description = newDescription,
|
||||||
_ => newDescription,
|
_ => newDescription,
|
||||||
};
|
};
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +172,7 @@ public class ModOptionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
s.Description = newDescription;
|
s.Description = newDescription;
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +189,7 @@ public class ModOptionEditor
|
||||||
MultiModGroup m => m.Priority = newPriority,
|
MultiModGroup m => m.Priority = newPriority,
|
||||||
_ => newPriority,
|
_ => newPriority,
|
||||||
};
|
};
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, -1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -204,7 +206,7 @@ public class ModOptionEditor
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
m.PrioritizedOptions[optionIdx] = (m.PrioritizedOptions[optionIdx].Mod, newPriority);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, mod, groupIdx, optionIdx, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +232,7 @@ public class ModOptionEditor
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,7 +252,7 @@ public class ModOptionEditor
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,7 +298,7 @@ public class ModOptionEditor
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,7 +319,7 @@ public class ModOptionEditor
|
||||||
}
|
}
|
||||||
|
|
||||||
group.UpdatePositions(optionIdx);
|
group.UpdatePositions(optionIdx);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionDeleted, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -328,7 +330,7 @@ public class ModOptionEditor
|
||||||
if (!group.MoveOption(optionIdxFrom, optionIdxTo))
|
if (!group.MoveOption(optionIdxFrom, optionIdxTo))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, mod, groupIdx, optionIdxFrom, optionIdxTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +344,7 @@ public class ModOptionEditor
|
||||||
|
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
subMod.ManipulationData.SetTo(manipulations);
|
subMod.ManipulationData.SetTo(manipulations);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,7 +357,7 @@ public class ModOptionEditor
|
||||||
|
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
subMod.FileData.SetTo(replacements);
|
subMod.FileData.SetTo(replacements);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,7 +369,7 @@ public class ModOptionEditor
|
||||||
subMod.FileData.AddFrom(additions);
|
subMod.FileData.AddFrom(additions);
|
||||||
if (oldCount != subMod.FileData.Count)
|
if (oldCount != subMod.FileData.Count)
|
||||||
{
|
{
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +383,7 @@ public class ModOptionEditor
|
||||||
|
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
|
||||||
subMod.FileSwapData.SetTo(swaps);
|
subMod.FileSwapData.SetTo(swaps);
|
||||||
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx));
|
_saveService.QueueSave(new ModSaveGroup(mod, groupIdx, _config.ReplaceNonAsciiOnImport));
|
||||||
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
_communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
|
|
@ -16,16 +16,18 @@ using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
public partial class ModCreator(SaveService _saveService, Configuration _config, ModDataEditor _dataEditor, MetaFileManager _metaFileManager,
|
public partial class ModCreator(SaveService _saveService, Configuration config, ModDataEditor _dataEditor, MetaFileManager _metaFileManager,
|
||||||
IGamePathParser _gamePathParser)
|
GamePathParser _gamePathParser)
|
||||||
{
|
{
|
||||||
|
public readonly Configuration Config = config;
|
||||||
|
|
||||||
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
/// <summary> Creates directory and files necessary for a new mod without adding it to the manager. </summary>
|
||||||
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "")
|
public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "")
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newDir = CreateModFolder(basePath, newName, _config.ReplaceNonAsciiOnImport, true);
|
var newDir = CreateModFolder(basePath, newName, Config.ReplaceNonAsciiOnImport, true);
|
||||||
_dataEditor.CreateMeta(newDir, newName, _config.DefaultModAuthor, description, "1.0", string.Empty);
|
_dataEditor.CreateMeta(newDir, newName, Config.DefaultModAuthor, description, "1.0", string.Empty);
|
||||||
CreateDefaultFiles(newDir);
|
CreateDefaultFiles(newDir);
|
||||||
return newDir;
|
return newDir;
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +88,8 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
if (group != null && mod.Groups.All(g => g.Name != group.Name))
|
if (group != null && mod.Groups.All(g => g.Name != group.Name))
|
||||||
{
|
{
|
||||||
changes = changes
|
changes = changes
|
||||||
|| _saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name) != file.FullName;
|
|| _saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name, true)
|
||||||
|
!= Path.Combine(file.DirectoryName!, ReplaceBadXivSymbols(file.Name, true));
|
||||||
mod.Groups.Add(group);
|
mod.Groups.Add(group);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -96,13 +99,13 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes)
|
if (changes)
|
||||||
_saveService.SaveAllOptionGroups(mod, true);
|
_saveService.SaveAllOptionGroups(mod, true, Config.ReplaceNonAsciiOnImport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Load the default option for a given mod.</summary>
|
/// <summary> Load the default option for a given mod.</summary>
|
||||||
public void LoadDefaultOption(Mod mod)
|
public void LoadDefaultOption(Mod mod)
|
||||||
{
|
{
|
||||||
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1);
|
var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport);
|
||||||
mod.Default.SetPosition(-1, 0);
|
mod.Default.SetPosition(-1, 0);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -161,8 +164,8 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
if (!changes)
|
if (!changes)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_saveService.SaveAllOptionGroups(mod, false);
|
_saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport);
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -188,7 +191,7 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var meta = new TexToolsMeta(_metaFileManager, _gamePathParser, File.ReadAllBytes(file.FullName),
|
var meta = new TexToolsMeta(_metaFileManager, _gamePathParser, File.ReadAllBytes(file.FullName),
|
||||||
_config.KeepDefaultMetaChanges);
|
Config.KeepDefaultMetaChanges);
|
||||||
Penumbra.Log.Verbose(
|
Penumbra.Log.Verbose(
|
||||||
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
$"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}");
|
||||||
deleteList.Add(file.FullName);
|
deleteList.Add(file.FullName);
|
||||||
|
|
@ -201,7 +204,7 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rgsp = TexToolsMeta.FromRgspFile(_metaFileManager, file.FullName, File.ReadAllBytes(file.FullName),
|
var rgsp = TexToolsMeta.FromRgspFile(_metaFileManager, file.FullName, File.ReadAllBytes(file.FullName),
|
||||||
_config.KeepDefaultMetaChanges);
|
Config.KeepDefaultMetaChanges);
|
||||||
Penumbra.Log.Verbose(
|
Penumbra.Log.Verbose(
|
||||||
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
$"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}");
|
||||||
deleteList.Add(file.FullName);
|
deleteList.Add(file.FullName);
|
||||||
|
|
@ -246,7 +249,7 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
DefaultSettings = defaultSettings,
|
DefaultSettings = defaultSettings,
|
||||||
};
|
};
|
||||||
group.PrioritizedOptions.AddRange(subMods.OfType<SubMod>().Select((s, idx) => (s, idx)));
|
group.PrioritizedOptions.AddRange(subMods.OfType<SubMod>().Select((s, idx) => (s, idx)));
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupType.Single:
|
case GroupType.Single:
|
||||||
|
|
@ -259,7 +262,7 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
DefaultSettings = defaultSettings,
|
DefaultSettings = defaultSettings,
|
||||||
};
|
};
|
||||||
group.OptionData.AddRange(subMods.OfType<SubMod>());
|
group.OptionData.AddRange(subMods.OfType<SubMod>());
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(baseFolder, group, index, Config.ReplaceNonAsciiOnImport));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +309,7 @@ public partial class ModCreator(SaveService _saveService, Configuration _config,
|
||||||
}
|
}
|
||||||
|
|
||||||
IncorporateMetaChanges(mod.Default, directory, true);
|
IncorporateMetaChanges(mod.Default, directory, true);
|
||||||
_saveService.ImmediateSaveSync(new ModSaveGroup(mod, -1));
|
_saveService.ImmediateSaveSync(new ModSaveGroup(mod, -1, Config.ReplaceNonAsciiOnImport));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
|
/// <summary> Return the name of a new valid directory based on the base directory and the given name. </summary>
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ public readonly struct ModSaveGroup : ISavable
|
||||||
private readonly IModGroup? _group;
|
private readonly IModGroup? _group;
|
||||||
private readonly int _groupIdx;
|
private readonly int _groupIdx;
|
||||||
private readonly ISubMod? _defaultMod;
|
private readonly ISubMod? _defaultMod;
|
||||||
|
private readonly bool _onlyAscii;
|
||||||
|
|
||||||
public ModSaveGroup(Mod mod, int groupIdx)
|
public ModSaveGroup(Mod mod, int groupIdx, bool onlyAscii)
|
||||||
{
|
{
|
||||||
_basePath = mod.ModPath;
|
_basePath = mod.ModPath;
|
||||||
_groupIdx = groupIdx;
|
_groupIdx = groupIdx;
|
||||||
|
|
@ -48,24 +49,27 @@ public readonly struct ModSaveGroup : ISavable
|
||||||
_defaultMod = mod.Default;
|
_defaultMod = mod.Default;
|
||||||
else
|
else
|
||||||
_group = mod.Groups[_groupIdx];
|
_group = mod.Groups[_groupIdx];
|
||||||
|
_onlyAscii = onlyAscii;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModSaveGroup(DirectoryInfo basePath, IModGroup group, int groupIdx)
|
public ModSaveGroup(DirectoryInfo basePath, IModGroup group, int groupIdx, bool onlyAscii)
|
||||||
{
|
{
|
||||||
_basePath = basePath;
|
_basePath = basePath;
|
||||||
_group = group;
|
_group = group;
|
||||||
_groupIdx = groupIdx;
|
_groupIdx = groupIdx;
|
||||||
|
_onlyAscii = onlyAscii;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ModSaveGroup(DirectoryInfo basePath, ISubMod @default)
|
public ModSaveGroup(DirectoryInfo basePath, ISubMod @default, bool onlyAscii)
|
||||||
{
|
{
|
||||||
_basePath = basePath;
|
_basePath = basePath;
|
||||||
_groupIdx = -1;
|
_groupIdx = -1;
|
||||||
_defaultMod = @default;
|
_defaultMod = @default;
|
||||||
|
_onlyAscii = onlyAscii;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ToFilename(FilenameService fileNames)
|
public string ToFilename(FilenameService fileNames)
|
||||||
=> fileNames.OptionGroupFile(_basePath.FullName, _groupIdx, _group?.Name ?? string.Empty);
|
=> fileNames.OptionGroupFile(_basePath.FullName, _groupIdx, _group?.Name ?? string.Empty, _onlyAscii);
|
||||||
|
|
||||||
public void Save(StreamWriter writer)
|
public void Save(StreamWriter writer)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,9 @@ public sealed class SubMod : ISubMod
|
||||||
public bool IsDefault
|
public bool IsDefault
|
||||||
=> GroupIdx < 0;
|
=> GroupIdx < 0;
|
||||||
|
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileData = new();
|
public Dictionary<Utf8GamePath, FullPath> FileData = [];
|
||||||
public Dictionary<Utf8GamePath, FullPath> FileSwapData = new();
|
public Dictionary<Utf8GamePath, FullPath> FileSwapData = [];
|
||||||
public HashSet<MetaManipulation> ManipulationData = new();
|
public HashSet<MetaManipulation> ManipulationData = [];
|
||||||
|
|
||||||
public SubMod(IMod parentMod)
|
public SubMod(IMod parentMod)
|
||||||
=> ParentMod = parentMod;
|
=> ParentMod = parentMod;
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ public class TemporaryMod : IMod
|
||||||
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
|
foreach (var manip in collection.MetaCache?.Manipulations ?? Array.Empty<MetaManipulation>())
|
||||||
defaultMod.ManipulationData.Add(manip);
|
defaultMod.ManipulationData.Add(manip);
|
||||||
|
|
||||||
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod));
|
saveService.ImmediateSave(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport));
|
||||||
modManager.AddMod(dir);
|
modManager.AddMod(dir);
|
||||||
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}.");
|
Penumbra.Log.Information($"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Name}.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using OtterGui.Services;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Util;
|
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Cache;
|
using Penumbra.Collections.Cache;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.ResourceLoading;
|
||||||
|
|
@ -47,45 +46,40 @@ public class Penumbra : IDalamudPlugin
|
||||||
private PenumbraWindowSystem? _windowSystem;
|
private PenumbraWindowSystem? _windowSystem;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly ServiceProvider _services;
|
private readonly ServiceManager _services;
|
||||||
|
|
||||||
public Penumbra(DalamudPluginInterface pluginInterface)
|
public Penumbra(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var startTimer = new StartTracker();
|
_services = ServiceManagerA.CreateProvider(this, pluginInterface, Log);
|
||||||
using var timer = startTimer.Measure(StartTimeType.Total);
|
Messager = _services.GetService<MessageService>();
|
||||||
_services = ServiceManager.CreateProvider(this, pluginInterface, Log, startTimer);
|
_validityChecker = _services.GetService<ValidityChecker>();
|
||||||
Messager = _services.GetRequiredService<MessageService>();
|
var startup = _services.GetService<DalamudConfigService>().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool s)
|
||||||
_validityChecker = _services.GetRequiredService<ValidityChecker>();
|
|
||||||
var startup = _services.GetRequiredService<DalamudServices>().GetDalamudConfig(DalamudServices.WaitingForPluginsOption, out bool s)
|
|
||||||
? s.ToString()
|
? s.ToString()
|
||||||
: "Unknown";
|
: "Unknown";
|
||||||
Log.Information(
|
Log.Information(
|
||||||
$"Loading Penumbra Version {_validityChecker.Version}, Commit #{_validityChecker.CommitHash} with Waiting For Plugins: {startup}...");
|
$"Loading Penumbra Version {_validityChecker.Version}, Commit #{_validityChecker.CommitHash} with Waiting For Plugins: {startup}...");
|
||||||
_services.GetRequiredService<BackupService>(); // Initialize because not required anywhere else.
|
_services.GetService<BackupService>(); // Initialize because not required anywhere else.
|
||||||
_config = _services.GetRequiredService<Configuration>();
|
_config = _services.GetService<Configuration>();
|
||||||
_characterUtility = _services.GetRequiredService<CharacterUtility>();
|
_characterUtility = _services.GetService<CharacterUtility>();
|
||||||
_tempMods = _services.GetRequiredService<TempModManager>();
|
_tempMods = _services.GetService<TempModManager>();
|
||||||
_residentResources = _services.GetRequiredService<ResidentResourceManager>();
|
_residentResources = _services.GetService<ResidentResourceManager>();
|
||||||
_services.GetRequiredService<ResourceManagerService>(); // Initialize because not required anywhere else.
|
_services.GetService<ResourceManagerService>(); // Initialize because not required anywhere else.
|
||||||
_modManager = _services.GetRequiredService<ModManager>();
|
_modManager = _services.GetService<ModManager>();
|
||||||
_collectionManager = _services.GetRequiredService<CollectionManager>();
|
_collectionManager = _services.GetService<CollectionManager>();
|
||||||
_tempCollections = _services.GetRequiredService<TempCollectionManager>();
|
_tempCollections = _services.GetService<TempCollectionManager>();
|
||||||
_redrawService = _services.GetRequiredService<RedrawService>();
|
_redrawService = _services.GetService<RedrawService>();
|
||||||
_communicatorService = _services.GetRequiredService<CommunicatorService>();
|
_communicatorService = _services.GetService<CommunicatorService>();
|
||||||
_services.GetRequiredService<ResourceService>(); // Initialize because not required anywhere else.
|
_services.GetService<ResourceService>(); // Initialize because not required anywhere else.
|
||||||
_services.GetRequiredService<ModCacheManager>(); // Initialize because not required anywhere else.
|
_services.GetService<ModCacheManager>(); // Initialize because not required anywhere else.
|
||||||
_services.GetRequiredService<ModelResourceHandleUtility>(); // Initialize because not required anywhere else.
|
_services.GetService<ModelResourceHandleUtility>(); // Initialize because not required anywhere else.
|
||||||
_collectionManager.Caches.CreateNecessaryCaches();
|
_collectionManager.Caches.CreateNecessaryCaches();
|
||||||
using (var t = _services.GetRequiredService<StartTracker>().Measure(StartTimeType.PathResolver))
|
_services.GetService<PathResolver>();
|
||||||
{
|
|
||||||
_services.GetRequiredService<PathResolver>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_services.GetRequiredService<SkinFixer>();
|
_services.GetService<SkinFixer>();
|
||||||
|
|
||||||
_services.GetRequiredService<DalamudSubstitutionProvider>(); // Initialize before Interface.
|
_services.GetService<DalamudSubstitutionProvider>(); // Initialize before Interface.
|
||||||
SetupInterface();
|
SetupInterface();
|
||||||
SetupApi();
|
SetupApi();
|
||||||
|
|
||||||
|
|
@ -108,9 +102,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
|
|
||||||
private void SetupApi()
|
private void SetupApi()
|
||||||
{
|
{
|
||||||
using var timer = _services.GetRequiredService<StartTracker>().Measure(StartTimeType.Api);
|
var api = _services.GetService<IPenumbraApi>();
|
||||||
var api = _services.GetRequiredService<IPenumbraApi>();
|
_services.GetService<PenumbraIpcProviders>();
|
||||||
_services.GetRequiredService<PenumbraIpcProviders>();
|
|
||||||
_communicatorService.ChangedItemHover.Subscribe(it =>
|
_communicatorService.ChangedItemHover.Subscribe(it =>
|
||||||
{
|
{
|
||||||
if (it is (Item, FullEquipType))
|
if (it is (Item, FullEquipType))
|
||||||
|
|
@ -128,10 +121,9 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
AsyncTask.Run(() =>
|
AsyncTask.Run(() =>
|
||||||
{
|
{
|
||||||
using var tInterface = _services.GetRequiredService<StartTracker>().Measure(StartTimeType.Interface);
|
var system = _services.GetService<PenumbraWindowSystem>();
|
||||||
var system = _services.GetRequiredService<PenumbraWindowSystem>();
|
system.Window.Setup(this, _services.GetService<ConfigTabBar>());
|
||||||
system.Window.Setup(this, _services.GetRequiredService<ConfigTabBar>());
|
_services.GetService<CommandHandler>();
|
||||||
_services.GetRequiredService<CommandHandler>();
|
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
_windowSystem = system;
|
_windowSystem = system;
|
||||||
else
|
else
|
||||||
|
|
@ -200,7 +192,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
sb.Append($"> **`Auto-Deduplication: `** {_config.AutoDeduplicateOnImport}\n");
|
sb.Append($"> **`Auto-Deduplication: `** {_config.AutoDeduplicateOnImport}\n");
|
||||||
sb.Append($"> **`Debug Mode: `** {_config.DebugMode}\n");
|
sb.Append($"> **`Debug Mode: `** {_config.DebugMode}\n");
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"> **`Synchronous Load (Dalamud): `** {(_services.GetRequiredService<DalamudServices>().GetDalamudConfig(DalamudServices.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n");
|
$"> **`Synchronous Load (Dalamud): `** {(_services.GetService<DalamudConfigService>().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n");
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"> **`Logging: `** Log: {_config.Ephemeral.EnableResourceLogging}, Watcher: {_config.Ephemeral.EnableResourceWatcher} ({_config.MaxResourceWatcherRecords})\n");
|
$"> **`Logging: `** Log: {_config.Ephemeral.EnableResourceLogging}, Watcher: {_config.Ephemeral.EnableResourceWatcher} ({_config.MaxResourceWatcherRecords})\n");
|
||||||
sb.Append($"> **`Use Ownership: `** {_config.UseOwnerNameForCharacterCollection}\n");
|
sb.Append($"> **`Use Ownership: `** {_config.UseOwnerNameForCharacterCollection}\n");
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,9 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low">
|
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
|
||||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" />
|
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
|
||||||
|
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
|
||||||
</Exec>
|
</Exec>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"Tags": [ "modding" ],
|
"Tags": [ "modding" ],
|
||||||
"DalamudApiLevel": 9,
|
"DalamudApiLevel": 9,
|
||||||
"LoadPriority": 69420,
|
"LoadPriority": 69420,
|
||||||
"LoadRequiredState": 2,
|
"LoadState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
public class BackupService
|
public class BackupService
|
||||||
{
|
{
|
||||||
public BackupService(Logger logger, StartTracker timer, FilenameService fileNames)
|
public BackupService(Logger logger, FilenameService fileNames)
|
||||||
{
|
{
|
||||||
using var t = timer.Measure(StartTimeType.Backup);
|
|
||||||
var files = PenumbraFiles(fileNames);
|
var files = PenumbraFiles(fileNames);
|
||||||
Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
|
Backup.CreateAutomaticBackup(logger, new DirectoryInfo(fileNames.ConfigDirectory), files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CreatePermanentBackup(FilenameService fileNames)
|
/// <summary> Collect all relevant files for penumbra configuration. </summary>
|
||||||
=> Backup.CreatePermanentBackup(Penumbra.Log, new DirectoryInfo(fileNames.ConfigDirectory), PenumbraFiles(fileNames),
|
|
||||||
"pre_ephemeral_config");
|
|
||||||
|
|
||||||
// Collect all relevant files for penumbra configuration.
|
|
||||||
private static IReadOnlyList<FileInfo> PenumbraFiles(FilenameService fileNames)
|
private static IReadOnlyList<FileInfo> PenumbraFiles(FilenameService fileNames)
|
||||||
{
|
{
|
||||||
var list = fileNames.CollectionFiles.ToList();
|
var list = fileNames.CollectionFiles.ToList();
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,15 @@ using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Interface.DragDrop;
|
using Dalamud.Interface.DragDrop;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using OtterGui.Services;
|
||||||
|
|
||||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
public class DalamudServices
|
public class DalamudConfigService
|
||||||
{
|
{
|
||||||
public DalamudServices(DalamudPluginInterface pluginInterface)
|
public DalamudConfigService(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
pluginInterface.Inject(this);
|
pluginInterface.Inject(this);
|
||||||
try
|
try
|
||||||
|
|
@ -52,55 +52,6 @@ public class DalamudServices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddSingleton(PluginInterface);
|
|
||||||
services.AddSingleton(Commands);
|
|
||||||
services.AddSingleton(GameData);
|
|
||||||
services.AddSingleton(ClientState);
|
|
||||||
services.AddSingleton(Chat);
|
|
||||||
services.AddSingleton(Framework);
|
|
||||||
services.AddSingleton(Conditions);
|
|
||||||
services.AddSingleton(Targets);
|
|
||||||
services.AddSingleton(Objects);
|
|
||||||
services.AddSingleton(TitleScreenMenu);
|
|
||||||
services.AddSingleton(GameGui);
|
|
||||||
services.AddSingleton(KeyState);
|
|
||||||
services.AddSingleton(SigScanner);
|
|
||||||
services.AddSingleton(this);
|
|
||||||
services.AddSingleton(UiBuilder);
|
|
||||||
services.AddSingleton(DragDropManager);
|
|
||||||
services.AddSingleton(TextureProvider);
|
|
||||||
services.AddSingleton(TextureSubstitutionProvider);
|
|
||||||
services.AddSingleton(Interop);
|
|
||||||
services.AddSingleton(Log);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove static
|
|
||||||
// @formatter:off
|
|
||||||
[PluginService][RequiredVersion("1.0")] public DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ICommandManager Commands { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IDataManager GameData { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IClientState ClientState { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IChatGui Chat { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IFramework Framework { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ICondition Conditions { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ITargetManager Targets { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IObjectTable Objects { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ITitleScreenMenu TitleScreenMenu { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IGameGui GameGui { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IKeyState KeyState { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ISigScanner SigScanner { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IDragDropManager DragDropManager { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ITextureProvider TextureProvider { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public ITextureSubstitutionProvider TextureSubstitutionProvider { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IGameInteropProvider Interop { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public IPluginLog Log { get; private set; } = null!;
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
public UiBuilder UiBuilder
|
|
||||||
=> PluginInterface.UiBuilder;
|
|
||||||
|
|
||||||
public const string WaitingForPluginsOption = "IsResumeGameAfterPluginLoad";
|
public const string WaitingForPluginsOption = "IsResumeGameAfterPluginLoad";
|
||||||
|
|
||||||
private readonly object? _dalamudConfig;
|
private readonly object? _dalamudConfig;
|
||||||
|
|
@ -164,3 +115,29 @@ public class DalamudServices
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DalamudServices
|
||||||
|
{
|
||||||
|
public static void AddServices(ServiceManager services, DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
services.AddExistingService(pi);
|
||||||
|
services.AddExistingService(pi.UiBuilder);
|
||||||
|
services.AddDalamudService<ICommandManager>(pi);
|
||||||
|
services.AddDalamudService<IDataManager>(pi);
|
||||||
|
services.AddDalamudService<IClientState>(pi);
|
||||||
|
services.AddDalamudService<IChatGui>(pi);
|
||||||
|
services.AddDalamudService<IFramework>(pi);
|
||||||
|
services.AddDalamudService<ICondition>(pi);
|
||||||
|
services.AddDalamudService<ITargetManager>(pi);
|
||||||
|
services.AddDalamudService<IObjectTable>(pi);
|
||||||
|
services.AddDalamudService<ITitleScreenMenu>(pi);
|
||||||
|
services.AddDalamudService<IGameGui>(pi);
|
||||||
|
services.AddDalamudService<IKeyState>(pi);
|
||||||
|
services.AddDalamudService<ISigScanner>(pi);
|
||||||
|
services.AddDalamudService<IDragDropManager>(pi);
|
||||||
|
services.AddDalamudService<ITextureProvider>(pi);
|
||||||
|
services.AddDalamudService<ITextureSubstitutionProvider>(pi);
|
||||||
|
services.AddDalamudService<IGameInteropProvider>(pi);
|
||||||
|
services.AddDalamudService<IPluginLog>(pi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,14 @@ public class FilenameService(DalamudPluginInterface pi)
|
||||||
=> Path.Combine(modDirectory, "meta.json");
|
=> Path.Combine(modDirectory, "meta.json");
|
||||||
|
|
||||||
/// <summary> Obtain the path of the file describing a given option group by its index and the mod. If the index is < 0, return the path for the default mod file. </summary>
|
/// <summary> Obtain the path of the file describing a given option group by its index and the mod. If the index is < 0, return the path for the default mod file. </summary>
|
||||||
public string OptionGroupFile(Mod mod, int index)
|
public string OptionGroupFile(Mod mod, int index, bool onlyAscii)
|
||||||
=> OptionGroupFile(mod.ModPath.FullName, index, index >= 0 ? mod.Groups[index].Name : string.Empty);
|
=> OptionGroupFile(mod.ModPath.FullName, index, index >= 0 ? mod.Groups[index].Name : string.Empty, onlyAscii);
|
||||||
|
|
||||||
/// <summary> Obtain the path of the file describing a given option group by its index, name and basepath. If the index is < 0, return the path for the default mod file. </summary>
|
/// <summary> Obtain the path of the file describing a given option group by its index, name and basepath. If the index is < 0, return the path for the default mod file. </summary>
|
||||||
public string OptionGroupFile(string basePath, int index, string name)
|
public string OptionGroupFile(string basePath, int index, string name, bool onlyAscii)
|
||||||
{
|
{
|
||||||
var fileName = index >= 0
|
var fileName = index >= 0
|
||||||
? $"group_{index + 1:D3}_{name.RemoveInvalidPathSymbols().ToLowerInvariant()}.json"
|
? $"group_{index + 1:D3}_{ModCreator.ReplaceBadXivSymbols(name.ToLowerInvariant(), onlyAscii)}.json"
|
||||||
: "default_mod.json";
|
: "default_mod.json";
|
||||||
return Path.Combine(basePath, fileName);
|
return Path.Combine(basePath, fileName);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public sealed class SaveService : SaveServiceBase<FilenameService>
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary> Immediately delete all existing option group files for a mod and save them anew. </summary>
|
/// <summary> Immediately delete all existing option group files for a mod and save them anew. </summary>
|
||||||
public void SaveAllOptionGroups(Mod mod, bool backup)
|
public void SaveAllOptionGroups(Mod mod, bool backup, bool onlyAscii)
|
||||||
{
|
{
|
||||||
foreach (var file in FileNames.GetOptionGroupFiles(mod))
|
foreach (var file in FileNames.GetOptionGroupFiles(mod))
|
||||||
{
|
{
|
||||||
|
|
@ -37,7 +37,7 @@ public sealed class SaveService : SaveServiceBase<FilenameService>
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < mod.Groups.Count - 1; ++i)
|
for (var i = 0; i < mod.Groups.Count - 1; ++i)
|
||||||
ImmediateSave(new ModSaveGroup(mod, i));
|
ImmediateSave(new ModSaveGroup(mod, i, onlyAscii));
|
||||||
ImmediateSaveSync(new ModSaveGroup(mod, mod.Groups.Count - 1));
|
ImmediateSaveSync(new ModSaveGroup(mod, mod.Groups.Count - 1, onlyAscii));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Compression;
|
using OtterGui.Compression;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
|
using OtterGui.Services;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Collections.Cache;
|
using Penumbra.Collections.Cache;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Import.Models;
|
using Penumbra.Import.Models;
|
||||||
|
using Penumbra.GameData.DataContainers;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Import.Textures;
|
using Penumbra.Import.Textures;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.ResourceLoading;
|
||||||
|
|
@ -25,19 +28,18 @@ using Penumbra.UI.Classes;
|
||||||
using Penumbra.UI.ModsTab;
|
using Penumbra.UI.ModsTab;
|
||||||
using Penumbra.UI.ResourceWatcher;
|
using Penumbra.UI.ResourceWatcher;
|
||||||
using Penumbra.UI.Tabs;
|
using Penumbra.UI.Tabs;
|
||||||
|
using Penumbra.UI.Tabs.Debug;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
public static class ServiceManager
|
public static class ServiceManagerA
|
||||||
{
|
{
|
||||||
public static ServiceProvider CreateProvider(Penumbra penumbra, DalamudPluginInterface pi, Logger log, StartTracker startTimer)
|
public static ServiceManager CreateProvider(Penumbra penumbra, DalamudPluginInterface pi, Logger log)
|
||||||
{
|
{
|
||||||
var services = new ServiceCollection()
|
var services = new ServiceManager(log)
|
||||||
.AddSingleton(log)
|
.AddExistingService(log)
|
||||||
.AddSingleton(startTimer)
|
.AddExistingService(penumbra)
|
||||||
.AddSingleton(penumbra)
|
|
||||||
.AddDalamud(pi)
|
|
||||||
.AddMeta()
|
.AddMeta()
|
||||||
.AddGameData()
|
.AddGameData()
|
||||||
.AddInterop()
|
.AddInterop()
|
||||||
|
|
@ -49,18 +51,14 @@ public static class ServiceManager
|
||||||
.AddInterface()
|
.AddInterface()
|
||||||
.AddModEditor()
|
.AddModEditor()
|
||||||
.AddApi();
|
.AddApi();
|
||||||
|
services.AddIServices(typeof(EquipItem).Assembly);
|
||||||
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateOnBuild = true });
|
services.AddIServices(typeof(Penumbra).Assembly);
|
||||||
}
|
DalamudServices.AddServices(services, pi);
|
||||||
|
services.CreateProvider();
|
||||||
private static IServiceCollection AddDalamud(this IServiceCollection services, DalamudPluginInterface pi)
|
|
||||||
{
|
|
||||||
var dalamud = new DalamudServices(pi);
|
|
||||||
dalamud.AddServices(services);
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IServiceCollection AddMeta(this IServiceCollection services)
|
private static ServiceManager AddMeta(this ServiceManager services)
|
||||||
=> services.AddSingleton<ValidityChecker>()
|
=> services.AddSingleton<ValidityChecker>()
|
||||||
.AddSingleton<PerformanceTracker>()
|
.AddSingleton<PerformanceTracker>()
|
||||||
.AddSingleton<FilenameService>()
|
.AddSingleton<FilenameService>()
|
||||||
|
|
@ -68,21 +66,24 @@ public static class ServiceManager
|
||||||
.AddSingleton<CommunicatorService>()
|
.AddSingleton<CommunicatorService>()
|
||||||
.AddSingleton<MessageService>()
|
.AddSingleton<MessageService>()
|
||||||
.AddSingleton<SaveService>()
|
.AddSingleton<SaveService>()
|
||||||
.AddSingleton<FileCompactor>();
|
.AddSingleton<FileCompactor>()
|
||||||
|
.AddSingleton<DalamudConfigService>();
|
||||||
|
|
||||||
|
|
||||||
private static IServiceCollection AddGameData(this IServiceCollection services)
|
private static ServiceManager AddGameData(this ServiceManager services)
|
||||||
=> services.AddSingleton<IGamePathParser, GamePathParser>()
|
=> services.AddSingleton<GamePathParser>()
|
||||||
.AddSingleton<IdentifierService>()
|
|
||||||
.AddSingleton<StainService>()
|
.AddSingleton<StainService>()
|
||||||
.AddSingleton<ItemService>()
|
|
||||||
.AddSingleton<ActorService>()
|
|
||||||
.AddSingleton<HumanModelList>();
|
.AddSingleton<HumanModelList>();
|
||||||
|
|
||||||
private static IServiceCollection AddInterop(this IServiceCollection services)
|
private static ServiceManager AddInterop(this ServiceManager services)
|
||||||
=> services.AddSingleton<GameEventManager>()
|
=> services.AddSingleton<GameEventManager>()
|
||||||
.AddSingleton<FrameworkManager>()
|
.AddSingleton<FrameworkManager>()
|
||||||
.AddSingleton<CutsceneService>()
|
.AddSingleton<CutsceneService>()
|
||||||
|
.AddSingleton(p =>
|
||||||
|
{
|
||||||
|
var cutsceneService = p.GetRequiredService<CutsceneService>();
|
||||||
|
return new CutsceneResolver(cutsceneService.GetParentIndex);
|
||||||
|
})
|
||||||
.AddSingleton<CharacterUtility>()
|
.AddSingleton<CharacterUtility>()
|
||||||
.AddSingleton<ResourceManagerService>()
|
.AddSingleton<ResourceManagerService>()
|
||||||
.AddSingleton<ResourceService>()
|
.AddSingleton<ResourceService>()
|
||||||
|
|
@ -94,12 +95,12 @@ public static class ServiceManager
|
||||||
.AddSingleton<RedrawService>()
|
.AddSingleton<RedrawService>()
|
||||||
.AddSingleton<ModelResourceHandleUtility>();
|
.AddSingleton<ModelResourceHandleUtility>();
|
||||||
|
|
||||||
private static IServiceCollection AddConfiguration(this IServiceCollection services)
|
private static ServiceManager AddConfiguration(this ServiceManager services)
|
||||||
=> services.AddTransient<ConfigMigrationService>()
|
=> services.AddSingleton<ConfigMigrationService>()
|
||||||
.AddSingleton<Configuration>()
|
.AddSingleton<Configuration>()
|
||||||
.AddSingleton<EphemeralConfig>();
|
.AddSingleton<EphemeralConfig>();
|
||||||
|
|
||||||
private static IServiceCollection AddCollections(this IServiceCollection services)
|
private static ServiceManager AddCollections(this ServiceManager services)
|
||||||
=> services.AddSingleton<CollectionStorage>()
|
=> services.AddSingleton<CollectionStorage>()
|
||||||
.AddSingleton<ActiveCollectionData>()
|
.AddSingleton<ActiveCollectionData>()
|
||||||
.AddSingleton<ActiveCollections>()
|
.AddSingleton<ActiveCollections>()
|
||||||
|
|
@ -109,7 +110,7 @@ public static class ServiceManager
|
||||||
.AddSingleton<CollectionEditor>()
|
.AddSingleton<CollectionEditor>()
|
||||||
.AddSingleton<CollectionManager>();
|
.AddSingleton<CollectionManager>();
|
||||||
|
|
||||||
private static IServiceCollection AddMods(this IServiceCollection services)
|
private static ServiceManager AddMods(this ServiceManager services)
|
||||||
=> services.AddSingleton<TempModManager>()
|
=> services.AddSingleton<TempModManager>()
|
||||||
.AddSingleton<ModDataEditor>()
|
.AddSingleton<ModDataEditor>()
|
||||||
.AddSingleton<ModOptionEditor>()
|
.AddSingleton<ModOptionEditor>()
|
||||||
|
|
@ -121,14 +122,14 @@ public static class ServiceManager
|
||||||
.AddSingleton<ModCacheManager>()
|
.AddSingleton<ModCacheManager>()
|
||||||
.AddSingleton(s => (ModStorage)s.GetRequiredService<ModManager>());
|
.AddSingleton(s => (ModStorage)s.GetRequiredService<ModManager>());
|
||||||
|
|
||||||
private static IServiceCollection AddResources(this IServiceCollection services)
|
private static ServiceManager AddResources(this ServiceManager services)
|
||||||
=> services.AddSingleton<ResourceLoader>()
|
=> services.AddSingleton<ResourceLoader>()
|
||||||
.AddSingleton<ResourceWatcher>()
|
.AddSingleton<ResourceWatcher>()
|
||||||
.AddSingleton<ResourceTreeFactory>()
|
.AddSingleton<ResourceTreeFactory>()
|
||||||
.AddSingleton<MetaFileManager>()
|
.AddSingleton<MetaFileManager>()
|
||||||
.AddSingleton<SkinFixer>();
|
.AddSingleton<SkinFixer>();
|
||||||
|
|
||||||
private static IServiceCollection AddResolvers(this IServiceCollection services)
|
private static ServiceManager AddResolvers(this ServiceManager services)
|
||||||
=> services.AddSingleton<AnimationHookService>()
|
=> services.AddSingleton<AnimationHookService>()
|
||||||
.AddSingleton<CollectionResolver>()
|
.AddSingleton<CollectionResolver>()
|
||||||
.AddSingleton<CutsceneService>()
|
.AddSingleton<CutsceneService>()
|
||||||
|
|
@ -139,7 +140,7 @@ public static class ServiceManager
|
||||||
.AddSingleton<IdentifiedCollectionCache>()
|
.AddSingleton<IdentifiedCollectionCache>()
|
||||||
.AddSingleton<PathResolver>();
|
.AddSingleton<PathResolver>();
|
||||||
|
|
||||||
private static IServiceCollection AddInterface(this IServiceCollection services)
|
private static ServiceManager AddInterface(this ServiceManager services)
|
||||||
=> services.AddSingleton<FileDialogService>()
|
=> services.AddSingleton<FileDialogService>()
|
||||||
.AddSingleton<TutorialService>()
|
.AddSingleton<TutorialService>()
|
||||||
.AddSingleton<PenumbraChangelog>()
|
.AddSingleton<PenumbraChangelog>()
|
||||||
|
|
@ -174,9 +175,10 @@ public static class ServiceManager
|
||||||
.AddSingleton<ResourceWatcher>()
|
.AddSingleton<ResourceWatcher>()
|
||||||
.AddSingleton<ItemSwapTab>()
|
.AddSingleton<ItemSwapTab>()
|
||||||
.AddSingleton<ModMergeTab>()
|
.AddSingleton<ModMergeTab>()
|
||||||
.AddSingleton<ChangedItemDrawer>();
|
.AddSingleton<ChangedItemDrawer>()
|
||||||
|
.AddSingleton(p => new Diagnostics(p));
|
||||||
|
|
||||||
private static IServiceCollection AddModEditor(this IServiceCollection services)
|
private static ServiceManager AddModEditor(this ServiceManager services)
|
||||||
=> services.AddSingleton<ModFileCollection>()
|
=> services.AddSingleton<ModFileCollection>()
|
||||||
.AddSingleton<DuplicateManager>()
|
.AddSingleton<DuplicateManager>()
|
||||||
.AddSingleton<MdlMaterialEditor>()
|
.AddSingleton<MdlMaterialEditor>()
|
||||||
|
|
@ -189,10 +191,11 @@ public static class ServiceManager
|
||||||
.AddSingleton<TextureManager>()
|
.AddSingleton<TextureManager>()
|
||||||
.AddSingleton<ModelManager>();
|
.AddSingleton<ModelManager>();
|
||||||
|
|
||||||
private static IServiceCollection AddApi(this IServiceCollection services)
|
private static ServiceManager AddApi(this ServiceManager services)
|
||||||
=> services.AddSingleton<PenumbraApi>()
|
=> services.AddSingleton<PenumbraApi>()
|
||||||
.AddSingleton<IPenumbraApi>(x => x.GetRequiredService<PenumbraApi>())
|
.AddSingleton<IPenumbraApi>(x => x.GetRequiredService<PenumbraApi>())
|
||||||
.AddSingleton<PenumbraIpcProviders>()
|
.AddSingleton<PenumbraIpcProviders>()
|
||||||
.AddSingleton<HttpApi>()
|
.AddSingleton<HttpApi>()
|
||||||
|
.AddSingleton<IpcTester>()
|
||||||
.AddSingleton<DalamudSubstitutionProvider>();
|
.AddSingleton<DalamudSubstitutionProvider>();
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
namespace Penumbra.Services;
|
||||||
|
|
||||||
|
|
@ -12,10 +11,9 @@ public abstract class SyncServiceWrapper<T> : IDisposable
|
||||||
public bool Valid
|
public bool Valid
|
||||||
=> !_isDisposed;
|
=> !_isDisposed;
|
||||||
|
|
||||||
protected SyncServiceWrapper(string name, StartTracker tracker, StartTimeType type, Func<T> factory)
|
protected SyncServiceWrapper(string name, Func<T> factory)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
using var timer = tracker.Measure(type);
|
|
||||||
Service = factory();
|
Service = factory();
|
||||||
Penumbra.Log.Verbose($"[{Name}] Created.");
|
Penumbra.Log.Verbose($"[{Name}] Created.");
|
||||||
}
|
}
|
||||||
|
|
@ -54,32 +52,6 @@ public abstract class AsyncServiceWrapper<T> : IDisposable
|
||||||
|
|
||||||
private bool _isDisposed;
|
private bool _isDisposed;
|
||||||
|
|
||||||
protected AsyncServiceWrapper(string name, StartTracker tracker, StartTimeType type, Func<T> factory)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
_task = TrackedTask.Run(() =>
|
|
||||||
{
|
|
||||||
using var timer = tracker.Measure(type);
|
|
||||||
var service = factory();
|
|
||||||
if (_isDisposed)
|
|
||||||
{
|
|
||||||
if (service is IDisposable d)
|
|
||||||
d.Dispose();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Service = service;
|
|
||||||
Penumbra.Log.Verbose($"[{Name}] Created.");
|
|
||||||
_task = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_task.ContinueWith((t, x) =>
|
|
||||||
{
|
|
||||||
if (!_isDisposed)
|
|
||||||
FinishedCreation?.Invoke();
|
|
||||||
}, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AsyncServiceWrapper(string name, Func<T> factory)
|
protected AsyncServiceWrapper(string name, Func<T> factory)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui.Log;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.UI.AdvancedWindow;
|
using Penumbra.UI.AdvancedWindow;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
@ -71,17 +72,16 @@ public class StainService : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly StainData StainData;
|
public readonly DictStains StainData;
|
||||||
public readonly FilterComboColors StainCombo;
|
public readonly FilterComboColors StainCombo;
|
||||||
public readonly StmFile StmFile;
|
public readonly StmFile StmFile;
|
||||||
public readonly StainTemplateCombo TemplateCombo;
|
public readonly StainTemplateCombo TemplateCombo;
|
||||||
|
|
||||||
public StainService(StartTracker timer, DalamudPluginInterface pluginInterface, IDataManager dataManager, IPluginLog dalamudLog)
|
public StainService(DalamudPluginInterface pluginInterface, IDataManager dataManager, Logger logger)
|
||||||
{
|
{
|
||||||
using var t = timer.Measure(StartTimeType.Stains);
|
StainData = new DictStains(pluginInterface, logger, dataManager);
|
||||||
StainData = new StainData(pluginInterface, dataManager, dataManager.Language, dalamudLog);
|
|
||||||
StainCombo = new FilterComboColors(140,
|
StainCombo = new FilterComboColors(140,
|
||||||
() => StainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))).ToList(),
|
() => StainData.Value.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))).ToList(),
|
||||||
Penumbra.Log);
|
Penumbra.Log);
|
||||||
StmFile = new StmFile(dataManager);
|
StmFile = new StmFile(dataManager);
|
||||||
TemplateCombo = new StainTemplateCombo(StainCombo, StmFile);
|
TemplateCombo = new StainTemplateCombo(StainCombo, StmFile);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class ValidityChecker
|
||||||
public readonly bool IsNotInstalledPenumbra;
|
public readonly bool IsNotInstalledPenumbra;
|
||||||
public readonly bool IsValidSourceRepo;
|
public readonly bool IsValidSourceRepo;
|
||||||
|
|
||||||
public readonly List<Exception> ImcExceptions = new();
|
public readonly List<Exception> ImcExceptions = [];
|
||||||
|
|
||||||
public readonly string Version;
|
public readonly string Version;
|
||||||
public readonly string CommitHash;
|
public readonly string CommitHash;
|
||||||
|
|
@ -26,7 +26,7 @@ public class ValidityChecker
|
||||||
IsNotInstalledPenumbra = CheckIsNotInstalled(pi);
|
IsNotInstalledPenumbra = CheckIsNotInstalled(pi);
|
||||||
IsValidSourceRepo = CheckSourceRepo(pi);
|
IsValidSourceRepo = CheckSourceRepo(pi);
|
||||||
|
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
var assembly = GetType().Assembly;
|
||||||
Version = assembly.GetName().Version?.ToString() ?? string.Empty;
|
Version = assembly.GetName().Version?.ToString() ?? string.Empty;
|
||||||
CommitHash = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
CommitHash = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
using Dalamud.Game;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Dalamud.Plugin.Services;
|
|
||||||
using Penumbra.GameData;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
using Penumbra.GameData.Data;
|
|
||||||
using Penumbra.Interop.PathResolving;
|
|
||||||
using Penumbra.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.Services;
|
|
||||||
|
|
||||||
public sealed class IdentifierService : AsyncServiceWrapper<IObjectIdentifier>
|
|
||||||
{
|
|
||||||
public IdentifierService(StartTracker tracker, DalamudPluginInterface pi, IDataManager data, ItemService items, IPluginLog log)
|
|
||||||
: base(nameof(IdentifierService), tracker, StartTimeType.Identifier,
|
|
||||||
() => GameData.GameData.GetIdentifier(pi, data, items.AwaitedService, log))
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ItemService : AsyncServiceWrapper<ItemData>
|
|
||||||
{
|
|
||||||
public ItemService(StartTracker tracker, DalamudPluginInterface pi, IDataManager gameData, IPluginLog log)
|
|
||||||
: base(nameof(ItemService), tracker, StartTimeType.Items, () => new ItemData(pi, gameData, gameData.Language, log))
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ActorService : AsyncServiceWrapper<ActorManager>
|
|
||||||
{
|
|
||||||
public ActorService(StartTracker tracker, DalamudPluginInterface pi, IObjectTable objects, IClientState clientState,
|
|
||||||
IFramework framework, IDataManager gameData, IGameGui gui, CutsceneService cutscene, IPluginLog log, IGameInteropProvider interop)
|
|
||||||
: base(nameof(ActorService), tracker, StartTimeType.Actors,
|
|
||||||
() => new ActorManager(pi, objects, clientState, framework, interop, gameData, gui, idx => (short)cutscene.GetParentIndex(idx), log))
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
using Dalamud.Interface;
|
|
||||||
using Dalamud.Interface.Internal.Notifications;
|
using Dalamud.Interface.Internal.Notifications;
|
||||||
using Dalamud.Utility;
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
|
@ -11,6 +8,7 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Meta;
|
using Penumbra.Meta;
|
||||||
|
|
@ -27,16 +25,14 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
{
|
{
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly ItemService _itemService;
|
|
||||||
private readonly CollectionManager _collectionManager;
|
private readonly CollectionManager _collectionManager;
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly MetaFileManager _metaFileManager;
|
private readonly MetaFileManager _metaFileManager;
|
||||||
|
|
||||||
public ItemSwapTab(CommunicatorService communicator, ItemService itemService, CollectionManager collectionManager,
|
public ItemSwapTab(CommunicatorService communicator, ItemData itemService, CollectionManager collectionManager,
|
||||||
ModManager modManager, IdentifierService identifier, MetaFileManager metaFileManager, Configuration config)
|
ModManager modManager, ObjectIdentification identifier, MetaFileManager metaFileManager, Configuration config)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_itemService = itemService;
|
|
||||||
_collectionManager = collectionManager;
|
_collectionManager = collectionManager;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_metaFileManager = metaFileManager;
|
_metaFileManager = metaFileManager;
|
||||||
|
|
@ -46,15 +42,15 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
_selectors = new Dictionary<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)>
|
_selectors = new Dictionary<SwapType, (ItemSelector Source, ItemSelector Target, string TextFrom, string TextTo)>
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
[SwapType.Hat] = (new ItemSelector(_itemService, FullEquipType.Head), new ItemSelector(_itemService, FullEquipType.Head), "Take this Hat", "and put it on this one" ),
|
[SwapType.Hat] = (new ItemSelector(itemService, FullEquipType.Head), new ItemSelector(itemService, FullEquipType.Head), "Take this Hat", "and put it on this one" ),
|
||||||
[SwapType.Top] = (new ItemSelector(_itemService, FullEquipType.Body), new ItemSelector(_itemService, FullEquipType.Body), "Take this Top", "and put it on this one" ),
|
[SwapType.Top] = (new ItemSelector(itemService, FullEquipType.Body), new ItemSelector(itemService, FullEquipType.Body), "Take this Top", "and put it on this one" ),
|
||||||
[SwapType.Gloves] = (new ItemSelector(_itemService, FullEquipType.Hands), new ItemSelector(_itemService, FullEquipType.Hands), "Take these Gloves", "and put them on these" ),
|
[SwapType.Gloves] = (new ItemSelector(itemService, FullEquipType.Hands), new ItemSelector(itemService, FullEquipType.Hands), "Take these Gloves", "and put them on these" ),
|
||||||
[SwapType.Pants] = (new ItemSelector(_itemService, FullEquipType.Legs), new ItemSelector(_itemService, FullEquipType.Legs), "Take these Pants", "and put them on these" ),
|
[SwapType.Pants] = (new ItemSelector(itemService, FullEquipType.Legs), new ItemSelector(itemService, FullEquipType.Legs), "Take these Pants", "and put them on these" ),
|
||||||
[SwapType.Shoes] = (new ItemSelector(_itemService, FullEquipType.Feet), new ItemSelector(_itemService, FullEquipType.Feet), "Take these Shoes", "and put them on these" ),
|
[SwapType.Shoes] = (new ItemSelector(itemService, FullEquipType.Feet), new ItemSelector(itemService, FullEquipType.Feet), "Take these Shoes", "and put them on these" ),
|
||||||
[SwapType.Earrings] = (new ItemSelector(_itemService, FullEquipType.Ears), new ItemSelector(_itemService, FullEquipType.Ears), "Take these Earrings", "and put them on these" ),
|
[SwapType.Earrings] = (new ItemSelector(itemService, FullEquipType.Ears), new ItemSelector(itemService, FullEquipType.Ears), "Take these Earrings", "and put them on these" ),
|
||||||
[SwapType.Necklace] = (new ItemSelector(_itemService, FullEquipType.Neck), new ItemSelector(_itemService, FullEquipType.Neck), "Take this Necklace", "and put it on this one" ),
|
[SwapType.Necklace] = (new ItemSelector(itemService, FullEquipType.Neck), new ItemSelector(itemService, FullEquipType.Neck), "Take this Necklace", "and put it on this one" ),
|
||||||
[SwapType.Bracelet] = (new ItemSelector(_itemService, FullEquipType.Wrists), new ItemSelector(_itemService, FullEquipType.Wrists), "Take these Bracelets", "and put them on these" ),
|
[SwapType.Bracelet] = (new ItemSelector(itemService, FullEquipType.Wrists), new ItemSelector(itemService, FullEquipType.Wrists), "Take these Bracelets", "and put them on these" ),
|
||||||
[SwapType.Ring] = (new ItemSelector(_itemService, FullEquipType.Finger), new ItemSelector(_itemService, FullEquipType.Finger), "Take this Ring", "and put it on this one" ),
|
[SwapType.Ring] = (new ItemSelector(itemService, FullEquipType.Finger), new ItemSelector(itemService, FullEquipType.Finger), "Take this Ring", "and put it on this one" ),
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -131,8 +127,8 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
|
|
||||||
private class ItemSelector : FilterComboCache<EquipItem>
|
private class ItemSelector : FilterComboCache<EquipItem>
|
||||||
{
|
{
|
||||||
public ItemSelector(ItemService data, FullEquipType type)
|
public ItemSelector(ItemData data, FullEquipType type)
|
||||||
: base(() => data.AwaitedService[type], Penumbra.Log)
|
: base(() => data.ByType[type], Penumbra.Log)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
protected override string ToString(EquipItem obj)
|
protected override string ToString(EquipItem obj)
|
||||||
|
|
@ -214,26 +210,26 @@ public class ItemSwapTab : IDisposable, ITab
|
||||||
break;
|
break;
|
||||||
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
case SwapType.Hair when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace),
|
_swapData.LoadCustomization(_metaFileManager, BodySlot.Hair, Names.CombinedRace(_currentGender, _currentRace),
|
||||||
(SetId)_sourceId,
|
(PrimaryId)_sourceId,
|
||||||
(SetId)_targetId,
|
(PrimaryId)_targetId,
|
||||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
case SwapType.Face when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace),
|
_swapData.LoadCustomization(_metaFileManager, BodySlot.Face, Names.CombinedRace(_currentGender, _currentRace),
|
||||||
(SetId)_sourceId,
|
(PrimaryId)_sourceId,
|
||||||
(SetId)_targetId,
|
(PrimaryId)_targetId,
|
||||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
case SwapType.Ears when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Zear, Names.CombinedRace(_currentGender, ModelRace.Viera),
|
_swapData.LoadCustomization(_metaFileManager, BodySlot.Ear, Names.CombinedRace(_currentGender, ModelRace.Viera),
|
||||||
(SetId)_sourceId,
|
(PrimaryId)_sourceId,
|
||||||
(SetId)_targetId,
|
(PrimaryId)_targetId,
|
||||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
case SwapType.Tail when _targetId > 0 && _sourceId > 0:
|
||||||
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace),
|
_swapData.LoadCustomization(_metaFileManager, BodySlot.Tail, Names.CombinedRace(_currentGender, _currentRace),
|
||||||
(SetId)_sourceId,
|
(PrimaryId)_sourceId,
|
||||||
(SetId)_targetId,
|
(PrimaryId)_targetId,
|
||||||
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
_useCurrentCollection ? _collectionManager.Active.Current : null);
|
||||||
break;
|
break;
|
||||||
case SwapType.Weapon: break;
|
case SwapType.Weapon: break;
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ public partial class ModEditWindow
|
||||||
LoadedShpkPath = path;
|
LoadedShpkPath = path;
|
||||||
var data = LoadedShpkPath.IsRooted
|
var data = LoadedShpkPath.IsRooted
|
||||||
? File.ReadAllBytes(LoadedShpkPath.FullName)
|
? File.ReadAllBytes(LoadedShpkPath.FullName)
|
||||||
: _edit._dalamud.GameData.GetFile(LoadedShpkPath.InternalName.ToString())?.Data;
|
: _edit._gameData.GetFile(LoadedShpkPath.InternalName.ToString())?.Data;
|
||||||
AssociatedShpk = data?.Length > 0 ? new ShpkFile(data) : throw new Exception("Failure to load file data.");
|
AssociatedShpk = data?.Length > 0 ? new ShpkFile(data) : throw new Exception("Failure to load file data.");
|
||||||
LoadedShpkPathName = path.ToPath();
|
LoadedShpkPathName = path.ToPath();
|
||||||
}
|
}
|
||||||
|
|
@ -457,13 +457,13 @@ public partial class ModEditWindow
|
||||||
var foundMaterials = new HashSet<nint>();
|
var foundMaterials = new HashSet<nint>();
|
||||||
foreach (var materialInfo in instances)
|
foreach (var materialInfo in instances)
|
||||||
{
|
{
|
||||||
var material = materialInfo.GetDrawObjectMaterial(_edit._dalamud.Objects);
|
var material = materialInfo.GetDrawObjectMaterial(_edit._objects);
|
||||||
if (foundMaterials.Contains((nint)material))
|
if (foundMaterials.Contains((nint)material))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._dalamud.Objects, materialInfo));
|
MaterialPreviewers.Add(new LiveMaterialPreviewer(_edit._objects, materialInfo));
|
||||||
foundMaterials.Add((nint)material);
|
foundMaterials.Add((nint)material);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
|
|
@ -481,7 +481,7 @@ public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ColorTablePreviewers.Add(new LiveColorTablePreviewer(_edit._dalamud.Objects, _edit._dalamud.Framework, materialInfo));
|
ColorTablePreviewers.Add(new LiveColorTablePreviewer(_edit._objects, _edit._framework, materialInfo));
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public partial class ModEditWindow
|
||||||
// TODO: is it worth trying to use streams for these instead? i'll need to do this for mtrl/tex too, so might be a good idea. that said, the mtrl reader doesn't accept streams, so...
|
// TODO: is it worth trying to use streams for these instead? i'll need to do this for mtrl/tex too, so might be a good idea. that said, the mtrl reader doesn't accept streams, so...
|
||||||
var bytes = resolvedPath switch
|
var bytes = resolvedPath switch
|
||||||
{
|
{
|
||||||
null => _edit._dalamud.GameData.GetFile(sklbPath)?.Data,
|
null => _edit._gameData.GetFile(sklbPath)?.Data,
|
||||||
FullPath path => File.ReadAllBytes(path.ToPath()),
|
FullPath path => File.ReadAllBytes(path.ToPath()),
|
||||||
};
|
};
|
||||||
if (bytes == null)
|
if (bytes == null)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ public partial class ModEditWindow
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var file = _dalamud.GameData.GetFile(path);
|
var file = _gameData.GetFile(path);
|
||||||
writable = file == null ? null : new RawGameFileWritable(file);
|
writable = file == null ? null : new RawGameFileWritable(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using OtterGui.Classes;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using static Penumbra.GameData.Files.ShpkFile;
|
using static Penumbra.GameData.Files.ShpkFile;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using Lumina.Misc;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.GameData.Interop;
|
||||||
|
|
||||||
namespace Penumbra.UI.AdvancedWindow;
|
namespace Penumbra.UI.AdvancedWindow;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ public partial class ModEditWindow
|
||||||
if (tex.Path != path)
|
if (tex.Path != path)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_dalamud.Framework.RunOnFrameworkThread(() => tex.Reload(_textures));
|
_framework.RunOnFrameworkThread(() => tex.Reload(_textures));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
private readonly ModEditor _editor;
|
private readonly ModEditor _editor;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly ItemSwapTab _itemSwapTab;
|
private readonly ItemSwapTab _itemSwapTab;
|
||||||
private readonly DalamudServices _dalamud;
|
|
||||||
private readonly MetaFileManager _metaFileManager;
|
private readonly MetaFileManager _metaFileManager;
|
||||||
private readonly ActiveCollections _activeCollections;
|
private readonly ActiveCollections _activeCollections;
|
||||||
private readonly StainService _stainService;
|
private readonly StainService _stainService;
|
||||||
|
|
@ -45,6 +44,9 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly IDragDropManager _dragDropManager;
|
private readonly IDragDropManager _dragDropManager;
|
||||||
private readonly GameEventManager _gameEvents;
|
private readonly GameEventManager _gameEvents;
|
||||||
|
private readonly IDataManager _gameData;
|
||||||
|
private readonly IFramework _framework;
|
||||||
|
private readonly IObjectTable _objects;
|
||||||
|
|
||||||
private Mod? _mod;
|
private Mod? _mod;
|
||||||
private Vector2 _iconSize = Vector2.Zero;
|
private Vector2 _iconSize = Vector2.Zero;
|
||||||
|
|
@ -563,19 +565,19 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
|
|
||||||
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
|
||||||
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
|
||||||
StainService stainService, ActiveCollections activeCollections, DalamudServices dalamud, ModMergeTab modMergeTab,
|
StainService stainService, ActiveCollections activeCollections, ModMergeTab modMergeTab,
|
||||||
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager, GameEventManager gameEvents,
|
CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager, GameEventManager gameEvents,
|
||||||
ChangedItemDrawer changedItemDrawer)
|
ChangedItemDrawer changedItemDrawer, IObjectTable objects, IFramework framework)
|
||||||
: base(WindowBaseLabel)
|
: base(WindowBaseLabel)
|
||||||
{
|
{
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
_itemSwapTab = itemSwapTab;
|
_itemSwapTab = itemSwapTab;
|
||||||
|
_gameData = gameData;
|
||||||
_config = config;
|
_config = config;
|
||||||
_editor = editor;
|
_editor = editor;
|
||||||
_metaFileManager = metaFileManager;
|
_metaFileManager = metaFileManager;
|
||||||
_stainService = stainService;
|
_stainService = stainService;
|
||||||
_activeCollections = activeCollections;
|
_activeCollections = activeCollections;
|
||||||
_dalamud = dalamud;
|
|
||||||
_modMergeTab = modMergeTab;
|
_modMergeTab = modMergeTab;
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_dragDropManager = dragDropManager;
|
_dragDropManager = dragDropManager;
|
||||||
|
|
@ -583,6 +585,8 @@ public partial class ModEditWindow : Window, IDisposable
|
||||||
_models = models;
|
_models = models;
|
||||||
_fileDialog = fileDialog;
|
_fileDialog = fileDialog;
|
||||||
_gameEvents = gameEvents;
|
_gameEvents = gameEvents;
|
||||||
|
_objects = objects;
|
||||||
|
_framework = framework;
|
||||||
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
|
_materialTab = new FileEditor<MtrlTab>(this, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl",
|
||||||
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
() => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => _mod?.ModPath.FullName ?? string.Empty,
|
||||||
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
|
(bytes, path, writable) => new MtrlTab(this, new MtrlFile(bytes), path, writable));
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,70 @@ public class ChangedItemDrawer : IDisposable
|
||||||
Emote = 0x01_00_00,
|
Emote = 0x01_00_00,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly ChangedItemIcon[] Order =
|
||||||
|
[
|
||||||
|
ChangedItemIcon.Head,
|
||||||
|
ChangedItemIcon.Body,
|
||||||
|
ChangedItemIcon.Hands,
|
||||||
|
ChangedItemIcon.Legs,
|
||||||
|
ChangedItemIcon.Feet,
|
||||||
|
ChangedItemIcon.Ears,
|
||||||
|
ChangedItemIcon.Neck,
|
||||||
|
ChangedItemIcon.Wrists,
|
||||||
|
ChangedItemIcon.Finger,
|
||||||
|
ChangedItemIcon.Mainhand,
|
||||||
|
ChangedItemIcon.Offhand,
|
||||||
|
ChangedItemIcon.Customization,
|
||||||
|
ChangedItemIcon.Action,
|
||||||
|
ChangedItemIcon.Emote,
|
||||||
|
ChangedItemIcon.Monster,
|
||||||
|
ChangedItemIcon.Demihuman,
|
||||||
|
ChangedItemIcon.Unknown,
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly string[] LowerNames = Order.Select(f => ToDescription(f).ToLowerInvariant()).ToArray();
|
||||||
|
|
||||||
|
public static bool TryParseIndex(ReadOnlySpan<char> input, out ChangedItemIcon slot)
|
||||||
|
{
|
||||||
|
// Handle numeric cases before TryParse because numbers
|
||||||
|
// are not logical otherwise.
|
||||||
|
if (int.TryParse(input, out var idx))
|
||||||
|
{
|
||||||
|
// We assume users will use 1-based index, but if they enter 0, just use the first.
|
||||||
|
if (idx == 0)
|
||||||
|
{
|
||||||
|
slot = Order[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use 1-based index.
|
||||||
|
--idx;
|
||||||
|
if (idx >= 0 && idx < Order.Length)
|
||||||
|
{
|
||||||
|
slot = Order[idx];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slot = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryParsePartial(string lowerInput, out ChangedItemIcon slot)
|
||||||
|
{
|
||||||
|
if (TryParseIndex(lowerInput, out slot))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
slot = 0;
|
||||||
|
foreach (var (item, flag) in LowerNames.Zip(Order))
|
||||||
|
if (item.Contains(lowerInput, StringComparison.Ordinal))
|
||||||
|
slot |= flag;
|
||||||
|
|
||||||
|
return slot != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public const ChangedItemIcon AllFlags = (ChangedItemIcon)0x01FFFF;
|
public const ChangedItemIcon AllFlags = (ChangedItemIcon)0x01FFFF;
|
||||||
|
public static readonly int NumCategories = Order.Length;
|
||||||
public const ChangedItemIcon DefaultFlags = AllFlags & ~ChangedItemIcon.Offhand;
|
public const ChangedItemIcon DefaultFlags = AllFlags & ~ChangedItemIcon.Offhand;
|
||||||
|
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
|
@ -163,26 +226,7 @@ public class ChangedItemDrawer : IDisposable
|
||||||
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
using var _ = ImRaii.PushId("ChangedItemIconFilter");
|
||||||
var size = TypeFilterIconSize;
|
var size = TypeFilterIconSize;
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
var order = new[]
|
|
||||||
{
|
|
||||||
ChangedItemIcon.Head,
|
|
||||||
ChangedItemIcon.Body,
|
|
||||||
ChangedItemIcon.Hands,
|
|
||||||
ChangedItemIcon.Legs,
|
|
||||||
ChangedItemIcon.Feet,
|
|
||||||
ChangedItemIcon.Ears,
|
|
||||||
ChangedItemIcon.Neck,
|
|
||||||
ChangedItemIcon.Wrists,
|
|
||||||
ChangedItemIcon.Finger,
|
|
||||||
ChangedItemIcon.Mainhand,
|
|
||||||
ChangedItemIcon.Offhand,
|
|
||||||
ChangedItemIcon.Customization,
|
|
||||||
ChangedItemIcon.Action,
|
|
||||||
ChangedItemIcon.Emote,
|
|
||||||
ChangedItemIcon.Monster,
|
|
||||||
ChangedItemIcon.Demihuman,
|
|
||||||
ChangedItemIcon.Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
bool DrawIcon(ChangedItemIcon type, ref ChangedItemIcon typeFilter)
|
bool DrawIcon(ChangedItemIcon type, ref ChangedItemIcon typeFilter)
|
||||||
{
|
{
|
||||||
|
|
@ -217,13 +261,13 @@ public class ChangedItemDrawer : IDisposable
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var iconType in order)
|
foreach (var iconType in Order)
|
||||||
{
|
{
|
||||||
ret |= DrawIcon(iconType, ref typeFilter);
|
ret |= DrawIcon(iconType, ref typeFilter);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SetCursorPos(new(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
|
ImGui.SetCursorPos(new Vector2(ImGui.GetContentRegionMax().X - size.X, ImGui.GetCursorPosY() + yOffset));
|
||||||
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
|
ImGui.Image(_icons[AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One,
|
||||||
typeFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
|
typeFilter == 0 ? new Vector4(0.6f, 0.3f, 0.3f, 1f) :
|
||||||
typeFilter == AllFlags ? new Vector4(0.75f, 0.75f, 0.75f, 1f) : new Vector4(0.5f, 0.5f, 1f, 1f));
|
typeFilter == AllFlags ? new Vector4(0.75f, 0.75f, 0.75f, 1f) : new Vector4(0.5f, 0.5f, 1f, 1f));
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ public class PenumbraChangelog
|
||||||
private static void Add8_3_0(Changelog log)
|
private static void Add8_3_0(Changelog log)
|
||||||
=> log.NextVersion("Version 0.8.3.0")
|
=> log.NextVersion("Version 0.8.3.0")
|
||||||
.RegisterHighlight("Improved the UI for the On-Screen tabs with highlighting of used paths, filtering and more selections. (by Ny)")
|
.RegisterHighlight("Improved the UI for the On-Screen tabs with highlighting of used paths, filtering and more selections. (by Ny)")
|
||||||
.RegisterEntry("Added an option to replace non-ASCII symbols with underscores for folder paths on mod import since this causes problems on some WINE systems. This option is off by default.")
|
.RegisterEntry(
|
||||||
|
"Added an option to replace non-ASCII symbols with underscores for folder paths on mod import since this causes problems on some WINE systems. This option is off by default.")
|
||||||
.RegisterEntry(
|
.RegisterEntry(
|
||||||
"Added support for the Changed Item Icons to load modded icons, but this depends on a not-yet-released Dalamud update.")
|
"Added support for the Changed Item Icons to load modded icons, but this depends on a not-yet-released Dalamud update.")
|
||||||
.RegisterEntry(
|
.RegisterEntry(
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public sealed class CollectionPanel : IDisposable
|
||||||
private readonly CollectionStorage _collections;
|
private readonly CollectionStorage _collections;
|
||||||
private readonly ActiveCollections _active;
|
private readonly ActiveCollections _active;
|
||||||
private readonly CollectionSelector _selector;
|
private readonly CollectionSelector _selector;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly ITargetManager _targets;
|
private readonly ITargetManager _targets;
|
||||||
private readonly IndividualAssignmentUi _individualAssignmentUi;
|
private readonly IndividualAssignmentUi _individualAssignmentUi;
|
||||||
private readonly InheritanceUi _inheritanceUi;
|
private readonly InheritanceUi _inheritanceUi;
|
||||||
|
|
@ -37,7 +37,7 @@ public sealed class CollectionPanel : IDisposable
|
||||||
private int _draggedIndividualAssignment = -1;
|
private int _draggedIndividualAssignment = -1;
|
||||||
|
|
||||||
public CollectionPanel(DalamudPluginInterface pi, CommunicatorService communicator, CollectionManager manager,
|
public CollectionPanel(DalamudPluginInterface pi, CommunicatorService communicator, CollectionManager manager,
|
||||||
CollectionSelector selector, ActorService actors, ITargetManager targets, ModStorage mods)
|
CollectionSelector selector, ActorManager actors, ITargetManager targets, ModStorage mods)
|
||||||
{
|
{
|
||||||
_collections = manager.Storage;
|
_collections = manager.Storage;
|
||||||
_active = manager.Active;
|
_active = manager.Active;
|
||||||
|
|
@ -382,11 +382,11 @@ public sealed class CollectionPanel : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCurrentCharacter(Vector2 width)
|
private void DrawCurrentCharacter(Vector2 width)
|
||||||
=> DrawIndividualButton("Current Character", width, string.Empty, 'c', _actors.AwaitedService.GetCurrentPlayer());
|
=> DrawIndividualButton("Current Character", width, string.Empty, 'c', _actors.GetCurrentPlayer());
|
||||||
|
|
||||||
private void DrawCurrentTarget(Vector2 width)
|
private void DrawCurrentTarget(Vector2 width)
|
||||||
=> DrawIndividualButton("Current Target", width, string.Empty, 't',
|
=> DrawIndividualButton("Current Target", width, string.Empty, 't',
|
||||||
_actors.AwaitedService.FromObject(_targets.Target, false, true, true));
|
_actors.FromObject(_targets.Target, false, true, true));
|
||||||
|
|
||||||
private void DrawNewPlayer(Vector2 width)
|
private void DrawNewPlayer(Vector2 width)
|
||||||
=> DrawIndividualButton("New Player", width, _individualAssignmentUi.PlayerTooltip, 'p',
|
=> DrawIndividualButton("New Player", width, _individualAssignmentUi.PlayerTooltip, 'p',
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Gui;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
|
||||||
namespace Penumbra.UI.CollectionTab;
|
namespace Penumbra.UI.CollectionTab;
|
||||||
|
|
@ -12,7 +15,7 @@ namespace Penumbra.UI.CollectionTab;
|
||||||
public class IndividualAssignmentUi : IDisposable
|
public class IndividualAssignmentUi : IDisposable
|
||||||
{
|
{
|
||||||
private readonly CommunicatorService _communicator;
|
private readonly CommunicatorService _communicator;
|
||||||
private readonly ActorService _actorService;
|
private readonly ActorManager _actors;
|
||||||
private readonly CollectionManager _collectionManager;
|
private readonly CollectionManager _collectionManager;
|
||||||
|
|
||||||
private WorldCombo _worldCombo = null!;
|
private WorldCombo _worldCombo = null!;
|
||||||
|
|
@ -24,16 +27,13 @@ public class IndividualAssignmentUi : IDisposable
|
||||||
|
|
||||||
private bool _ready;
|
private bool _ready;
|
||||||
|
|
||||||
public IndividualAssignmentUi(CommunicatorService communicator, ActorService actors, CollectionManager collectionManager)
|
public IndividualAssignmentUi(CommunicatorService communicator, ActorManager actors, CollectionManager collectionManager)
|
||||||
{
|
{
|
||||||
_communicator = communicator;
|
_communicator = communicator;
|
||||||
_actorService = actors;
|
_actors = actors;
|
||||||
_collectionManager = collectionManager;
|
_collectionManager = collectionManager;
|
||||||
_communicator.CollectionChange.Subscribe(UpdateIdentifiers, CollectionChange.Priority.IndividualAssignmentUi);
|
_communicator.CollectionChange.Subscribe(UpdateIdentifiers, CollectionChange.Priority.IndividualAssignmentUi);
|
||||||
if (_actorService.Valid)
|
_actors.Awaiter.ContinueWith(_ => SetupCombos());
|
||||||
SetupCombos();
|
|
||||||
else
|
|
||||||
_actorService.FinishedCreation += SetupCombos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string PlayerTooltip { get; private set; } = NewPlayerTooltipEmpty;
|
public string PlayerTooltip { get; private set; } = NewPlayerTooltipEmpty;
|
||||||
|
|
@ -91,10 +91,10 @@ public class IndividualAssignmentUi : IDisposable
|
||||||
// Input Selections.
|
// Input Selections.
|
||||||
private string _newCharacterName = string.Empty;
|
private string _newCharacterName = string.Empty;
|
||||||
private ObjectKind _newKind = ObjectKind.BattleNpc;
|
private ObjectKind _newKind = ObjectKind.BattleNpc;
|
||||||
private ActorIdentifier[] _playerIdentifiers = Array.Empty<ActorIdentifier>();
|
private ActorIdentifier[] _playerIdentifiers = [];
|
||||||
private ActorIdentifier[] _retainerIdentifiers = Array.Empty<ActorIdentifier>();
|
private ActorIdentifier[] _retainerIdentifiers = [];
|
||||||
private ActorIdentifier[] _npcIdentifiers = Array.Empty<ActorIdentifier>();
|
private ActorIdentifier[] _npcIdentifiers = [];
|
||||||
private ActorIdentifier[] _ownedIdentifiers = Array.Empty<ActorIdentifier>();
|
private ActorIdentifier[] _ownedIdentifiers = [];
|
||||||
|
|
||||||
private const string NewPlayerTooltipEmpty = "Please enter a valid player name and choose an available world or 'Any World'.";
|
private const string NewPlayerTooltipEmpty = "Please enter a valid player name and choose an available world or 'Any World'.";
|
||||||
private const string NewRetainerTooltipEmpty = "Please enter a valid retainer name.";
|
private const string NewRetainerTooltipEmpty = "Please enter a valid retainer name.";
|
||||||
|
|
@ -126,14 +126,13 @@ public class IndividualAssignmentUi : IDisposable
|
||||||
/// <summary> Create combos when ready. </summary>
|
/// <summary> Create combos when ready. </summary>
|
||||||
private void SetupCombos()
|
private void SetupCombos()
|
||||||
{
|
{
|
||||||
_worldCombo = new WorldCombo(_actorService.AwaitedService.Data.Worlds, Penumbra.Log);
|
_worldCombo = new WorldCombo(_actors.Data.Worlds, Penumbra.Log);
|
||||||
_mountCombo = new NpcCombo("##mountCombo", _actorService.AwaitedService.Data.Mounts, Penumbra.Log);
|
_mountCombo = new NpcCombo("##mountCombo", _actors.Data.Mounts, Penumbra.Log);
|
||||||
_companionCombo = new NpcCombo("##companionCombo", _actorService.AwaitedService.Data.Companions, Penumbra.Log);
|
_companionCombo = new NpcCombo("##companionCombo", _actors.Data.Companions, Penumbra.Log);
|
||||||
_ornamentCombo = new NpcCombo("##ornamentCombo", _actorService.AwaitedService.Data.Ornaments, Penumbra.Log);
|
_ornamentCombo = new NpcCombo("##ornamentCombo", _actors.Data.Ornaments, Penumbra.Log);
|
||||||
_bnpcCombo = new NpcCombo("##bnpcCombo", _actorService.AwaitedService.Data.BNpcs, Penumbra.Log);
|
_bnpcCombo = new NpcCombo("##bnpcCombo", _actors.Data.BNpcs, Penumbra.Log);
|
||||||
_enpcCombo = new NpcCombo("##enpcCombo", _actorService.AwaitedService.Data.ENpcs, Penumbra.Log);
|
_enpcCombo = new NpcCombo("##enpcCombo", _actors.Data.ENpcs, Penumbra.Log);
|
||||||
_ready = true;
|
_ready = true;
|
||||||
_actorService.FinishedCreation -= SetupCombos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateIdentifiers(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
private void UpdateIdentifiers(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
|
||||||
|
|
@ -146,16 +145,16 @@ public class IndividualAssignmentUi : IDisposable
|
||||||
{
|
{
|
||||||
var combo = GetNpcCombo(_newKind);
|
var combo = GetNpcCombo(_newKind);
|
||||||
PlayerTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Player, _newCharacterName,
|
PlayerTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Player, _newCharacterName,
|
||||||
_worldCombo.CurrentSelection.Key, ObjectKind.None,
|
_worldCombo.CurrentSelection.Key, ObjectKind.None, [], out _playerIdentifiers) switch
|
||||||
Array.Empty<uint>(), out _playerIdentifiers) switch
|
|
||||||
{
|
{
|
||||||
_ when _newCharacterName.Length == 0 => NewPlayerTooltipEmpty,
|
_ when _newCharacterName.Length == 0 => NewPlayerTooltipEmpty,
|
||||||
IndividualCollections.AddResult.Invalid => NewPlayerTooltipInvalid,
|
IndividualCollections.AddResult.Invalid => NewPlayerTooltipInvalid,
|
||||||
IndividualCollections.AddResult.AlreadySet => AlreadyAssigned,
|
IndividualCollections.AddResult.AlreadySet => AlreadyAssigned,
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
RetainerTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Retainer, _newCharacterName, 0, ObjectKind.None,
|
RetainerTooltip =
|
||||||
Array.Empty<uint>(), out _retainerIdentifiers) switch
|
_collectionManager.Active.Individuals.CanAdd(IdentifierType.Retainer, _newCharacterName, 0, ObjectKind.None, [],
|
||||||
|
out _retainerIdentifiers) switch
|
||||||
{
|
{
|
||||||
_ when _newCharacterName.Length == 0 => NewRetainerTooltipEmpty,
|
_ when _newCharacterName.Length == 0 => NewRetainerTooltipEmpty,
|
||||||
IndividualCollections.AddResult.Invalid => NewRetainerTooltipInvalid,
|
IndividualCollections.AddResult.Invalid => NewRetainerTooltipInvalid,
|
||||||
|
|
@ -184,8 +183,8 @@ public class IndividualAssignmentUi : IDisposable
|
||||||
{
|
{
|
||||||
NpcTooltip = NewNpcTooltipEmpty;
|
NpcTooltip = NewNpcTooltipEmpty;
|
||||||
OwnedTooltip = NewNpcTooltipEmpty;
|
OwnedTooltip = NewNpcTooltipEmpty;
|
||||||
_npcIdentifiers = Array.Empty<ActorIdentifier>();
|
_npcIdentifiers = [];
|
||||||
_ownedIdentifiers = Array.Empty<ActorIdentifier>();
|
_ownedIdentifiers = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.Communication;
|
using Penumbra.Communication;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Mods.Subclasses;
|
using Penumbra.Mods.Subclasses;
|
||||||
|
|
@ -169,6 +171,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
|
ImRaii.TreeNode(leaf.Value.Name, flags).Dispose();
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Middle))
|
||||||
{
|
{
|
||||||
|
_modManager.SetKnown(leaf.Value);
|
||||||
var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index];
|
var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index];
|
||||||
if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
|
if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive())
|
||||||
{
|
{
|
||||||
|
|
@ -189,9 +192,9 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
var itemPos = ImGui.GetItemRectMax().X;
|
var itemPos = ImGui.GetItemRectMax().X;
|
||||||
var maxWidth = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
|
var maxWidth = ImGui.GetWindowPos().X + ImGui.GetWindowContentRegionMax().X;
|
||||||
var priorityString = $"[{state.Priority}]";
|
var priorityString = $"[{state.Priority}]";
|
||||||
var requiredSize = ImGui.CalcTextSize(priorityString).X;
|
var Size = ImGui.CalcTextSize(priorityString).X;
|
||||||
var remainingSpace = maxWidth - itemPos;
|
var remainingSpace = maxWidth - itemPos;
|
||||||
var offset = remainingSpace - requiredSize;
|
var offset = remainingSpace - Size;
|
||||||
if (ImGui.GetScrollMaxY() == 0)
|
if (ImGui.GetScrollMaxY() == 0)
|
||||||
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
|
offset -= ImGui.GetStyle().ItemInnerSpacing.X;
|
||||||
|
|
||||||
|
|
@ -510,6 +513,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
private LowerString _modFilter = LowerString.Empty;
|
private LowerString _modFilter = LowerString.Empty;
|
||||||
private int _filterType = -1;
|
private int _filterType = -1;
|
||||||
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
private ModFilter _stateFilter = ModFilterExtensions.UnfilteredStateMods;
|
||||||
|
private ChangedItemDrawer.ChangedItemIcon _slotFilter = 0;
|
||||||
|
|
||||||
private void SetFilterTooltip()
|
private void SetFilterTooltip()
|
||||||
{
|
{
|
||||||
|
|
@ -517,7 +521,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
+ "Enter c:[string] to filter for mods changing specific items.\n"
|
+ "Enter c:[string] to filter for mods changing specific items.\n"
|
||||||
+ "Enter t:[string] to filter for mods set to specific tags.\n"
|
+ "Enter t:[string] to filter for mods set to specific tags.\n"
|
||||||
+ "Enter n:[string] to filter only for mod names and no paths.\n"
|
+ "Enter n:[string] to filter only for mod names and no paths.\n"
|
||||||
+ "Enter a:[string] to filter for mods by specific authors.\n\n"
|
+ "Enter a:[string] to filter for mods by specific authors.\n"
|
||||||
|
+ $"Enter s:[string] to filter for mods by the categories of the items they change (1-{ChangedItemDrawer.NumCategories+1} or partial category name).\n"
|
||||||
+ "Use None as a placeholder value that only matches empty lists or names.";
|
+ "Use None as a placeholder value that only matches empty lists or names.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,6 +543,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
|
'C' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 3),
|
||||||
't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
|
't' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
|
||||||
'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
|
'T' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 4),
|
||||||
|
's' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5),
|
||||||
|
'S' => filterValue.Length == 2 ? (LowerString.Empty, -1) : ParseFilter(filterValue, 5),
|
||||||
_ => (new LowerString(filterValue), 0),
|
_ => (new LowerString(filterValue), 0),
|
||||||
},
|
},
|
||||||
_ => (new LowerString(filterValue), 0),
|
_ => (new LowerString(filterValue), 0),
|
||||||
|
|
@ -548,10 +555,13 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
|
|
||||||
private const int EmptyOffset = 128;
|
private const int EmptyOffset = 128;
|
||||||
|
|
||||||
private static (LowerString, int) ParseFilter(string value, int id)
|
private (LowerString, int) ParseFilter(string value, int id)
|
||||||
{
|
{
|
||||||
value = value[2..];
|
value = value[2..];
|
||||||
var lower = new LowerString(value);
|
var lower = new LowerString(value);
|
||||||
|
if (id == 5 && !ChangedItemDrawer.TryParsePartial(lower.Lower, out _slotFilter))
|
||||||
|
_slotFilter = 0;
|
||||||
|
|
||||||
return (lower, lower.Lower is "none" ? id + EmptyOffset : id);
|
return (lower, lower.Lower is "none" ? id + EmptyOffset : id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,9 +611,11 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
|
||||||
2 => !mod.Author.Contains(_modFilter),
|
2 => !mod.Author.Contains(_modFilter),
|
||||||
3 => !mod.LowerChangedItemsString.Contains(_modFilter.Lower),
|
3 => !mod.LowerChangedItemsString.Contains(_modFilter.Lower),
|
||||||
4 => !mod.AllTagsLower.Contains(_modFilter.Lower),
|
4 => !mod.AllTagsLower.Contains(_modFilter.Lower),
|
||||||
|
5 => mod.ChangedItems.All(p => (ChangedItemDrawer.GetCategoryIcon(p.Key, p.Value) & _slotFilter) == 0),
|
||||||
2 + EmptyOffset => !mod.Author.IsEmpty,
|
2 + EmptyOffset => !mod.Author.IsEmpty,
|
||||||
3 + EmptyOffset => mod.LowerChangedItemsString.Length > 0,
|
3 + EmptyOffset => mod.LowerChangedItemsString.Length > 0,
|
||||||
4 + EmptyOffset => mod.AllTagsLower.Length > 0,
|
4 + EmptyOffset => mod.AllTagsLower.Length > 0,
|
||||||
|
5 + EmptyOffset => mod.ChangedItems.Count == 0,
|
||||||
_ => false, // Should never happen
|
_ => false, // Should never happen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ public class ModPanelEditTab : ITab
|
||||||
_modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
|
_modManager.DataEditor.ChangeModTag(_mod, tagIdx, editedTag);
|
||||||
|
|
||||||
UiHelpers.DefaultLineSpace();
|
UiHelpers.DefaultLineSpace();
|
||||||
AddOptionGroup.Draw(_filenames, _modManager, _mod);
|
AddOptionGroup.Draw(_filenames, _modManager, _mod, _config.ReplaceNonAsciiOnImport);
|
||||||
UiHelpers.DefaultLineSpace();
|
UiHelpers.DefaultLineSpace();
|
||||||
|
|
||||||
for (var groupIdx = 0; groupIdx < _mod.Groups.Count; ++groupIdx)
|
for (var groupIdx = 0; groupIdx < _mod.Groups.Count; ++groupIdx)
|
||||||
|
|
@ -235,13 +235,13 @@ public class ModPanelEditTab : ITab
|
||||||
public static void Reset()
|
public static void Reset()
|
||||||
=> _newGroupName = string.Empty;
|
=> _newGroupName = string.Empty;
|
||||||
|
|
||||||
public static void Draw(FilenameService filenames, ModManager modManager, Mod mod)
|
public static void Draw(FilenameService filenames, ModManager modManager, Mod mod, bool onlyAscii)
|
||||||
{
|
{
|
||||||
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3));
|
using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(UiHelpers.ScaleX3));
|
||||||
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3);
|
ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3);
|
||||||
ImGui.InputTextWithHint("##newGroup", "Add new option group...", ref _newGroupName, 256);
|
ImGui.InputTextWithHint("##newGroup", "Add new option group...", ref _newGroupName, 256);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var defaultFile = filenames.OptionGroupFile(mod, -1);
|
var defaultFile = filenames.OptionGroupFile(mod, -1, onlyAscii);
|
||||||
var fileExists = File.Exists(defaultFile);
|
var fileExists = File.Exists(defaultFile);
|
||||||
var tt = fileExists
|
var tt = fileExists
|
||||||
? "Open the default option json file in the text editor of your choice."
|
? "Open the default option json file in the text editor of your choice."
|
||||||
|
|
@ -438,7 +438,7 @@ public class ModPanelEditTab : ITab
|
||||||
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, groupIdx));
|
_delayedActions.Enqueue(() => DescriptionEdit.OpenPopup(_mod, groupIdx));
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var fileName = _filenames.OptionGroupFile(_mod, groupIdx);
|
var fileName = _filenames.OptionGroupFile(_mod, groupIdx, _config.ReplaceNonAsciiOnImport);
|
||||||
var fileExists = File.Exists(fileName);
|
var fileExists = File.Exists(fileName);
|
||||||
tt = fileExists
|
tt = fileExists
|
||||||
? $"Open the {group.Name} json file in the text editor of your choice."
|
? $"Open the {group.Name} json file in the text editor of your choice."
|
||||||
|
|
@ -475,10 +475,11 @@ public class ModPanelEditTab : ITab
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.TableSetupColumn("idx", ImGuiTableColumnFlags.WidthFixed, 60 * UiHelpers.Scale);
|
var maxWidth = ImGui.CalcTextSize("Option #88.").X;
|
||||||
|
ImGui.TableSetupColumn("idx", ImGuiTableColumnFlags.WidthFixed, maxWidth);
|
||||||
ImGui.TableSetupColumn("default", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
ImGui.TableSetupColumn("default", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||||
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthFixed,
|
ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthFixed,
|
||||||
UiHelpers.InputTextWidth.X - 72 * UiHelpers.Scale - ImGui.GetFrameHeight() - UiHelpers.IconButtonSize.X);
|
UiHelpers.InputTextWidth.X - maxWidth - 12 * UiHelpers.Scale - ImGui.GetFrameHeight() - UiHelpers.IconButtonSize.X);
|
||||||
ImGui.TableSetupColumn("description", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
|
ImGui.TableSetupColumn("description", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
|
||||||
ImGui.TableSetupColumn("delete", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
|
ImGui.TableSetupColumn("delete", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
|
||||||
ImGui.TableSetupColumn("priority", ImGuiTableColumnFlags.WidthFixed, 50 * UiHelpers.Scale);
|
ImGui.TableSetupColumn("priority", ImGuiTableColumnFlags.WidthFixed, 50 * UiHelpers.Scale);
|
||||||
|
|
@ -644,7 +645,7 @@ public class ModPanelEditTab : ITab
|
||||||
_ => "Unknown",
|
_ => "Unknown",
|
||||||
};
|
};
|
||||||
|
|
||||||
ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X - 3 * (UiHelpers.IconButtonSize.X - 4 * UiHelpers.Scale));
|
ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X - 2 * UiHelpers.IconButtonSize.X - 2 * ImGui.GetStyle().ItemSpacing.X);
|
||||||
using var combo = ImRaii.Combo("##GroupType", GroupTypeName(group.Type));
|
using var combo = ImRaii.Combo("##GroupType", GroupTypeName(group.Type));
|
||||||
if (!combo)
|
if (!combo)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,16 @@ using OtterGui.Widgets;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
using Penumbra.Interop.ResourceLoading;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Services;
|
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.ResourceWatcher;
|
namespace Penumbra.UI.ResourceWatcher;
|
||||||
|
|
||||||
public class ResourceWatcher : IDisposable, ITab
|
public sealed class ResourceWatcher : IDisposable, ITab
|
||||||
{
|
{
|
||||||
public const int DefaultMaxEntries = 1024;
|
public const int DefaultMaxEntries = 1024;
|
||||||
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
|
public const RecordType AllRecords = RecordType.Request | RecordType.ResourceLoad | RecordType.FileLoad | RecordType.Destruction;
|
||||||
|
|
@ -24,15 +24,15 @@ public class ResourceWatcher : IDisposable, ITab
|
||||||
private readonly EphemeralConfig _ephemeral;
|
private readonly EphemeralConfig _ephemeral;
|
||||||
private readonly ResourceService _resources;
|
private readonly ResourceService _resources;
|
||||||
private readonly ResourceLoader _loader;
|
private readonly ResourceLoader _loader;
|
||||||
private readonly ActorService _actors;
|
private readonly ActorManager _actors;
|
||||||
private readonly List<Record> _records = new();
|
private readonly List<Record> _records = [];
|
||||||
private readonly ConcurrentQueue<Record> _newRecords = new();
|
private readonly ConcurrentQueue<Record> _newRecords = [];
|
||||||
private readonly ResourceWatcherTable _table;
|
private readonly ResourceWatcherTable _table;
|
||||||
private string _logFilter = string.Empty;
|
private string _logFilter = string.Empty;
|
||||||
private Regex? _logRegex;
|
private Regex? _logRegex;
|
||||||
private int _newMaxEntries;
|
private int _newMaxEntries;
|
||||||
|
|
||||||
public unsafe ResourceWatcher(ActorService actors, Configuration config, ResourceService resources, ResourceLoader loader)
|
public unsafe ResourceWatcher(ActorManager actors, Configuration config, ResourceService resources, ResourceLoader loader)
|
||||||
{
|
{
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
@ -266,12 +266,12 @@ public class ResourceWatcher : IDisposable, ITab
|
||||||
|
|
||||||
public unsafe string Name(ResolveData resolve, string none = "")
|
public unsafe string Name(ResolveData resolve, string none = "")
|
||||||
{
|
{
|
||||||
if (resolve.AssociatedGameObject == IntPtr.Zero || !_actors.Valid)
|
if (resolve.AssociatedGameObject == IntPtr.Zero || !_actors.Awaiter.IsCompletedSuccessfully)
|
||||||
return none;
|
return none;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var id = _actors.AwaitedService.FromObject((GameObject*)resolve.AssociatedGameObject, out _, false, true, true);
|
var id = _actors.FromObject((GameObject*)resolve.AssociatedGameObject, out _, false, true, true);
|
||||||
if (id.IsValid)
|
if (id.IsValid)
|
||||||
{
|
{
|
||||||
if (id.Type is not (IdentifierType.Player or IdentifierType.Owned))
|
if (id.Type is not (IdentifierType.Player or IdentifierType.Owned))
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
@ -14,7 +15,7 @@ using Penumbra.UI.CollectionTab;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
||||||
public class CollectionsTab : IDisposable, ITab
|
public sealed class CollectionsTab : IDisposable, ITab
|
||||||
{
|
{
|
||||||
private readonly EphemeralConfig _config;
|
private readonly EphemeralConfig _config;
|
||||||
private readonly CollectionSelector _selector;
|
private readonly CollectionSelector _selector;
|
||||||
|
|
@ -40,7 +41,7 @@ public class CollectionsTab : IDisposable, ITab
|
||||||
}
|
}
|
||||||
|
|
||||||
public CollectionsTab(DalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator,
|
public CollectionsTab(DalamudPluginInterface pi, Configuration configuration, CommunicatorService communicator,
|
||||||
CollectionManager collectionManager, ModStorage modStorage, ActorService actors, ITargetManager targets, TutorialService tutorial)
|
CollectionManager collectionManager, ModStorage modStorage, ActorManager actors, ITargetManager targets, TutorialService tutorial)
|
||||||
{
|
{
|
||||||
_config = configuration.Ephemeral;
|
_config = configuration.Ephemeral;
|
||||||
_tutorial = tutorial;
|
_tutorial = tutorial;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using OtterGui.Widgets;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Services;
|
using Penumbra.Services;
|
||||||
|
using Penumbra.UI.Tabs.Debug;
|
||||||
using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher;
|
using Watcher = Penumbra.UI.ResourceWatcher.ResourceWatcher;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,31 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Utility;
|
using Dalamud.Interface.Utility;
|
||||||
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
using FFXIVClientStructs.FFXIV.Client.Game.Group;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Services;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.DataContainers;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Import.Textures;
|
using Penumbra.Import.Textures;
|
||||||
using Penumbra.Interop.ResourceLoading;
|
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
|
using Penumbra.Interop.ResourceLoading;
|
||||||
|
using Penumbra.Interop.Services;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
|
@ -31,23 +38,39 @@ using CharacterBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBa
|
||||||
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
|
||||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
using Penumbra.Interop.Services;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
|
||||||
using ImGuiClip = OtterGui.ImGuiClip;
|
using ImGuiClip = OtterGui.ImGuiClip;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs;
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
public class Diagnostics(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
public void DrawDiagnostics()
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader("Diagnostics"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("##data", 4, ImGuiTableFlags.RowBg);
|
||||||
|
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
||||||
|
.Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer))))
|
||||||
|
{
|
||||||
|
var container = (IAsyncDataContainer) provider.GetRequiredService(type);
|
||||||
|
ImGuiUtil.DrawTableColumn(container.Name);
|
||||||
|
ImGuiUtil.DrawTableColumn(container.Time.ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(Functions.HumanReadableSize(container.Memory));
|
||||||
|
ImGuiUtil.DrawTableColumn(container.TotalCount.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class DebugTab : Window, ITab
|
public class DebugTab : Window, ITab
|
||||||
{
|
{
|
||||||
private readonly StartTracker _timer;
|
|
||||||
private readonly PerformanceTracker _performance;
|
private readonly PerformanceTracker _performance;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
private readonly CollectionManager _collectionManager;
|
private readonly CollectionManager _collectionManager;
|
||||||
private readonly ModManager _modManager;
|
private readonly ModManager _modManager;
|
||||||
private readonly ValidityChecker _validityChecker;
|
private readonly ValidityChecker _validityChecker;
|
||||||
private readonly HttpApi _httpApi;
|
private readonly HttpApi _httpApi;
|
||||||
private readonly ActorService _actorService;
|
private readonly ActorManager _actors;
|
||||||
private readonly DalamudServices _dalamud;
|
|
||||||
private readonly StainService _stains;
|
private readonly StainService _stains;
|
||||||
private readonly CharacterUtility _characterUtility;
|
private readonly CharacterUtility _characterUtility;
|
||||||
private readonly ResidentResourceManager _residentResources;
|
private readonly ResidentResourceManager _residentResources;
|
||||||
|
|
@ -64,16 +87,19 @@ public class DebugTab : Window, ITab
|
||||||
private readonly FrameworkManager _framework;
|
private readonly FrameworkManager _framework;
|
||||||
private readonly TextureManager _textureManager;
|
private readonly TextureManager _textureManager;
|
||||||
private readonly SkinFixer _skinFixer;
|
private readonly SkinFixer _skinFixer;
|
||||||
private readonly IdentifierService _identifier;
|
|
||||||
private readonly RedrawService _redraws;
|
private readonly RedrawService _redraws;
|
||||||
|
private readonly DictEmote _emotes;
|
||||||
|
private readonly Diagnostics _diagnostics;
|
||||||
|
private readonly IObjectTable _objects;
|
||||||
|
private readonly IClientState _clientState;
|
||||||
|
private readonly IpcTester _ipcTester;
|
||||||
|
|
||||||
public DebugTab(StartTracker timer, PerformanceTracker performance, Configuration config, CollectionManager collectionManager,
|
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, IObjectTable objects, IClientState clientState,
|
||||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorService actorService,
|
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains, CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||||
DalamudServices dalamud, StainService stains, CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
|
||||||
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
ResourceManagerService resourceManager, PenumbraIpcProviders ipc, CollectionResolver collectionResolver,
|
||||||
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache,
|
||||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||||
TextureManager textureManager, SkinFixer skinFixer, IdentifierService identifier, RedrawService redraws)
|
TextureManager textureManager, SkinFixer skinFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester)
|
||||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||||
{
|
{
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
|
@ -82,15 +108,13 @@ public class DebugTab : Window, ITab
|
||||||
MinimumSize = new Vector2(200, 200),
|
MinimumSize = new Vector2(200, 200),
|
||||||
MaximumSize = new Vector2(2000, 2000),
|
MaximumSize = new Vector2(2000, 2000),
|
||||||
};
|
};
|
||||||
_timer = timer;
|
|
||||||
_performance = performance;
|
_performance = performance;
|
||||||
_config = config;
|
_config = config;
|
||||||
_collectionManager = collectionManager;
|
_collectionManager = collectionManager;
|
||||||
_validityChecker = validityChecker;
|
_validityChecker = validityChecker;
|
||||||
_modManager = modManager;
|
_modManager = modManager;
|
||||||
_httpApi = httpApi;
|
_httpApi = httpApi;
|
||||||
_actorService = actorService;
|
_actors = actors;
|
||||||
_dalamud = dalamud;
|
|
||||||
_stains = stains;
|
_stains = stains;
|
||||||
_characterUtility = characterUtility;
|
_characterUtility = characterUtility;
|
||||||
_residentResources = residentResources;
|
_residentResources = residentResources;
|
||||||
|
|
@ -107,8 +131,12 @@ public class DebugTab : Window, ITab
|
||||||
_framework = framework;
|
_framework = framework;
|
||||||
_textureManager = textureManager;
|
_textureManager = textureManager;
|
||||||
_skinFixer = skinFixer;
|
_skinFixer = skinFixer;
|
||||||
_identifier = identifier;
|
|
||||||
_redraws = redraws;
|
_redraws = redraws;
|
||||||
|
_emotes = emotes;
|
||||||
|
_diagnostics = diagnostics;
|
||||||
|
_ipcTester = ipcTester;
|
||||||
|
_objects = objects;
|
||||||
|
_clientState = clientState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -130,6 +158,7 @@ public class DebugTab : Window, ITab
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DrawDebugTabGeneral();
|
DrawDebugTabGeneral();
|
||||||
|
_diagnostics.DrawDiagnostics();
|
||||||
DrawPerformanceTab();
|
DrawPerformanceTab();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawPathResolverDebug();
|
DrawPathResolverDebug();
|
||||||
|
|
@ -357,7 +386,6 @@ public class DebugTab : Window, ITab
|
||||||
ImGuiUtil.DrawTableColumn(name);
|
ImGuiUtil.DrawTableColumn(name);
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,11 +400,8 @@ public class DebugTab : Window, ITab
|
||||||
using (var start = TreeNode("Startup Performance", ImGuiTreeNodeFlags.DefaultOpen))
|
using (var start = TreeNode("Startup Performance", ImGuiTreeNodeFlags.DefaultOpen))
|
||||||
{
|
{
|
||||||
if (start)
|
if (start)
|
||||||
{
|
|
||||||
_timer.Draw("##startTimer", TimingExtensions.ToName);
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_performance.Draw("##performance", "Enable Runtime Performance Tracking", TimingExtensions.ToName);
|
_performance.Draw("##performance", "Enable Runtime Performance Tracking", TimingExtensions.ToName);
|
||||||
}
|
}
|
||||||
|
|
@ -391,6 +416,26 @@ public class DebugTab : Window, ITab
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
DrawSpecial("Current Player", _actors.GetCurrentPlayer());
|
||||||
|
DrawSpecial("Current Inspect", _actors.GetInspectPlayer());
|
||||||
|
DrawSpecial("Current Card", _actors.GetCardPlayer());
|
||||||
|
DrawSpecial("Current Glamour", _actors.GetGlamourPlayer());
|
||||||
|
|
||||||
|
foreach (var obj in _objects)
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
|
||||||
|
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
|
||||||
|
ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero
|
||||||
|
? string.Empty
|
||||||
|
: $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
||||||
|
var identifier = _actors.FromObject(obj, false, true, false);
|
||||||
|
ImGuiUtil.DrawTableColumn(_actors.ToString(identifier));
|
||||||
|
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
||||||
|
ImGuiUtil.DrawTableColumn(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
void DrawSpecial(string name, ActorIdentifier id)
|
void DrawSpecial(string name, ActorIdentifier id)
|
||||||
{
|
{
|
||||||
if (!id.IsValid)
|
if (!id.IsValid)
|
||||||
|
|
@ -399,27 +444,9 @@ public class DebugTab : Window, ITab
|
||||||
ImGuiUtil.DrawTableColumn(name);
|
ImGuiUtil.DrawTableColumn(name);
|
||||||
ImGuiUtil.DrawTableColumn(string.Empty);
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
ImGuiUtil.DrawTableColumn(string.Empty);
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(id));
|
ImGuiUtil.DrawTableColumn(_actors.ToString(id));
|
||||||
ImGuiUtil.DrawTableColumn(string.Empty);
|
ImGuiUtil.DrawTableColumn(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawSpecial("Current Player", _actorService.AwaitedService.GetCurrentPlayer());
|
|
||||||
DrawSpecial("Current Inspect", _actorService.AwaitedService.GetInspectPlayer());
|
|
||||||
DrawSpecial("Current Card", _actorService.AwaitedService.GetCardPlayer());
|
|
||||||
DrawSpecial("Current Glamour", _actorService.AwaitedService.GetGlamourPlayer());
|
|
||||||
|
|
||||||
foreach (var obj in _dalamud.Objects)
|
|
||||||
{
|
|
||||||
ImGuiUtil.DrawTableColumn($"{((GameObject*)obj.Address)->ObjectIndex}");
|
|
||||||
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
|
|
||||||
ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero
|
|
||||||
? string.Empty
|
|
||||||
: $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
|
||||||
var identifier = _actorService.AwaitedService.FromObject(obj, false, true, false);
|
|
||||||
ImGuiUtil.DrawTableColumn(_actorService.AwaitedService.ToString(identifier));
|
|
||||||
var id = obj.ObjectKind == ObjectKind.BattleNpc ? $"{identifier.DataId} | {obj.DataId}" : identifier.DataId.ToString();
|
|
||||||
ImGuiUtil.DrawTableColumn(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -616,7 +643,7 @@ public class DebugTab : Window, ITab
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
|
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing());
|
||||||
var dummy = ImGuiClip.FilteredClippedDraw(_identifier.AwaitedService.Emotes, skips,
|
var dummy = ImGuiClip.FilteredClippedDraw(_emotes, skips,
|
||||||
p => p.Key.Contains(_emoteSearchFile, StringComparison.OrdinalIgnoreCase)
|
p => p.Key.Contains(_emoteSearchFile, StringComparison.OrdinalIgnoreCase)
|
||||||
&& (_emoteSearchName.Length == 0
|
&& (_emoteSearchName.Length == 0
|
||||||
|| p.Value.Any(s => s.Name.ToDalamudString().TextValue.Contains(_emoteSearchName, StringComparison.OrdinalIgnoreCase))),
|
|| p.Value.Any(s => s.Name.ToDalamudString().TextValue.Contains(_emoteSearchName, StringComparison.OrdinalIgnoreCase))),
|
||||||
|
|
@ -804,7 +831,7 @@ public class DebugTab : Window, ITab
|
||||||
/// <summary> Draw information about the models, materials and resources currently loaded by the local player. </summary>
|
/// <summary> Draw information about the models, materials and resources currently loaded by the local player. </summary>
|
||||||
private unsafe void DrawPlayerModelInfo()
|
private unsafe void DrawPlayerModelInfo()
|
||||||
{
|
{
|
||||||
var player = _dalamud.ClientState.LocalPlayer;
|
var player = _clientState.LocalPlayer;
|
||||||
var name = player?.Name.ToString() ?? "NULL";
|
var name = player?.Name.ToString() ?? "NULL";
|
||||||
if (!ImGui.CollapsingHeader($"Player Model Info: {name}##Draw") || player == null)
|
if (!ImGui.CollapsingHeader($"Player Model Info: {name}##Draw") || player == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -929,11 +956,11 @@ public class DebugTab : Window, ITab
|
||||||
{
|
{
|
||||||
if (!ImGui.CollapsingHeader("IPC"))
|
if (!ImGui.CollapsingHeader("IPC"))
|
||||||
{
|
{
|
||||||
_ipc.Tester.UnsubscribeEvents();
|
_ipcTester.UnsubscribeEvents();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ipc.Tester.Draw();
|
_ipcTester.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Helper to print a property and its value in a 2-column table. </summary>
|
/// <summary> Helper to print a property and its value in a 2-column table. </summary>
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Components;
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -33,19 +35,23 @@ public class SettingsTab : ITab
|
||||||
private readonly ModFileSystemSelector _selector;
|
private readonly ModFileSystemSelector _selector;
|
||||||
private readonly CharacterUtility _characterUtility;
|
private readonly CharacterUtility _characterUtility;
|
||||||
private readonly ResidentResourceManager _residentResources;
|
private readonly ResidentResourceManager _residentResources;
|
||||||
private readonly DalamudServices _dalamud;
|
|
||||||
private readonly HttpApi _httpApi;
|
private readonly HttpApi _httpApi;
|
||||||
private readonly DalamudSubstitutionProvider _dalamudSubstitutionProvider;
|
private readonly DalamudSubstitutionProvider _dalamudSubstitutionProvider;
|
||||||
private readonly FileCompactor _compactor;
|
private readonly FileCompactor _compactor;
|
||||||
|
private readonly DalamudConfigService _dalamudConfig;
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly IDataManager _gameData;
|
||||||
|
|
||||||
private int _minimumX = int.MaxValue;
|
private int _minimumX = int.MaxValue;
|
||||||
private int _minimumY = int.MaxValue;
|
private int _minimumY = int.MaxValue;
|
||||||
|
|
||||||
public SettingsTab(Configuration config, FontReloader fontReloader, TutorialService tutorial, Penumbra penumbra,
|
public SettingsTab(DalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial,
|
||||||
FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, CharacterUtility characterUtility,
|
Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector,
|
||||||
ResidentResourceManager residentResources, DalamudServices dalamud, ModExportManager modExportManager, HttpApi httpApi,
|
CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi,
|
||||||
DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor)
|
DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig,
|
||||||
|
IDataManager gameData)
|
||||||
{
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fontReloader = fontReloader;
|
_fontReloader = fontReloader;
|
||||||
_tutorial = tutorial;
|
_tutorial = tutorial;
|
||||||
|
|
@ -55,11 +61,12 @@ public class SettingsTab : ITab
|
||||||
_selector = selector;
|
_selector = selector;
|
||||||
_characterUtility = characterUtility;
|
_characterUtility = characterUtility;
|
||||||
_residentResources = residentResources;
|
_residentResources = residentResources;
|
||||||
_dalamud = dalamud;
|
|
||||||
_modExportManager = modExportManager;
|
_modExportManager = modExportManager;
|
||||||
_httpApi = httpApi;
|
_httpApi = httpApi;
|
||||||
_dalamudSubstitutionProvider = dalamudSubstitutionProvider;
|
_dalamudSubstitutionProvider = dalamudSubstitutionProvider;
|
||||||
_compactor = compactor;
|
_compactor = compactor;
|
||||||
|
_dalamudConfig = dalamudConfig;
|
||||||
|
_gameData = gameData;
|
||||||
if (_compactor.CanCompact)
|
if (_compactor.CanCompact)
|
||||||
_compactor.Enabled = _config.UseFileSystemCompression;
|
_compactor.Enabled = _config.UseFileSystemCompression;
|
||||||
}
|
}
|
||||||
|
|
@ -164,14 +171,14 @@ public class SettingsTab : ITab
|
||||||
if (IsSubPathOf(programFiles, newName) || IsSubPathOf(programFilesX86, newName))
|
if (IsSubPathOf(programFiles, newName) || IsSubPathOf(programFilesX86, newName))
|
||||||
return ("Path is not allowed to be in ProgramFiles.", false);
|
return ("Path is not allowed to be in ProgramFiles.", false);
|
||||||
|
|
||||||
var dalamud = _dalamud.PluginInterface.ConfigDirectory.Parent!.Parent!;
|
var dalamud = _pluginInterface.ConfigDirectory.Parent!.Parent!;
|
||||||
if (IsSubPathOf(dalamud.FullName, newName))
|
if (IsSubPathOf(dalamud.FullName, newName))
|
||||||
return ("Path is not allowed to be inside your Dalamud directories.", false);
|
return ("Path is not allowed to be inside your Dalamud directories.", false);
|
||||||
|
|
||||||
if (Functions.GetDownloadsFolder(out var downloads) && IsSubPathOf(downloads, newName))
|
if (Functions.GetDownloadsFolder(out var downloads) && IsSubPathOf(downloads, newName))
|
||||||
return ("Path is not allowed to be inside your Downloads folder.", false);
|
return ("Path is not allowed to be inside your Downloads folder.", false);
|
||||||
|
|
||||||
var gameDir = _dalamud.GameData.GameData.DataPath.Parent!.Parent!.FullName;
|
var gameDir = _gameData.GameData.DataPath.Parent!.Parent!.FullName;
|
||||||
if (IsSubPathOf(gameDir, newName))
|
if (IsSubPathOf(gameDir, newName))
|
||||||
return ("Path is not allowed to be inside your game folder.", false);
|
return ("Path is not allowed to be inside your game folder.", false);
|
||||||
|
|
||||||
|
|
@ -368,21 +375,21 @@ public class SettingsTab : ITab
|
||||||
v =>
|
v =>
|
||||||
{
|
{
|
||||||
_config.HideUiWhenUiHidden = v;
|
_config.HideUiWhenUiHidden = v;
|
||||||
_dalamud.UiBuilder.DisableUserUiHide = !v;
|
_pluginInterface.UiBuilder.DisableUserUiHide = !v;
|
||||||
});
|
});
|
||||||
Checkbox("Hide Config Window when in Cutscenes",
|
Checkbox("Hide Config Window when in Cutscenes",
|
||||||
"Hide the Penumbra main window when you are currently watching a cutscene.", _config.HideUiInCutscenes,
|
"Hide the Penumbra main window when you are currently watching a cutscene.", _config.HideUiInCutscenes,
|
||||||
v =>
|
v =>
|
||||||
{
|
{
|
||||||
_config.HideUiInCutscenes = v;
|
_config.HideUiInCutscenes = v;
|
||||||
_dalamud.UiBuilder.DisableCutsceneUiHide = !v;
|
_pluginInterface.UiBuilder.DisableCutsceneUiHide = !v;
|
||||||
});
|
});
|
||||||
Checkbox("Hide Config Window when in GPose",
|
Checkbox("Hide Config Window when in GPose",
|
||||||
"Hide the Penumbra main window when you are currently in GPose mode.", _config.HideUiInGPose,
|
"Hide the Penumbra main window when you are currently in GPose mode.", _config.HideUiInGPose,
|
||||||
v =>
|
v =>
|
||||||
{
|
{
|
||||||
_config.HideUiInGPose = v;
|
_config.HideUiInGPose = v;
|
||||||
_dalamud.UiBuilder.DisableGposeUiHide = !v;
|
_pluginInterface.UiBuilder.DisableGposeUiHide = !v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -847,7 +854,7 @@ public class SettingsTab : ITab
|
||||||
/// <summary> Draw a checkbox that toggles the dalamud setting to wait for plugins on open. </summary>
|
/// <summary> Draw a checkbox that toggles the dalamud setting to wait for plugins on open. </summary>
|
||||||
private void DrawWaitForPluginsReflection()
|
private void DrawWaitForPluginsReflection()
|
||||||
{
|
{
|
||||||
if (!_dalamud.GetDalamudConfig(DalamudServices.WaitingForPluginsOption, out bool value))
|
if (!_dalamudConfig.GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool value))
|
||||||
{
|
{
|
||||||
using var disabled = ImRaii.Disabled();
|
using var disabled = ImRaii.Disabled();
|
||||||
Checkbox("Wait for Plugins on Startup (Disabled, can not access Dalamud Configuration)", string.Empty, false, v => { });
|
Checkbox("Wait for Plugins on Startup (Disabled, can not access Dalamud Configuration)", string.Empty, false, v => { });
|
||||||
|
|
@ -855,9 +862,12 @@ public class SettingsTab : ITab
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Checkbox("Wait for Plugins on Startup",
|
Checkbox("Wait for Plugins on Startup",
|
||||||
"Some mods need to change files that are loaded once when the game starts and never afterwards.\nThis can cause issues with Penumbra loading after the files are already loaded.\nThis setting causes the game to wait until certain plugins have finished loading, making those mods work (in the base collection).\n\nThis changes a setting in the Dalamud Configuration found at /xlsettings -> General.",
|
"Some mods need to change files that are loaded once when the game starts and never afterwards.\n"
|
||||||
|
+ "This can cause issues with Penumbra loading after the files are already loaded.\n"
|
||||||
|
+ "This setting causes the game to wait until certain plugins have finished loading, making those mods work (in the base collection).\n\n"
|
||||||
|
+ "This changes a setting in the Dalamud Configuration found at /xlsettings -> General.",
|
||||||
value,
|
value,
|
||||||
v => _dalamud.SetDalamudConfig(DalamudServices.WaitingForPluginsOption, v, "doWaitForPluginsOnStartup"));
|
v => _dalamudConfig.SetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, v, "doWaitForPluginsOnStartup"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ using Dalamud.Interface;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Penumbra.UI.AdvancedWindow;
|
using Penumbra.UI.AdvancedWindow;
|
||||||
using Penumbra.UI.Tabs;
|
using Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,7 @@
|
||||||
global using StartTracker = OtterGui.Classes.StartTimeTracker<Penumbra.Util.StartTimeType>;
|
|
||||||
global using PerformanceTracker = OtterGui.Classes.PerformanceTracker<Penumbra.Util.PerformanceType>;
|
global using PerformanceTracker = OtterGui.Classes.PerformanceTracker<Penumbra.Util.PerformanceType>;
|
||||||
|
|
||||||
namespace Penumbra.Util;
|
namespace Penumbra.Util;
|
||||||
|
|
||||||
public enum StartTimeType
|
|
||||||
{
|
|
||||||
Total,
|
|
||||||
Identifier,
|
|
||||||
Stains,
|
|
||||||
Items,
|
|
||||||
Actors,
|
|
||||||
Backup,
|
|
||||||
Mods,
|
|
||||||
Collections,
|
|
||||||
PathResolver,
|
|
||||||
Interface,
|
|
||||||
Api,
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum PerformanceType
|
public enum PerformanceType
|
||||||
{
|
{
|
||||||
UiMainWindow,
|
UiMainWindow,
|
||||||
|
|
@ -48,23 +32,6 @@ public enum PerformanceType
|
||||||
|
|
||||||
public static class TimingExtensions
|
public static class TimingExtensions
|
||||||
{
|
{
|
||||||
public static string ToName(this StartTimeType type)
|
|
||||||
=> type switch
|
|
||||||
{
|
|
||||||
StartTimeType.Total => "Total Construction",
|
|
||||||
StartTimeType.Identifier => "Identification Data",
|
|
||||||
StartTimeType.Stains => "Stain Data",
|
|
||||||
StartTimeType.Items => "Item Data",
|
|
||||||
StartTimeType.Actors => "Actor Data",
|
|
||||||
StartTimeType.Backup => "Checking Backups",
|
|
||||||
StartTimeType.Mods => "Loading Mods",
|
|
||||||
StartTimeType.Collections => "Loading Collections",
|
|
||||||
StartTimeType.Api => "Setting Up API",
|
|
||||||
StartTimeType.Interface => "Setting Up Interface",
|
|
||||||
StartTimeType.PathResolver => "Setting Up Path Resolver",
|
|
||||||
_ => $"Unknown {(int)type}",
|
|
||||||
};
|
|
||||||
|
|
||||||
public static string ToName(this PerformanceType type)
|
public static string ToName(this PerformanceType type)
|
||||||
=> type switch
|
=> type switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ottergui": {
|
"ottergui": {
|
||||||
"type": "Project"
|
"type": "Project",
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.Extensions.DependencyInjection": "[7.0.0, )"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"penumbra.api": {
|
"penumbra.api": {
|
||||||
"type": "Project"
|
"type": "Project"
|
||||||
|
|
@ -104,7 +107,8 @@
|
||||||
"penumbra.gamedata": {
|
"penumbra.gamedata": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Penumbra.Api": "[1.0.8, )",
|
"OtterGui": "[1.0.0, )",
|
||||||
|
"Penumbra.Api": "[1.0.13, )",
|
||||||
"Penumbra.String": "[1.0.4, )"
|
"Penumbra.String": "[1.0.4, )"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"Description": "Runtime mod loader and manager.",
|
"Description": "Runtime mod loader and manager.",
|
||||||
"InternalName": "Penumbra",
|
"InternalName": "Penumbra",
|
||||||
"AssemblyVersion": "0.8.3.1",
|
"AssemblyVersion": "0.8.3.1",
|
||||||
"TestingAssemblyVersion": "0.8.3.1",
|
"TestingAssemblyVersion": "0.8.3.2",
|
||||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 9,
|
"DalamudApiLevel": 9,
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
"LoadRequiredState": 2,
|
"LoadRequiredState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/0.8.3.1/Penumbra.zip",
|
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/0.8.3.1/Penumbra.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/0.8.3.1/Penumbra.zip",
|
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_0.8.3.2/Penumbra.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/0.8.3.1/Penumbra.zip",
|
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/0.8.3.1/Penumbra.zip",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue