From 96f0479b53b62b41f9ccd136c87884d35ecc1234 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:42:29 +0200 Subject: [PATCH 01/73] Some cleanup. --- OtterGui | 2 +- Penumbra.GameData | 2 +- Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs | 1 - Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs | 1 - Penumbra/UI/Tabs/Debug/DebugTab.cs | 5 ++--- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/OtterGui b/OtterGui index 07a00913..276327f8 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 07a009134bf5eb7da9a54ba40e82c88fc613544a +Subproject commit 276327f812e2f7e6aac7aee9e5ef0a560b065765 diff --git a/Penumbra.GameData b/Penumbra.GameData index b7fdfe9d..c8708ec5 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit b7fdfe9d19f7e3229834480db446478b0bf6acee +Subproject commit c8708ec5153cb60c9e43b2c53d02b81b2c8175f9 diff --git a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs index 4aae45a2..515f6ff4 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs @@ -2,7 +2,6 @@ using OtterGui.Classes; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.GameData.Structs; -using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Groups; using Penumbra.Mods.Settings; diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs index 9d1ab78a..4ab1c6aa 100644 --- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -4,7 +4,6 @@ using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget; -using OtterGui.Widgets; using OtterGuiInternal.Utility; using Penumbra.GameData.Structs; using Penumbra.Mods.Groups; diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 5b82a523..7c6cd01e 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -1,5 +1,4 @@ using Dalamud.Interface; -using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; @@ -43,7 +42,6 @@ using Penumbra.Api.IpcTester; using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.GameData.Files.StainMapStructs; -using Penumbra.UI.AdvancedWindow; using Penumbra.UI.AdvancedWindow.Materials; namespace Penumbra.UI.Tabs.Debug; @@ -721,7 +719,8 @@ public class DebugTab : Window, ITab, IUiService if (!tree) continue; - using var table = Table("##table", data.Colors.Length + data.Scalars.Length, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); + using var table = Table("##table", data.Colors.Length + data.Scalars.Length, + ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) continue; From 35492837690b93761a7e9c2c52f2c20cb28b5765 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:42:47 +0200 Subject: [PATCH 02/73] Order meta entries. --- .../UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs | 5 ++++- .../UI/AdvancedWindow/Meta/EqpMetaDrawer.cs | 5 ++++- .../UI/AdvancedWindow/Meta/EstMetaDrawer.cs | 6 +++++- .../Meta/GlobalEqpMetaDrawer.cs | 5 ++++- .../UI/AdvancedWindow/Meta/GmpMetaDrawer.cs | 4 +++- .../UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 21 ++++++++++++------- .../UI/AdvancedWindow/Meta/RspMetaDrawer.cs | 5 ++++- 7 files changed, 38 insertions(+), 13 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs index 5206ece8..f586045c 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -61,7 +61,10 @@ public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFil } protected override IEnumerable<(EqdpIdentifier, EqdpEntryInternal)> Enumerate() - => Editor.Eqdp.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Eqdp.OrderBy(kvp => kvp.Key.SetId) + .ThenBy(kvp => kvp.Key.GenderRace) + .ThenBy(kvp => kvp.Key.Slot) + .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref EqdpIdentifier identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs index 56c06bc9..b1031b44 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs @@ -59,7 +59,10 @@ public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile } protected override IEnumerable<(EqpIdentifier, EqpEntryInternal)> Enumerate() - => Editor.Eqp.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Eqp + .OrderBy(kvp => kvp.Key.SetId) + .ThenBy(kvp => kvp.Key.Slot) + .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref EqpIdentifier identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs index 5c3c5df5..628cee40 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs @@ -58,7 +58,11 @@ public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile } protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate() - => Editor.Est.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Est + .OrderBy(kvp => kvp.Key.SetId) + .ThenBy(kvp => kvp.Key.GenderRace) + .ThenBy(kvp => kvp.Key.Slot) + .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref EstIdentifier identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs index 130831a0..bc2e1bde 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs @@ -47,7 +47,10 @@ public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager me } protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate() - => Editor.GlobalEqp.Select(identifier => (identifier, (byte)0)); + => Editor.GlobalEqp + .OrderBy(identifier => identifier.Type) + .ThenBy(identifier => identifier.Condition) + .Select(identifier => (identifier, (byte)0)); private static void DrawIdentifierInput(ref GlobalEqpManipulation identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs index 87ed21dc..1e91731d 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs @@ -57,7 +57,9 @@ public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile } protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() - => Editor.Gmp.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Gmp + .OrderBy(kvp => kvp.Key.SetId) + .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref GmpIdentifier identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index 58f626fc..e33eb1aa 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -140,7 +140,14 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate() - => Editor.Imc.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Imc + .OrderBy(kvp => kvp.Key.ObjectType) + .ThenBy(kvp => kvp.Key.PrimaryId) + .ThenBy(kvp => kvp.Key.EquipSlot) + .ThenBy(kvp => kvp.Key.BodySlot) + .ThenBy(kvp => kvp.Key.SecondaryId) + .ThenBy(kvp => kvp.Key.Variant) + .Select(kvp => (kvp.Key, kvp.Value)); public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) { @@ -149,18 +156,18 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile if (ret) { - var equipSlot = type switch + var (equipSlot, secondaryId) = type switch { - ObjectType.Equipment => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, - ObjectType.DemiHuman => identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, - ObjectType.Accessory => identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears, - _ => EquipSlot.Unknown, + ObjectType.Equipment => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, (SecondaryId) 0), + ObjectType.DemiHuman => (identifier.EquipSlot.IsEquipment() ? identifier.EquipSlot : EquipSlot.Head, identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId), + ObjectType.Accessory => (identifier.EquipSlot.IsAccessory() ? identifier.EquipSlot : EquipSlot.Ears, (SecondaryId)0), + _ => (EquipSlot.Unknown, identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId), }; identifier = identifier with { ObjectType = type, EquipSlot = equipSlot, - SecondaryId = identifier.SecondaryId == 0 ? 1 : identifier.SecondaryId, + SecondaryId = secondaryId, }; } diff --git a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs index be02e321..6d819b16 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs @@ -58,7 +58,10 @@ public sealed class RspMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile } protected override IEnumerable<(RspIdentifier, RspEntry)> Enumerate() - => Editor.Rsp.Select(kvp => (kvp.Key, kvp.Value)); + => Editor.Rsp + .OrderBy(kvp => kvp.Key.SubRace) + .ThenBy(kvp => kvp.Key.Attribute) + .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref RspIdentifier identifier) { From a2237773e315be9f3209ab8c6a462e5cdcf8837e Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:43:11 +0200 Subject: [PATCH 03/73] Update packages. --- Penumbra/Import/Models/Export/MeshExporter.cs | 13 +- .../Import/Models/Export/VertexFragment.cs | 140 ++++++++++++------ .../Import/Models/Import/SubMeshImporter.cs | 2 +- Penumbra/Import/TexToolsImport.cs | 2 +- Penumbra/Import/TexToolsImporter.Archives.cs | 12 +- Penumbra/Penumbra.csproj | 8 +- Penumbra/Services/MigrationManager.cs | 2 +- Penumbra/packages.lock.json | 54 ++++--- 8 files changed, 139 insertions(+), 94 deletions(-) diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 219a046e..3a57ab55 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -1,4 +1,6 @@ using System.Collections.Immutable; +using System.Text.Json; +using System.Text.Json.Nodes; using Lumina.Extensions; using OtterGui; using Penumbra.GameData.Files; @@ -23,11 +25,11 @@ public class MeshExporter ? scene.AddSkinnedMesh(data.Mesh, Matrix4x4.Identity, [.. skeleton.Value.Joints]) : scene.AddRigidMesh(data.Mesh, Matrix4x4.Identity); - var extras = new Dictionary(data.Attributes.Length); + var node = new JsonObject(); foreach (var attribute in data.Attributes) - extras.Add(attribute, true); + node[attribute] = true; - instance.WithExtras(JsonContent.CreateFrom(extras)); + instance.WithExtras(node); } } } @@ -233,10 +235,7 @@ public class MeshExporter // Named morph targets aren't part of the specification, however `MESH.extras.targetNames` // is a commonly-accepted means of providing the data. - meshBuilder.Extras = JsonContent.CreateFrom(new Dictionary() - { - { "targetNames", shapeNames }, - }); + meshBuilder.Extras = new JsonObject { ["targetNames"] = JsonSerializer.SerializeToNode(shapeNames) }; string[] attributes = []; var maxAttribute = 31 - BitOperations.LeadingZeroCount(attributeMask); diff --git a/Penumbra/Import/Models/Export/VertexFragment.cs b/Penumbra/Import/Models/Export/VertexFragment.cs index 7a82e994..eff34d54 100644 --- a/Penumbra/Import/Models/Export/VertexFragment.cs +++ b/Penumbra/Import/Models/Export/VertexFragment.cs @@ -1,4 +1,6 @@ +using System; using SharpGLTF.Geometry.VertexTypes; +using SharpGLTF.Memory; using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Export; @@ -11,35 +13,40 @@ and there's reason to overhaul the export pipeline. public struct VertexColorFfxiv : IVertexCustom { - // NOTE: We only realistically require UNSIGNED_BYTE for this, however Blender 3.6 errors on that (fixed in 4.0). - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, true)] + public IEnumerable> GetEncodingAttributes() + { + // NOTE: We only realistically require UNSIGNED_BYTE for this, however Blender 3.6 errors on that (fixed in 4.0). + yield return new KeyValuePair("_FFXIV_COLOR", + new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); + } + public Vector4 FfxivColor; - public int MaxColors => 0; + public int MaxColors + => 0; - public int MaxTextCoords => 0; + public int MaxTextCoords + => 0; private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; - public IEnumerable CustomAttributes => CustomNames; + + public IEnumerable CustomAttributes + => CustomNames; public VertexColorFfxiv(Vector4 ffxivColor) - { - FfxivColor = ffxivColor; - } + => FfxivColor = ffxivColor; public void Add(in VertexMaterialDelta delta) - { - } + { } public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, Vector2.Zero, Vector2.Zero); + => new(Vector4.Zero, Vector4.Zero, Vector2.Zero, Vector2.Zero); public Vector2 GetTexCoord(int index) => throw new ArgumentOutOfRangeException(nameof(index)); public void SetTexCoord(int setIndex, Vector2 coord) - { - } + { } public bool TryGetCustomAttribute(string attributeName, out object? value) { @@ -65,12 +72,17 @@ public struct VertexColorFfxiv : IVertexCustom => throw new ArgumentOutOfRangeException(nameof(index)); public void SetColor(int setIndex, Vector4 color) - { - } + { } public void Validate() { - var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + var components = new[] + { + FfxivColor.X, + FfxivColor.Y, + FfxivColor.Z, + FfxivColor.W, + }; if (components.Any(component => component < 0 || component > 1)) throw new ArgumentOutOfRangeException(nameof(FfxivColor)); } @@ -78,22 +90,32 @@ public struct VertexColorFfxiv : IVertexCustom public struct VertexTexture1ColorFfxiv : IVertexCustom { - [VertexAttribute("TEXCOORD_0")] + public IEnumerable> GetEncodingAttributes() + { + yield return new KeyValuePair("TEXCOORD_0", + new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false)); + yield return new KeyValuePair("_FFXIV_COLOR", + new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); + } + public Vector2 TexCoord0; - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, true)] public Vector4 FfxivColor; - public int MaxColors => 0; + public int MaxColors + => 0; - public int MaxTextCoords => 1; + public int MaxTextCoords + => 1; private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; - public IEnumerable CustomAttributes => CustomNames; + + public IEnumerable CustomAttributes + => CustomNames; public VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) { - TexCoord0 = texCoord0; + TexCoord0 = texCoord0; FfxivColor = ffxivColor; } @@ -103,9 +125,7 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom } public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - { - return new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), Vector2.Zero); - } + => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), Vector2.Zero); public Vector2 GetTexCoord(int index) => index switch @@ -116,8 +136,10 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom public void SetTexCoord(int setIndex, Vector2 coord) { - if (setIndex == 0) TexCoord0 = coord; - if (setIndex >= 1) throw new ArgumentOutOfRangeException(nameof(setIndex)); + if (setIndex == 0) + TexCoord0 = coord; + if (setIndex >= 1) + throw new ArgumentOutOfRangeException(nameof(setIndex)); } public bool TryGetCustomAttribute(string attributeName, out object? value) @@ -144,12 +166,17 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom => throw new ArgumentOutOfRangeException(nameof(index)); public void SetColor(int setIndex, Vector4 color) - { - } + { } public void Validate() { - var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + var components = new[] + { + FfxivColor.X, + FfxivColor.Y, + FfxivColor.Z, + FfxivColor.W, + }; if (components.Any(component => component < 0 || component > 1)) throw new ArgumentOutOfRangeException(nameof(FfxivColor)); } @@ -157,26 +184,35 @@ public struct VertexTexture1ColorFfxiv : IVertexCustom public struct VertexTexture2ColorFfxiv : IVertexCustom { - [VertexAttribute("TEXCOORD_0")] + public IEnumerable> GetEncodingAttributes() + { + yield return new KeyValuePair("TEXCOORD_0", + new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false)); + yield return new KeyValuePair("TEXCOORD_1", + new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false)); + yield return new KeyValuePair("_FFXIV_COLOR", + new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); + } + public Vector2 TexCoord0; - - [VertexAttribute("TEXCOORD_1")] public Vector2 TexCoord1; - - [VertexAttribute("_FFXIV_COLOR", EncodingType.UNSIGNED_SHORT, true)] public Vector4 FfxivColor; - public int MaxColors => 0; + public int MaxColors + => 0; - public int MaxTextCoords => 2; + public int MaxTextCoords + => 2; private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; - public IEnumerable CustomAttributes => CustomNames; + + public IEnumerable CustomAttributes + => CustomNames; public VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector4 ffxivColor) { - TexCoord0 = texCoord0; - TexCoord1 = texCoord1; + TexCoord0 = texCoord0; + TexCoord1 = texCoord1; FfxivColor = ffxivColor; } @@ -187,9 +223,7 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom } public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - { - return new VertexMaterialDelta(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); - } + => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); public Vector2 GetTexCoord(int index) => index switch @@ -201,9 +235,12 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom public void SetTexCoord(int setIndex, Vector2 coord) { - if (setIndex == 0) TexCoord0 = coord; - if (setIndex == 1) TexCoord1 = coord; - if (setIndex >= 2) throw new ArgumentOutOfRangeException(nameof(setIndex)); + if (setIndex == 0) + TexCoord0 = coord; + if (setIndex == 1) + TexCoord1 = coord; + if (setIndex >= 2) + throw new ArgumentOutOfRangeException(nameof(setIndex)); } public bool TryGetCustomAttribute(string attributeName, out object? value) @@ -230,12 +267,17 @@ public struct VertexTexture2ColorFfxiv : IVertexCustom => throw new ArgumentOutOfRangeException(nameof(index)); public void SetColor(int setIndex, Vector4 color) - { - } + { } public void Validate() { - var components = new[] { FfxivColor.X, FfxivColor.Y, FfxivColor.Z, FfxivColor.W }; + var components = new[] + { + FfxivColor.X, + FfxivColor.Y, + FfxivColor.Z, + FfxivColor.W, + }; if (components.Any(component => component < 0 || component > 1)) throw new ArgumentOutOfRangeException(nameof(FfxivColor)); } diff --git a/Penumbra/Import/Models/Import/SubMeshImporter.cs b/Penumbra/Import/Models/Import/SubMeshImporter.cs index e81bb622..df08eea3 100644 --- a/Penumbra/Import/Models/Import/SubMeshImporter.cs +++ b/Penumbra/Import/Models/Import/SubMeshImporter.cs @@ -61,7 +61,7 @@ public class SubMeshImporter try { - _morphNames = node.Mesh.Extras.GetNode("targetNames").Deserialize>(); + _morphNames = node.Mesh.Extras["targetNames"].Deserialize>(); } catch { diff --git a/Penumbra/Import/TexToolsImport.cs b/Penumbra/Import/TexToolsImport.cs index ba089662..fed06573 100644 --- a/Penumbra/Import/TexToolsImport.cs +++ b/Penumbra/Import/TexToolsImport.cs @@ -148,7 +148,7 @@ public partial class TexToolsImporter : IDisposable // You can in no way rely on any file paths in TTMPs so we need to just do this, sorry private static ZipArchiveEntry? FindZipEntry(ZipArchive file, string fileName) - => file.Entries.FirstOrDefault(e => !e.IsDirectory && e.Key.Contains(fileName)); + => file.Entries.FirstOrDefault(e => e is { IsDirectory: false, Key: not null } && e.Key.Contains(fileName)); private static string GetStringFromZipEntry(ZipArchiveEntry entry, Encoding encoding) { diff --git a/Penumbra/Import/TexToolsImporter.Archives.cs b/Penumbra/Import/TexToolsImporter.Archives.cs index dea343c6..febbe179 100644 --- a/Penumbra/Import/TexToolsImporter.Archives.cs +++ b/Penumbra/Import/TexToolsImporter.Archives.cs @@ -82,7 +82,7 @@ public partial class TexToolsImporter if (name.Length == 0) throw new Exception("Invalid mod archive: mod meta has no name."); - using var f = File.OpenWrite(Path.Combine(_currentModDirectory.FullName, reader.Entry.Key)); + using var f = File.OpenWrite(Path.Combine(_currentModDirectory.FullName, reader.Entry.Key!)); s.Seek(0, SeekOrigin.Begin); s.WriteTo(f); } @@ -155,13 +155,9 @@ public partial class TexToolsImporter ret = directory; // Check that all other files are also contained in the top-level directory. - if (ret.IndexOfAny(new[] - { - '/', - '\\', - }) - >= 0 - || !archive.Entries.All(e => e.Key.StartsWith(ret) && (e.Key.Length == ret.Length || e.Key[ret.Length] is '/' or '\\'))) + if (ret.IndexOfAny(['/', '\\']) >= 0 + || !archive.Entries.All(e + => e.Key != null && e.Key.StartsWith(ret) && (e.Key.Length == ret.Length || e.Key[ret.Length] is '/' or '\\'))) throw new Exception( "Invalid mod archive: meta.json in wrong location. It needs to be either at root or one directory deep, in which all other files must be nested too."); } diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index 8e143e3c..f42d16ad 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -86,11 +86,11 @@ - + - - - + + + diff --git a/Penumbra/Services/MigrationManager.cs b/Penumbra/Services/MigrationManager.cs index 9041fbd0..aa2d445e 100644 --- a/Penumbra/Services/MigrationManager.cs +++ b/Penumbra/Services/MigrationManager.cs @@ -242,7 +242,7 @@ public class MigrationManager(Configuration config) : IService return; } - var path = Path.Combine(directory, reader.Entry.Key); + var path = Path.Combine(directory, reader.Entry.Key!); using var s = new MemoryStream(); using var e = reader.OpenEntryStream(); e.CopyTo(s); diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index 8e7106dd..fd3a0a9e 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -4,11 +4,11 @@ "net8.0-windows7.0": { "EmbedIO": { "type": "Direct", - "requested": "[3.4.3, )", - "resolved": "3.4.3", - "contentHash": "YM6hpZNAfvbbixfG9T4lWDGfF0D/TqutbTROL4ogVcHKwPF1hp+xS3ABwd3cxxTxvDFkj/zZl57QgWuFA8Igxw==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "YU4j+3XvuO8/VPkNf7KWOF1TpMhnyVhXnPsG1mvnDhTJ9D5BZOFXVDvCpE/SkQ1AJ0Aa+dXOVSW3ntgmLL7aJg==", "dependencies": { - "Unosquare.Swan.Lite": "3.0.0" + "Unosquare.Swan.Lite": "3.1.0" } }, "PeNet": { @@ -23,23 +23,26 @@ }, "SharpCompress": { "type": "Direct", - "requested": "[0.33.0, )", - "resolved": "0.33.0", - "contentHash": "FlHfpTAADzaSlVCBF33iKJk9UhOr3Xj+r5LXbW2GzqYr0SrhiOf6shLX2LC2fqs7g7d+YlwKbBXqWFtb+e7icw==" + "requested": "[0.37.2, )", + "resolved": "0.37.2", + "contentHash": "cFBpTct57aubLQXkdqMmgP8GGTFRh7fnRWP53lgE/EYUpDZJ27SSvTkdjB4OYQRZ20SJFpzczUquKLbt/9xkhw==", + "dependencies": { + "ZstdSharp.Port": "0.8.0" + } }, "SharpGLTF.Core": { "type": "Direct", - "requested": "[1.0.0-alpha0030, )", - "resolved": "1.0.0-alpha0030", - "contentHash": "HVL6PcrM0H/uEk96nRZfhtPeYvSFGHnni3g1aIckot2IWVp0jLMH5KWgaWfsatEz4Yds3XcdSLUWmJZivDBUPA==" + "requested": "[1.0.1, )", + "resolved": "1.0.1", + "contentHash": "ykeV1oNHcJrEJE7s0pGAsf/nYGYY7wqF9nxCMxJUjp/WdW+UUgR1cGdbAa2lVZPkiXEwLzWenZ5wPz7yS0Gj9w==" }, "SharpGLTF.Toolkit": { "type": "Direct", - "requested": "[1.0.0-alpha0030, )", - "resolved": "1.0.0-alpha0030", - "contentHash": "nsoJWAFhXgEky9bVCY0zLeZVDx+S88u7VjvuebvMb6dJiNyFOGF6FrrMHiJe+x5pcVBxxlc3VoXliBF7r/EqYA==", + "requested": "[1.0.1, )", + "resolved": "1.0.1", + "contentHash": "LYBjHdHW5Z8R1oT1iI04si3559tWdZ3jTdHfDEu0jqhuyU8w3oJRLFUoDfVeCOI5zWXlVQPtlpjhH9XTfFFAcA==", "dependencies": { - "SharpGLTF.Runtime": "1.0.0-alpha0030" + "SharpGLTF.Runtime": "1.0.1" } }, "SixLabors.ImageSharp": { @@ -50,8 +53,8 @@ }, "JetBrains.Annotations": { "type": "Transitive", - "resolved": "2023.3.0", - "contentHash": "PHfnvdBUdGaTVG9bR/GEfxgTwWM0Z97Y6X3710wiljELBISipSfF5okn/vz+C2gfO+ihoEyVPjaJwn8ZalVukA==" + "resolved": "2024.2.0", + "contentHash": "GNnqCFW/163p1fOehKx0CnAqjmpPrUSqrgfHM6qca+P+RN39C9rhlfZHQpJhxmQG/dkOYe/b3Z0P8b6Kv5m1qw==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", @@ -73,10 +76,10 @@ }, "SharpGLTF.Runtime": { "type": "Transitive", - "resolved": "1.0.0-alpha0030", - "contentHash": "Ysn+fyj9EVXj6mfG0BmzSTBGNi/QvcnTrMd54dBMOlI/TsMRvnOY3JjTn0MpeH2CgHXX4qogzlDt4m+rb3n4Og==", + "resolved": "1.0.1", + "contentHash": "KsgEBKLfsEnu2IPeKaWp4Ih97+kby17IohrAB6Ev8gET18iS80nKMW/APytQWpenMmcWU06utInpANqyrwRlDg==", "dependencies": { - "SharpGLTF.Core": "1.0.0-alpha0030" + "SharpGLTF.Core": "1.0.1" } }, "System.Formats.Asn1": { @@ -99,16 +102,21 @@ }, "Unosquare.Swan.Lite": { "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "noPwJJl1Q9uparXy1ogtkmyAPGNfSGb0BLT1292nFH1jdMKje6o2kvvrQUvF9Xklj+IoiAI0UzF6Aqxlvo10lw==", + "resolved": "3.1.0", + "contentHash": "X3s5QE/KMj3WAPFqFve7St+Ds10BB50u8kW8PmKIn7FVkn7yEXe9Yxr2htt1WV85DRqfFR0MN/BUNHkGHtL4OQ==", "dependencies": { "System.ValueTuple": "4.5.0" } }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.8.0", + "contentHash": "Z62eNBIu8E8YtbqlMy57tK3dV1+m2b9NhPeaYovB5exmLKvrGCqOhJTzrEUH5VyUWU6vwX3c1XHJGhW5HVs8dA==" + }, "ottergui": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2023.3.0, )", + "JetBrains.Annotations": "[2024.2.0, )", "Microsoft.Extensions.DependencyInjection": "[8.0.0, )" } }, @@ -122,7 +130,7 @@ "type": "Project", "dependencies": { "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.2.0, )", + "Penumbra.Api": "[5.3.0, )", "Penumbra.String": "[1.0.4, )" } }, From 726340e4f8cc4a8ab22f9629e5362a800f3ae962 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 24 Aug 2024 20:45:18 +0200 Subject: [PATCH 04/73] Meh. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index c8708ec5..c43c5cac 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c8708ec5153cb60c9e43b2c53d02b81b2c8175f9 +Subproject commit c43c5cac4cee092bf0aed8d46bab112b037ef8f2 From f3346c5d7e52f1ba86332ee2e30f77f144bf9ee3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 26 Aug 2024 18:20:29 +0200 Subject: [PATCH 05/73] Add Targa support. --- Penumbra/Import/Textures/Texture.cs | 15 ++++++++++++ Penumbra/Import/Textures/TextureDrawer.cs | 2 +- Penumbra/Import/Textures/TextureManager.cs | 23 +++++++++++-------- .../AdvancedWindow/ModEditWindow.Textures.cs | 1 + 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Penumbra/Import/Textures/Texture.cs b/Penumbra/Import/Textures/Texture.cs index c5207e94..ae0aabd9 100644 --- a/Penumbra/Import/Textures/Texture.cs +++ b/Penumbra/Import/Textures/Texture.cs @@ -10,6 +10,21 @@ public enum TextureType Tex, Png, Bitmap, + Targa, +} + +internal static class TextureTypeExtensions +{ + public static TextureType ReduceToBehaviour(this TextureType type) + => type switch + { + TextureType.Dds => TextureType.Dds, + TextureType.Tex => TextureType.Tex, + TextureType.Png => TextureType.Png, + TextureType.Bitmap => TextureType.Png, + TextureType.Targa => TextureType.Png, + _ => TextureType.Unknown, + }; } public sealed class Texture : IDisposable diff --git a/Penumbra/Import/Textures/TextureDrawer.cs b/Penumbra/Import/Textures/TextureDrawer.cs index c83604e4..b0a65ac0 100644 --- a/Penumbra/Import/Textures/TextureDrawer.cs +++ b/Penumbra/Import/Textures/TextureDrawer.cs @@ -66,7 +66,7 @@ public static class TextureDrawer current.Load(textures, paths[0]); } - fileDialog.OpenFilePicker("Open Image...", "Textures{.png,.dds,.tex}", UpdatePath, 1, startPath, false); + fileDialog.OpenFilePicker("Open Image...", "Textures{.png,.dds,.tex,.tga}", UpdatePath, 1, startPath, false); } ImGui.SameLine(); diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index cc785d02..4afc8a56 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -165,11 +165,13 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur return; } + var imageTypeBehaviour = image.Type.ReduceToBehaviour(); var dds = _type switch { - CombinedTexture.TextureSaveType.AsIs when image.Type is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, + CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, + cancel, rgba, width, height), - CombinedTexture.TextureSaveType.AsIs when image.Type is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), + CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC7 => ConvertToCompressedDds(image, _mipMaps, true, cancel, rgba, width, height), @@ -218,7 +220,9 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur => Path.GetExtension(path).ToLowerInvariant() switch { ".dds" => (LoadDds(path), TextureType.Dds), - ".png" => (LoadPng(path), TextureType.Png), + ".png" => (LoadImageSharp(path), TextureType.Png), + ".tga" => (LoadImageSharp(path), TextureType.Targa), + ".bmp" => (LoadImageSharp(path), TextureType.Bitmap), ".tex" => (LoadTex(path), TextureType.Tex), _ => throw new Exception($"Extension {Path.GetExtension(path)} unknown."), }; @@ -234,17 +238,17 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public BaseImage LoadDds(string path) => ScratchImage.LoadDDS(path); - /// Load a .png file from drive using ImageSharp. - public BaseImage LoadPng(string path) + /// Load a supported file type from drive using ImageSharp. + public BaseImage LoadImageSharp(string path) { using var stream = File.OpenRead(path); return Image.Load(stream); } - /// Convert an existing image to .png. Does not create a deep copy of an existing .png and just returns the existing one. + /// Convert an existing image to ImageSharp. Does not create a deep copy of an existing ImageSharp file and just returns the existing one. public static BaseImage ConvertToPng(BaseImage input, CancellationToken cancel, byte[]? rgba = null, int width = 0, int height = 0) { - switch (input.Type) + switch (input.Type.ReduceToBehaviour()) { case TextureType.Png: return input; case TextureType.Dds: @@ -261,7 +265,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public static BaseImage ConvertToRgbaDds(BaseImage input, bool mipMaps, CancellationToken cancel, byte[]? rgba = null, int width = 0, int height = 0) { - switch (input.Type) + switch (input.Type.ReduceToBehaviour()) { case TextureType.Png: { @@ -291,7 +295,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, int width = 0, int height = 0) { - switch (input.Type) + switch (input.Type.ReduceToBehaviour()) { case TextureType.Png: { @@ -470,6 +474,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur TextureType.Dds => $"Custom {_width} x {_height} {_image.Format} Image", TextureType.Tex => $"Custom {_width} x {_height} {_image.Format} Image", TextureType.Png => $"Custom {_width} x {_height} .png Image", + TextureType.Targa => $"Custom {_width} x {_height} .tga Image", TextureType.Bitmap => $"Custom {_width} x {_height} RGBA Image", _ => "Unknown Image", }; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 652ecb49..67a27a0b 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -329,5 +329,6 @@ public partial class ModEditWindow ".png", ".dds", ".tex", + ".tga", }; } From c4853434c8842ee8fa390e5b716134eca407dbfd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 26 Aug 2024 18:25:43 +0200 Subject: [PATCH 06/73] Whatever. --- Penumbra/Import/Textures/TextureManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 4afc8a56..996b5dbf 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -474,7 +474,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur TextureType.Dds => $"Custom {_width} x {_height} {_image.Format} Image", TextureType.Tex => $"Custom {_width} x {_height} {_image.Format} Image", TextureType.Png => $"Custom {_width} x {_height} .png Image", - TextureType.Targa => $"Custom {_width} x {_height} .tga Image", + TextureType.Targa => $"Custom {_width} x {_height} .tga Image", TextureType.Bitmap => $"Custom {_width} x {_height} RGBA Image", _ => "Unknown Image", }; From ded910d8a128b7ffb9cc9483c201df847a2b9e4c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 26 Aug 2024 21:21:38 +0200 Subject: [PATCH 07/73] Add Targa export. --- Penumbra.Api | 2 +- Penumbra/Api/Api/EditingApi.cs | 2 + Penumbra/Import/Textures/CombinedTexture.cs | 12 ++++ Penumbra/Import/Textures/TextureManager.cs | 56 ++++++++++++++----- .../AdvancedWindow/ModEditWindow.Textures.cs | 17 +++--- 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index 552246e5..a38e9bcf 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 552246e595ffab2aaba2c75f578d564f8938fc9a +Subproject commit a38e9bcfb80c456102945bbb4c59f5621cae0442 diff --git a/Penumbra/Api/Api/EditingApi.cs b/Penumbra/Api/Api/EditingApi.cs index 93345053..e50b7a1b 100644 --- a/Penumbra/Api/Api/EditingApi.cs +++ b/Penumbra/Api/Api/EditingApi.cs @@ -10,6 +10,7 @@ public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IA => textureType switch { TextureType.Png => textureManager.SavePng(inputFile, outputFile), + TextureType.Targa => textureManager.SaveTga(inputFile, outputFile), TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile), TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile), TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile), @@ -26,6 +27,7 @@ public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IA => textureType switch { TextureType.Png => textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), + TextureType.Targa => textureManager.SaveTga(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width), diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index 98b87ac3..c1a22088 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -55,6 +55,14 @@ public partial class CombinedTexture : IDisposable SaveTask = textures.SavePng(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height); } + public void SaveAsTarga(TextureManager textures, string path) + { + if (!IsLoaded || _current == null) + return; + + SaveTask = textures.SaveTga(_current.BaseImage, path, _current.RgbaPixels, _current.TextureWrap!.Width, _current.TextureWrap!.Height); + } + private void SaveAs(TextureManager textures, string path, TextureSaveType type, bool mipMaps, bool writeTex) { if (!IsLoaded || _current == null) @@ -72,6 +80,7 @@ public partial class CombinedTexture : IDisposable ".tex" => TextureType.Tex, ".dds" => TextureType.Dds, ".png" => TextureType.Png, + ".tga" => TextureType.Targa, _ => TextureType.Unknown, }; @@ -85,6 +94,9 @@ public partial class CombinedTexture : IDisposable break; case TextureType.Png: SaveAsPng(textures, path); + break; + case TextureType.Targa: + SaveAsTarga(textures, path); break; default: throw new ArgumentException( diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 996b5dbf..7118f8af 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -8,6 +8,7 @@ using OtterGui.Tasks; using OtterTex; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; using Image = SixLabors.ImageSharp.Image; @@ -33,10 +34,17 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } public Task SavePng(string input, string output) - => Enqueue(new SavePngAction(this, input, output)); + => Enqueue(new SaveImageSharpAction(this, input, output, TextureType.Png)); public Task SavePng(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) - => Enqueue(new SavePngAction(this, image, path, rgba, width, height)); + => Enqueue(new SaveImageSharpAction(this, image, path, TextureType.Png, rgba, width, height)); + + public Task SaveTga(string input, string output) + => Enqueue(new SaveImageSharpAction(this, input, output, TextureType.Targa)); + + public Task SaveTga(BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) + => Enqueue(new SaveImageSharpAction(this, image, path, TextureType.Targa, rgba, width, height)); + public Task SaveAs(CombinedTexture.TextureSaveType type, bool mipMaps, bool asTex, string input, string output) => Enqueue(new SaveAsAction(this, type, mipMaps, asTex, input, output)); @@ -66,44 +74,65 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur return t; } - private class SavePngAction : IAction + private class SaveImageSharpAction : IAction { private readonly TextureManager _textures; private readonly string _outputPath; private readonly ImageInputData _input; + private readonly TextureType _type; - public SavePngAction(TextureManager textures, string input, string output) + public SaveImageSharpAction(TextureManager textures, string input, string output, TextureType type) { _textures = textures; _input = new ImageInputData(input); _outputPath = output; + _type = type; + if (_type.ReduceToBehaviour() is not TextureType.Png) + throw new ArgumentOutOfRangeException(nameof(type), type, $"Can not save as {type} with ImageSharp."); } - public SavePngAction(TextureManager textures, BaseImage image, string path, byte[]? rgba = null, int width = 0, int height = 0) + public SaveImageSharpAction(TextureManager textures, BaseImage image, string path, TextureType type, byte[]? rgba = null, int width = 0, + int height = 0) { _textures = textures; _input = new ImageInputData(image, rgba, width, height); _outputPath = path; + _type = type; + if (_type.ReduceToBehaviour() is not TextureType.Png) + throw new ArgumentOutOfRangeException(nameof(type), type, $"Can not save as {type} with ImageSharp."); } public void Execute(CancellationToken cancel) { - _textures._logger.Information($"[{nameof(TextureManager)}] Saving {_input} as .png to {_outputPath}..."); + _textures._logger.Information($"[{nameof(TextureManager)}] Saving {_input} as {_type} to {_outputPath}..."); var (image, rgba, width, height) = _input.GetData(_textures); cancel.ThrowIfCancellationRequested(); - Image? png = null; + Image? data = null; if (image.Type is TextureType.Unknown) { if (rgba != null && width > 0 && height > 0) - png = ConvertToPng(rgba, width, height).AsPng!; + data = ConvertToPng(rgba, width, height).AsPng!; } else { - png = ConvertToPng(image, cancel, rgba).AsPng!; + data = ConvertToPng(image, cancel, rgba).AsPng!; } cancel.ThrowIfCancellationRequested(); - png?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel).Wait(cancel); + switch (_type) + { + case TextureType.Png: + data?.SaveAsync(_outputPath, new PngEncoder() { CompressionLevel = PngCompressionLevel.NoCompression }, cancel) + .Wait(cancel); + return; + case TextureType.Targa: + data?.SaveAsync(_outputPath, new TgaEncoder() + { + Compression = TgaCompression.None, + BitsPerPixel = TgaBitsPerPixel.Pixel32, + }, cancel).Wait(cancel); + return; + } } public override string ToString() @@ -111,7 +140,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur public bool Equals(IAction? other) { - if (other is not SavePngAction rhs) + if (other is not SaveImageSharpAction rhs) return false; return string.Equals(_outputPath, rhs._outputPath, StringComparison.OrdinalIgnoreCase) && _input.Equals(rhs._input); @@ -168,9 +197,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur var imageTypeBehaviour = image.Type.ReduceToBehaviour(); var dds = _type switch { - CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, - cancel, rgba, - width, height), + CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Png => ConvertToRgbaDds(image, _mipMaps, cancel, + rgba, width, height), CombinedTexture.TextureSaveType.AsIs when imageTypeBehaviour is TextureType.Dds => AddMipMaps(image.AsDds!, _mipMaps), CombinedTexture.TextureSaveType.Bitmap => ConvertToRgbaDds(image, _mipMaps, cancel, rgba, width, height), CombinedTexture.TextureSaveType.BC3 => ConvertToCompressedDds(image, _mipMaps, false, cancel, rgba, width, height), diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 67a27a0b..c08e8a8e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -85,7 +85,7 @@ public partial class ModEditWindow ImGuiUtil.SelectableHelpMarker(newDesc); } - } + } private void RedrawOnSaveBox() { @@ -128,7 +128,8 @@ public partial class ModEditWindow ? "This saves the texture in place. This is not revertible." : $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save."; - var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); + var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { @@ -141,17 +142,18 @@ public partial class ModEditWindow if (ImGui.Button("Save as TEX", buttonSize2)) OpenSaveAsDialog(".tex"); - if (ImGui.Button("Export as PNG", buttonSize2)) + if (ImGui.Button("Export as TGA", buttonSize3)) + OpenSaveAsDialog(".tga"); + ImGui.SameLine(); + if (ImGui.Button("Export as PNG", buttonSize3)) OpenSaveAsDialog(".png"); ImGui.SameLine(); - if (ImGui.Button("Export as DDS", buttonSize2)) + if (ImGui.Button("Export as DDS", buttonSize3)) OpenSaveAsDialog(".dds"); - ImGui.NewLine(); var canConvertInPlace = canSaveInPlace && _left.Type is TextureType.Tex && _center.IsLeftCopy; - var buttonSize3 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X * 2) / 3, 0); if (ImGuiUtil.DrawDisabledButton("Convert to BC7", buttonSize3, "This converts the texture to BC7 format in place. This is not revertible.", !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) @@ -226,7 +228,8 @@ public partial class ModEditWindow private void OpenSaveAsDialog(string defaultExtension) { var fileName = Path.GetFileNameWithoutExtension(_left.Path.Length > 0 ? _left.Path : _right.Path); - _fileDialog.OpenSavePicker("Save Texture as TEX, DDS or PNG...", "Textures{.png,.dds,.tex},.tex,.dds,.png", fileName, defaultExtension, + _fileDialog.OpenSavePicker("Save Texture as TEX, DDS, PNG or TGA...", "Textures{.png,.dds,.tex,.tga},.tex,.dds,.png,.tga", fileName, + defaultExtension, (a, b) => { if (a) From 3e2c9177a71ecd9a64493b02359b7ba16188c651 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 15:48:02 +0200 Subject: [PATCH 08/73] Prepare API for new meta format. --- Penumbra/Api/Api/MetaApi.cs | 258 ++++++++++++++++++++++++++++++- Penumbra/Api/Api/TemporaryApi.cs | 26 +--- 2 files changed, 257 insertions(+), 27 deletions(-) diff --git a/Penumbra/Api/Api/MetaApi.cs b/Penumbra/Api/Api/MetaApi.cs index ce1a9def..6f3ed51e 100644 --- a/Penumbra/Api/Api/MetaApi.cs +++ b/Penumbra/Api/Api/MetaApi.cs @@ -1,16 +1,21 @@ +using Dalamud.Plugin.Services; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Services; using Penumbra.Collections; +using Penumbra.Collections.Cache; +using Penumbra.GameData.Files.Utility; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; using Penumbra.Meta.Manipulations; namespace Penumbra.Api.Api; -public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) : IPenumbraApiMeta, IApiService +public class MetaApi(IFramework framework, CollectionResolver collectionResolver, ApiHelpers helpers) + : IPenumbraApiMeta, IApiService { - public const int CurrentVersion = 0; + public const int CurrentVersion = 1; public string GetPlayerMetaManipulations() { @@ -24,7 +29,32 @@ public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) return CompressMetaManipulations(collection); } + public Task GetPlayerMetaManipulationsAsync() + { + return Task.Run(async () => + { + var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false); + return CompressMetaManipulations(playerCollection); + }); + } + + public Task GetMetaManipulationsAsync(int gameObjectIdx) + { + return Task.Run(async () => + { + var playerCollection = await framework.RunOnFrameworkThread(() => + { + helpers.AssociatedCollection(gameObjectIdx, out var collection); + return collection; + }).ConfigureAwait(false); + return CompressMetaManipulations(playerCollection); + }); + } + internal static string CompressMetaManipulations(ModCollection collection) + => CompressMetaManipulationsV0(collection); + + private static string CompressMetaManipulationsV0(ModCollection collection) { var array = new JArray(); if (collection.MetaCache is { } cache) @@ -38,6 +68,228 @@ public class MetaApi(CollectionResolver collectionResolver, ApiHelpers helpers) MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); } - return Functions.ToCompressedBase64(array, CurrentVersion); + return Functions.ToCompressedBase64(array, 0); + } + + private static unsafe string CompressMetaManipulationsV1(ModCollection? collection) + { + using var ms = new MemoryStream(); + ms.Capacity = 1024; + using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true)) + { + zipStream.Write((byte)1); + zipStream.Write("META0001"u8); + if (collection?.MetaCache is not { } cache) + { + zipStream.Write(0); + zipStream.Write(0); + zipStream.Write(0); + zipStream.Write(0); + zipStream.Write(0); + zipStream.Write(0); + zipStream.Write(0); + } + else + { + WriteCache(zipStream, cache.Imc); + WriteCache(zipStream, cache.Eqp); + WriteCache(zipStream, cache.Eqdp); + WriteCache(zipStream, cache.Est); + WriteCache(zipStream, cache.Rsp); + WriteCache(zipStream, cache.Gmp); + cache.GlobalEqp.EnterReadLock(); + + try + { + zipStream.Write(cache.GlobalEqp.Count); + foreach (var (globalEqp, _) in cache.GlobalEqp) + zipStream.Write(new ReadOnlySpan(&globalEqp, sizeof(GlobalEqpManipulation))); + } + finally + { + cache.GlobalEqp.ExitReadLock(); + } + } + } + + ms.Flush(); + ms.Position = 0; + var data = ms.GetBuffer().AsSpan(0, (int)ms.Length); + return Convert.ToBase64String(data); + + void WriteCache(Stream stream, MetaCacheBase metaCache) + where TKey : unmanaged, IMetaIdentifier + where TValue : unmanaged + { + metaCache.EnterReadLock(); + try + { + stream.Write(metaCache.Count); + foreach (var (identifier, (_, value)) in metaCache) + { + stream.Write(identifier); + stream.Write(value); + } + } + finally + { + metaCache.ExitReadLock(); + } + } + } + + /// + /// Convert manipulations from a transmitted base64 string to actual manipulations. + /// The empty string is treated as an empty set. + /// Only returns true if all conversions are successful and distinct. + /// + internal static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips) + { + if (manipString.Length == 0) + { + manips = new MetaDictionary(); + return true; + } + + try + { + var bytes = Convert.FromBase64String(manipString); + using var compressedStream = new MemoryStream(bytes); + using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress); + using var resultStream = new MemoryStream(); + zipStream.CopyTo(resultStream); + resultStream.Flush(); + resultStream.Position = 0; + var data = resultStream.GetBuffer().AsSpan(0, (int)resultStream.Length); + var version = data[0]; + data = data[1..]; + switch (version) + { + case 0: return ConvertManipsV0(data, out manips); + case 1: return ConvertManipsV1(data, out manips); + default: + Penumbra.Log.Debug($"Invalid version for manipulations: {version}."); + manips = null; + return false; + } + } + catch (Exception ex) + { + Penumbra.Log.Debug($"Error decompressing manipulations:\n{ex}"); + manips = null; + return false; + } + } + + private static bool ConvertManipsV1(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips) + { + if (!data.StartsWith("META0001"u8)) + { + Penumbra.Log.Debug($"Invalid manipulations of version 1, does not start with valid prefix."); + manips = null; + return false; + } + + manips = new MetaDictionary(); + var r = new SpanBinaryReader(data[8..]); + var imcCount = r.ReadInt32(); + for (var i = 0; i < imcCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var eqpCount = r.ReadInt32(); + for (var i = 0; i < eqpCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var eqdpCount = r.ReadInt32(); + for (var i = 0; i < eqdpCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var estCount = r.ReadInt32(); + for (var i = 0; i < estCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var rspCount = r.ReadInt32(); + for (var i = 0; i < rspCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var gmpCount = r.ReadInt32(); + for (var i = 0; i < gmpCount; ++i) + { + var identifier = r.Read(); + var value = r.Read(); + if (!identifier.Validate() || !manips.TryAdd(identifier, value)) + return false; + } + + var globalEqpCount = r.ReadInt32(); + for (var i = 0; i < globalEqpCount; ++i) + { + var manip = r.Read(); + if (!manip.Validate() || !manips.TryAdd(manip)) + return false; + } + + return true; + } + + private static bool ConvertManipsV0(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips) + { + var json = Encoding.UTF8.GetString(data); + manips = JsonConvert.DeserializeObject(json); + return manips != null; + } + + internal void TestMetaManipulations() + { + var collection = collectionResolver.PlayerCollection(); + var dict = new MetaDictionary(collection.MetaCache); + var count = dict.Count; + + var watch = Stopwatch.StartNew(); + var v0 = CompressMetaManipulationsV0(collection); + var v0Time = watch.ElapsedMilliseconds; + + watch.Restart(); + var v1 = CompressMetaManipulationsV1(collection); + var v1Time = watch.ElapsedMilliseconds; + + watch.Restart(); + var v1Success = ConvertManips(v1, out var v1Roundtrip); + var v1RoundtripTime = watch.ElapsedMilliseconds; + + watch.Restart(); + var v0Success = ConvertManips(v0, out var v0Roundtrip); + var v0RoundtripTime = watch.ElapsedMilliseconds; + + Penumbra.Log.Information($"Version | Count | Time | Length | Success | ReCount | ReTime | Equal"); + Penumbra.Log.Information( + $"0 | {count} | {v0Time} | {v0.Length} | {v0Success} | {v0Roundtrip?.Count} | {v0RoundtripTime} | {v0Roundtrip?.Equals(dict)}"); + Penumbra.Log.Information( + $"1 | {count} | {v1Time} | {v1.Length} | {v1Success} | {v1Roundtrip?.Count} | {v1RoundtripTime} | {v0Roundtrip?.Equals(dict)}"); } } diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index f02b0d94..516b4347 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -1,10 +1,8 @@ -using OtterGui; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; -using Penumbra.Meta.Manipulations; using Penumbra.Mods.Settings; using Penumbra.String.Classes; @@ -62,7 +60,7 @@ public class TemporaryApi( if (!ConvertPaths(paths, out var p)) return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args); - if (!ConvertManips(manipString, out var m)) + if (!MetaApi.ConvertManips(manipString, out var m)) return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args); var ret = tempMods.Register(tag, null, p, m, new ModPriority(priority)) switch @@ -88,7 +86,7 @@ public class TemporaryApi( if (!ConvertPaths(paths, out var p)) return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args); - if (!ConvertManips(manipString, out var m)) + if (!MetaApi.ConvertManips(manipString, out var m)) return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args); var ret = tempMods.Register(tag, collection, p, m, new ModPriority(priority)) switch @@ -153,24 +151,4 @@ public class TemporaryApi( return true; } - - /// - /// Convert manipulations from a transmitted base64 string to actual manipulations. - /// The empty string is treated as an empty set. - /// Only returns true if all conversions are successful and distinct. - /// - private static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips) - { - if (manipString.Length == 0) - { - manips = new MetaDictionary(); - return true; - } - - if (Functions.FromCompressedBase64(manipString, out manips!) == MetaApi.CurrentVersion) - return true; - - manips = null; - return false; - } } From 233a9996507521c6a184743cc2b0314d73ac427b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 15:48:42 +0200 Subject: [PATCH 09/73] Add button to remove default-valued meta entries. --- Penumbra/Mods/Editor/ModMetaEditor.cs | 64 ++++++++++++++++++- .../UI/AdvancedWindow/ModEditWindow.Meta.cs | 15 +++-- 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index bacf4122..d9018ff6 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -1,12 +1,15 @@ using System.Collections.Frozen; using OtterGui.Services; +using Penumbra.GameData.Structs; +using Penumbra.Meta; +using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; namespace Penumbra.Mods.Editor; -public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService +public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManager, ImcChecker imcChecker) : MetaDictionary, IService { public sealed class OtherOptionData : HashSet { @@ -62,6 +65,65 @@ public class ModMetaEditor(ModManager modManager) : MetaDictionary, IService Changes = false; } + public void DeleteDefaultValues() + { + var clone = Clone(); + Clear(); + foreach (var (key, value) in clone.Imc) + { + var defaultEntry = imcChecker.GetDefaultEntry(key, false); + if (!defaultEntry.Entry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + + foreach (var (key, value) in clone.Eqp) + { + var defaultEntry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(metaFileManager, key.SetId), key.Slot); + if (!defaultEntry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + + foreach (var (key, value) in clone.Eqdp) + { + var defaultEntry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(metaFileManager, key), key.Slot); + if (!defaultEntry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + + foreach (var (key, value) in clone.Est) + { + var defaultEntry = EstFile.GetDefault(metaFileManager, key); + if (!defaultEntry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + + foreach (var (key, value) in clone.Gmp) + { + var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, key); + if (!defaultEntry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + + foreach (var (key, value) in clone.Rsp) + { + var defaultEntry = CmpFile.GetDefault(metaFileManager, key.SubRace, key.Attribute); + if (!defaultEntry.Equals(value)) + TryAdd(key, value); + else + Changes = true; + } + } + public void Apply(IModDataContainer container) { if (!Changes) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 3ec6a4d5..49eac96e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -16,21 +16,21 @@ public partial class ModEditWindow private void DrawMetaTab() { - using var tab = ImRaii.TabItem("Meta Manipulations"); + using var tab = ImUtf8.TabItem("Meta Manipulations"u8); if (!tab) return; DrawOptionSelectHeader(); var setsEqual = !_editor.MetaEditor.Changes; - var tt = setsEqual ? "No changes staged." : "Apply the currently staged changes to the option."; + var tt = setsEqual ? "No changes staged."u8 : "Apply the currently staged changes to the option."u8; ImGui.NewLine(); - if (ImGuiUtil.DrawDisabledButton("Apply Changes", Vector2.Zero, tt, setsEqual)) + if (ImUtf8.ButtonEx("Apply Changes"u8, tt, Vector2.Zero, setsEqual)) _editor.MetaEditor.Apply(_editor.Option!); ImGui.SameLine(); - tt = setsEqual ? "No changes staged." : "Revert all currently staged changes."; - if (ImGuiUtil.DrawDisabledButton("Revert Changes", Vector2.Zero, tt, setsEqual)) + tt = setsEqual ? "No changes staged."u8 : "Revert all currently staged changes."u8; + if (ImUtf8.ButtonEx("Revert Changes"u8, tt, Vector2.Zero, setsEqual)) _editor.MetaEditor.Load(_editor.Mod!, _editor.Option!); ImGui.SameLine(); @@ -40,8 +40,11 @@ public partial class ModEditWindow ImGui.SameLine(); CopyToClipboardButton("Copy all current manipulations to clipboard.", _iconSize, _editor.MetaEditor); ImGui.SameLine(); - if (ImGui.Button("Write as TexTools Files")) + if (ImUtf8.Button("Write as TexTools Files"u8)) _metaFileManager.WriteAllTexToolsMeta(Mod!); + ImGui.SameLine(); + if (ImUtf8.ButtonEx("Remove All Default-Values", "Delete any entries from all lists that set the value to its default value."u8)) + _editor.MetaEditor.DeleteDefaultValues(); using var child = ImRaii.Child("##meta", -Vector2.One, true); if (!child) From a3c22f2826b4091010e6f76ad5a236e1c92b44fb Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 15:49:07 +0200 Subject: [PATCH 10/73] Fix ordering of meta entries. --- Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs | 2 +- Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs | 2 +- Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs | 2 +- Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs | 2 +- Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs | 2 +- Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs index f586045c..aea2ef78 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -61,7 +61,7 @@ public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFil } protected override IEnumerable<(EqdpIdentifier, EqdpEntryInternal)> Enumerate() - => Editor.Eqdp.OrderBy(kvp => kvp.Key.SetId) + => Editor.Eqdp.OrderBy(kvp => kvp.Key.SetId.Id) .ThenBy(kvp => kvp.Key.GenderRace) .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs index b1031b44..733517f3 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs @@ -60,7 +60,7 @@ public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(EqpIdentifier, EqpEntryInternal)> Enumerate() => Editor.Eqp - .OrderBy(kvp => kvp.Key.SetId) + .OrderBy(kvp => kvp.Key.SetId.Id) .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); diff --git a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs index 628cee40..a33f8b7b 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs @@ -59,7 +59,7 @@ public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(EstIdentifier, EstEntry)> Enumerate() => Editor.Est - .OrderBy(kvp => kvp.Key.SetId) + .OrderBy(kvp => kvp.Key.SetId.Id) .ThenBy(kvp => kvp.Key.GenderRace) .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); diff --git a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs index bc2e1bde..5d67ddcf 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs @@ -49,7 +49,7 @@ public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager me protected override IEnumerable<(GlobalEqpManipulation, byte)> Enumerate() => Editor.GlobalEqp .OrderBy(identifier => identifier.Type) - .ThenBy(identifier => identifier.Condition) + .ThenBy(identifier => identifier.Condition.Id) .Select(identifier => (identifier, (byte)0)); private static void DrawIdentifierInput(ref GlobalEqpManipulation identifier) diff --git a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs index 1e91731d..bd42b60a 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs @@ -58,7 +58,7 @@ public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() => Editor.Gmp - .OrderBy(kvp => kvp.Key.SetId) + .OrderBy(kvp => kvp.Key.SetId.Id) .Select(kvp => (kvp.Key, kvp.Value)); private static bool DrawIdentifierInput(ref GmpIdentifier identifier) diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index e33eb1aa..4e949b98 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -142,11 +142,11 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(ImcIdentifier, ImcEntry)> Enumerate() => Editor.Imc .OrderBy(kvp => kvp.Key.ObjectType) - .ThenBy(kvp => kvp.Key.PrimaryId) + .ThenBy(kvp => kvp.Key.PrimaryId.Id) .ThenBy(kvp => kvp.Key.EquipSlot) .ThenBy(kvp => kvp.Key.BodySlot) - .ThenBy(kvp => kvp.Key.SecondaryId) - .ThenBy(kvp => kvp.Key.Variant) + .ThenBy(kvp => kvp.Key.SecondaryId.Id) + .ThenBy(kvp => kvp.Key.Variant.Id) .Select(kvp => (kvp.Key, kvp.Value)); public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) From d713d5a112d138d43fc6d4470711cd8d1c711631 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 18:28:49 +0200 Subject: [PATCH 11/73] Improve handling of mod selection. --- Penumbra/Communication/CollectionChange.cs | 3 + .../CollectionInheritanceChanged.cs | 3 + Penumbra/Communication/ModSettingChanged.cs | 4 +- Penumbra/Mods/Editor/ModMerger.cs | 39 ++++--- Penumbra/Mods/ModSelection.cs | 104 ++++++++++++++++++ Penumbra/UI/AdvancedWindow/ModEditWindow.cs | 7 +- Penumbra/UI/Classes/CollectionSelectHeader.cs | 18 +-- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 82 ++++---------- Penumbra/UI/ModsTab/ModPanel.cs | 35 +++--- Penumbra/UI/ModsTab/ModPanelHeader.cs | 34 ++++-- Penumbra/UI/ModsTab/ModPanelSettingsTab.cs | 43 ++++---- Penumbra/UI/Tabs/ModsTab.cs | 3 +- 12 files changed, 227 insertions(+), 148 deletions(-) create mode 100644 Penumbra/Mods/ModSelection.cs diff --git a/Penumbra/Communication/CollectionChange.cs b/Penumbra/Communication/CollectionChange.cs index 95d4ac4d..2788177d 100644 --- a/Penumbra/Communication/CollectionChange.cs +++ b/Penumbra/Communication/CollectionChange.cs @@ -46,5 +46,8 @@ public sealed class CollectionChange() /// ModFileSystemSelector = 0, + + /// + ModSelection = 10, } } diff --git a/Penumbra/Communication/CollectionInheritanceChanged.cs b/Penumbra/Communication/CollectionInheritanceChanged.cs index dbcf9e4a..30af2b20 100644 --- a/Penumbra/Communication/CollectionInheritanceChanged.cs +++ b/Penumbra/Communication/CollectionInheritanceChanged.cs @@ -23,5 +23,8 @@ public sealed class CollectionInheritanceChanged() /// ModFileSystemSelector = 0, + + /// + ModSelection = 10, } } diff --git a/Penumbra/Communication/ModSettingChanged.cs b/Penumbra/Communication/ModSettingChanged.cs index 7fda2f35..d4bf00be 100644 --- a/Penumbra/Communication/ModSettingChanged.cs +++ b/Penumbra/Communication/ModSettingChanged.cs @@ -1,5 +1,4 @@ using OtterGui.Classes; -using Penumbra.Api; using Penumbra.Api.Api; using Penumbra.Api.Enums; using Penumbra.Collections; @@ -35,5 +34,8 @@ public sealed class ModSettingChanged() /// ModFileSystemSelector = 0, + + /// + ModSelection = 10, } } diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index b059813b..d75ac671 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -10,22 +10,21 @@ using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; using Penumbra.Services; using Penumbra.String.Classes; -using Penumbra.UI.ModsTab; namespace Penumbra.Mods.Editor; public class ModMerger : IDisposable, IService { - private readonly Configuration _config; - private readonly CommunicatorService _communicator; - private readonly ModGroupEditor _editor; - private readonly ModFileSystemSelector _selector; - private readonly DuplicateManager _duplicates; - private readonly ModManager _mods; - private readonly ModCreator _creator; + private readonly Configuration _config; + private readonly CommunicatorService _communicator; + private readonly ModGroupEditor _editor; + private readonly ModSelection _selection; + private readonly DuplicateManager _duplicates; + private readonly ModManager _mods; + private readonly ModCreator _creator; public Mod? MergeFromMod - => _selector.Selected; + => _selection.Mod; public Mod? MergeToMod; public string OptionGroupName = "Merges"; @@ -41,23 +40,23 @@ public class ModMerger : IDisposable, IService public readonly IReadOnlyList Warnings = new List(); public Exception? Error { get; private set; } - public ModMerger(ModManager mods, ModGroupEditor editor, ModFileSystemSelector selector, DuplicateManager duplicates, + public ModMerger(ModManager mods, ModGroupEditor editor, ModSelection selection, DuplicateManager duplicates, CommunicatorService communicator, ModCreator creator, Configuration config) { - _editor = editor; - _selector = selector; - _duplicates = duplicates; - _communicator = communicator; - _creator = creator; - _config = config; - _mods = mods; - _selector.SelectionChanged += OnSelectionChange; + _editor = editor; + _selection = selection; + _duplicates = duplicates; + _communicator = communicator; + _creator = creator; + _config = config; + _mods = mods; + _selection.Subscribe(OnSelectionChange, ModSelection.Priority.ModMerger); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModMerger); } public void Dispose() { - _selector.SelectionChanged -= OnSelectionChange; + _selection.Unsubscribe(OnSelectionChange); _communicator.ModPathChanged.Unsubscribe(OnModPathChange); } @@ -390,7 +389,7 @@ public class ModMerger : IDisposable, IService } } - private void OnSelectionChange(Mod? oldSelection, Mod? newSelection, in ModFileSystemSelector.ModState state) + private void OnSelectionChange(Mod? oldSelection, Mod? newSelection) { if (OptionGroupName == "Merges" && OptionName.Length == 0 || OptionName == oldSelection?.Name.Text) OptionName = newSelection?.Name.Text ?? string.Empty; diff --git a/Penumbra/Mods/ModSelection.cs b/Penumbra/Mods/ModSelection.cs new file mode 100644 index 00000000..73d0272b --- /dev/null +++ b/Penumbra/Mods/ModSelection.cs @@ -0,0 +1,104 @@ +using OtterGui.Classes; +using Penumbra.Api.Enums; +using Penumbra.Collections; +using Penumbra.Collections.Manager; +using Penumbra.Communication; +using Penumbra.Mods.Manager; +using Penumbra.Mods.Settings; +using Penumbra.Services; + +namespace Penumbra.Mods; + +/// +/// Triggered whenever the selected mod changes +/// +/// Parameter is the old selected mod. +/// Parameter is the new selected mod +/// +/// +public class ModSelection : EventWrapper +{ + private readonly ActiveCollections _collections; + private readonly EphemeralConfig _config; + private readonly CommunicatorService _communicator; + + public ModSelection(CommunicatorService communicator, ModManager mods, ActiveCollections collections, EphemeralConfig config) + : base(nameof(ModSelection)) + { + _communicator = communicator; + _collections = collections; + _config = config; + if (_config.LastModPath.Length > 0) + SelectMod(mods.FirstOrDefault(m => string.Equals(m.Identifier, config.LastModPath, StringComparison.OrdinalIgnoreCase))); + + _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModSelection); + _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModSelection); + _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModSelection); + } + + public ModSettings Settings { get; private set; } = ModSettings.Empty; + public ModCollection Collection { get; private set; } = ModCollection.Empty; + public Mod? Mod { get; private set; } + + + public void SelectMod(Mod? mod) + { + if (mod == Mod) + return; + + var oldMod = Mod; + Mod = mod; + OnCollectionChange(CollectionType.Current, null, _collections.Current, string.Empty); + Invoke(oldMod, Mod); + _config.LastModPath = mod?.ModPath.Name ?? string.Empty; + _config.Save(); + } + + protected override void Dispose(bool _) + { + _communicator.CollectionChange.Unsubscribe(OnCollectionChange); + _communicator.CollectionInheritanceChanged.Unsubscribe(OnInheritanceChange); + _communicator.ModSettingChanged.Unsubscribe(OnSettingChange); + } + + private void OnCollectionChange(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _2) + { + if (type is CollectionType.Current && oldCollection != newCollection) + UpdateSettings(); + } + + private void OnSettingChange(ModCollection collection, ModSettingChange _1, Mod? mod, Setting _2, int _3, bool _4) + { + if (collection == _collections.Current && mod == Mod) + UpdateSettings(); + } + + private void OnInheritanceChange(ModCollection collection, bool arg2) + { + if (collection == _collections.Current) + UpdateSettings(); + } + + private void UpdateSettings() + { + if (Mod == null) + { + Settings = ModSettings.Empty; + Collection = ModCollection.Empty; + } + else + { + (var settings, Collection) = _collections.Current[Mod.Index]; + Settings = settings ?? ModSettings.Empty; + } + } + + public enum Priority + { + /// + ModPanel = 0, + + /// + ModMerger = 0, + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 13458252..7bb067d8 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -36,8 +36,6 @@ public partial class ModEditWindow : Window, IDisposable, IUiService { private const string WindowBaseLabel = "###SubModEdit"; - public readonly MigrationManager MigrationManager; - private readonly PerformanceTracker _performance; private readonly ModEditor _editor; private readonly Configuration _config; @@ -587,7 +585,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService CommunicatorService communicator, TextureManager textures, ModelManager models, IDragDropManager dragDropManager, ResourceTreeViewerFactory resourceTreeViewerFactory, IFramework framework, MetaDrawers metaDrawers, MigrationManager migrationManager, - MtrlTabFactory mtrlTabFactory) + MtrlTabFactory mtrlTabFactory, ModSelection selection) : base(WindowBaseLabel) { _performance = performance; @@ -604,7 +602,6 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _models = models; _fileDialog = fileDialog; _framework = framework; - MigrationManager = migrationManager; _metaDrawers = metaDrawers; _materialTab = new FileEditor(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl", () => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => Mod?.ModPath.FullName ?? string.Empty, @@ -622,6 +619,8 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _quickImportViewer = resourceTreeViewerFactory.Create(2, OnQuickImportRefresh, DrawQuickImportActions); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow); IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true }; + if (IsOpen && selection.Mod != null) + ChangeMod(selection.Mod); } public void Dispose() diff --git a/Penumbra/UI/Classes/CollectionSelectHeader.cs b/Penumbra/UI/Classes/CollectionSelectHeader.cs index 0f9b2518..3972e350 100644 --- a/Penumbra/UI/Classes/CollectionSelectHeader.cs +++ b/Penumbra/UI/Classes/CollectionSelectHeader.cs @@ -5,24 +5,24 @@ using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; +using Penumbra.Mods; using Penumbra.UI.CollectionTab; -using Penumbra.UI.ModsTab; namespace Penumbra.UI.Classes; public class CollectionSelectHeader : IUiService { - private readonly CollectionCombo _collectionCombo; - private readonly ActiveCollections _activeCollections; - private readonly TutorialService _tutorial; - private readonly ModFileSystemSelector _selector; - private readonly CollectionResolver _resolver; + private readonly CollectionCombo _collectionCombo; + private readonly ActiveCollections _activeCollections; + private readonly TutorialService _tutorial; + private readonly ModSelection _selection; + private readonly CollectionResolver _resolver; - public CollectionSelectHeader(CollectionManager collectionManager, TutorialService tutorial, ModFileSystemSelector selector, + public CollectionSelectHeader(CollectionManager collectionManager, TutorialService tutorial, ModSelection selection, CollectionResolver resolver) { _tutorial = tutorial; - _selector = selector; + _selection = selection; _resolver = resolver; _activeCollections = collectionManager.Active; _collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Name).ToList()); @@ -115,7 +115,7 @@ public class CollectionSelectHeader : IUiService private (ModCollection?, string, string, bool) GetInheritedCollectionInfo() { - var collection = _selector.Selected == null ? null : _selector.SelectedSettingCollection; + var collection = _selection.Mod == null ? null : _selection.Collection; return CheckCollection(collection, true) switch { CollectionState.Unavailable => (null, "Not Inherited", diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 42689efb..2f76340b 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -25,7 +25,6 @@ namespace Penumbra.UI.ModsTab; public sealed class ModFileSystemSelector : FileSystemSelector, IUiService { private readonly CommunicatorService _communicator; - private readonly MessageService _messager; private readonly Configuration _config; private readonly FileDialogService _fileDialog; private readonly ModManager _modManager; @@ -33,15 +32,12 @@ public sealed class ModFileSystemSelector : FileSystemSelector 0) - { - var mod = _modManager.FirstOrDefault(m - => string.Equals(m.Identifier, _config.Ephemeral.LastModPath, StringComparison.OrdinalIgnoreCase)); - if (mod != null) - SelectByValue(mod); - } - + if (_selection.Mod != null) + SelectByValue(_selection.Mod); _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemSelector); _communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemSelector); _communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModFileSystemSelector); _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemSelector); _communicator.ModDiscoveryStarted.Subscribe(StoreCurrentSelection, ModDiscoveryStarted.Priority.ModFileSystemSelector); _communicator.ModDiscoveryFinished.Subscribe(RestoreLastSelection, ModDiscoveryFinished.Priority.ModFileSystemSelector); - OnCollectionChange(CollectionType.Current, null, _collectionManager.Active.Current, ""); - } - + SetFilterDirty(); + SelectionChanged += OnSelectionChanged; + } + public void SetRenameSearchPath(RenameField value) { switch (value) @@ -449,12 +439,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector _selection.SelectMod(newSelection); + #endregion #region Filters @@ -567,7 +529,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Appropriately identify and set the string filter and its type. protected override bool ChangeFilter(string filterValue) { - Filter.Parse(filterValue); + _filter.Parse(filterValue); return true; } @@ -597,7 +559,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Apply the string filters. private bool ApplyStringFilters(ModFileSystem.Leaf leaf, Mod mod) - => !Filter.IsVisible(leaf); + => !_filter.IsVisible(leaf); /// Only get the text color for a mod if no filters are set. private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) diff --git a/Penumbra/UI/ModsTab/ModPanel.cs b/Penumbra/UI/ModsTab/ModPanel.cs index ee6fab1f..9d6ead62 100644 --- a/Penumbra/UI/ModsTab/ModPanel.cs +++ b/Penumbra/UI/ModsTab/ModPanel.cs @@ -10,22 +10,23 @@ namespace Penumbra.UI.ModsTab; public class ModPanel : IDisposable, IUiService { - private readonly MultiModPanel _multiModPanel; - private readonly ModFileSystemSelector _selector; - private readonly ModEditWindow _editWindow; - private readonly ModPanelHeader _header; - private readonly ModPanelTabBar _tabs; - private bool _resetCursor; + private readonly MultiModPanel _multiModPanel; + private readonly ModSelection _selection; + private readonly ModEditWindow _editWindow; + private readonly ModPanelHeader _header; + private readonly ModPanelTabBar _tabs; + private bool _resetCursor; - public ModPanel(IDalamudPluginInterface pi, ModFileSystemSelector selector, ModEditWindow editWindow, ModPanelTabBar tabs, + public ModPanel(IDalamudPluginInterface pi, ModSelection selection, ModEditWindow editWindow, ModPanelTabBar tabs, MultiModPanel multiModPanel, CommunicatorService communicator) { - _selector = selector; - _editWindow = editWindow; - _tabs = tabs; - _multiModPanel = multiModPanel; - _header = new ModPanelHeader(pi, communicator); - _selector.SelectionChanged += OnSelectionChange; + _selection = selection; + _editWindow = editWindow; + _tabs = tabs; + _multiModPanel = multiModPanel; + _header = new ModPanelHeader(pi, communicator); + _selection.Subscribe(OnSelectionChange, ModSelection.Priority.ModPanel); + OnSelectionChange(null, _selection.Mod); } public void Draw() @@ -52,17 +53,17 @@ public class ModPanel : IDisposable, IUiService public void Dispose() { - _selector.SelectionChanged -= OnSelectionChange; + _selection.Unsubscribe(OnSelectionChange); _header.Dispose(); } private bool _valid; private Mod _mod = null!; - private void OnSelectionChange(Mod? old, Mod? mod, in ModFileSystemSelector.ModState _) + private void OnSelectionChange(Mod? old, Mod? mod) { _resetCursor = true; - if (mod == null || _selector.Selected == null) + if (mod == null || _selection.Mod == null) { _editWindow.IsOpen = false; _valid = false; @@ -73,7 +74,7 @@ public class ModPanel : IDisposable, IUiService _editWindow.ChangeMod(mod); _valid = true; _mod = mod; - _header.UpdateModData(_mod); + _header.ChangeMod(_mod); _tabs.Settings.Reset(); _tabs.Edit.Reset(); } diff --git a/Penumbra/UI/ModsTab/ModPanelHeader.cs b/Penumbra/UI/ModsTab/ModPanelHeader.cs index 6c974f9c..aafbffa6 100644 --- a/Penumbra/UI/ModsTab/ModPanelHeader.cs +++ b/Penumbra/UI/ModsTab/ModPanelHeader.cs @@ -18,7 +18,8 @@ public class ModPanelHeader : IDisposable private readonly IFontHandle _nameFont; private readonly CommunicatorService _communicator; - private float _lastPreSettingsHeight = 0; + private float _lastPreSettingsHeight; + private bool _dirty = true; public ModPanelHeader(IDalamudPluginInterface pi, CommunicatorService communicator) { @@ -33,6 +34,7 @@ public class ModPanelHeader : IDisposable /// public void Draw() { + UpdateModData(); var height = ImGui.GetContentRegionAvail().Y; var maxHeight = 3 * height / 4; using var child = _lastPreSettingsHeight > maxHeight && _communicator.PreSettingsTabBarDraw.HasSubscribers @@ -49,16 +51,25 @@ public class ModPanelHeader : IDisposable _lastPreSettingsHeight = ImGui.GetCursorPosY(); } + public void ChangeMod(Mod mod) + { + _mod = mod; + _dirty = true; + } + /// /// Update all mod header data. Should someone change frame padding or item spacing, /// or his default font, this will break, but he will just have to select a different mod to restore. /// - public void UpdateModData(Mod mod) + private void UpdateModData() { + if (!_dirty) + return; + + _dirty = false; _lastPreSettingsHeight = 0; - _mod = mod; // Name - var name = $" {mod.Name} "; + var name = $" {_mod.Name} "; if (name != _modName) { using var f = _nameFont.Push(); @@ -67,16 +78,16 @@ public class ModPanelHeader : IDisposable } // Author - if (mod.Author != _modAuthor) + if (_mod.Author != _modAuthor) { - var author = mod.Author.IsEmpty ? string.Empty : $"by {mod.Author}"; - _modAuthor = mod.Author.Text; + var author = _mod.Author.IsEmpty ? string.Empty : $"by {_mod.Author}"; + _modAuthor = _mod.Author.Text; _modAuthorWidth = ImGui.CalcTextSize(author).X; _secondRowWidth = _modAuthorWidth + _modWebsiteButtonWidth + ImGui.GetStyle().ItemSpacing.X; } // Version - var version = mod.Version.Length > 0 ? $"({mod.Version})" : string.Empty; + var version = _mod.Version.Length > 0 ? $"({_mod.Version})" : string.Empty; if (version != _modVersion) { _modVersion = version; @@ -84,9 +95,9 @@ public class ModPanelHeader : IDisposable } // Website - if (_modWebsite != mod.Website) + if (_modWebsite != _mod.Website) { - _modWebsite = mod.Website; + _modWebsite = _mod.Website; _websiteValid = Uri.TryCreate(_modWebsite, UriKind.Absolute, out var uriResult) && (uriResult.Scheme == Uri.UriSchemeHttps || uriResult.Scheme == Uri.UriSchemeHttp); _modWebsiteButton = _websiteValid ? "Open Website" : _modWebsite.Length == 0 ? string.Empty : $"from {_modWebsite}"; @@ -253,7 +264,6 @@ public class ModPanelHeader : IDisposable { const ModDataChangeType relevantChanges = ModDataChangeType.Author | ModDataChangeType.Name | ModDataChangeType.Website | ModDataChangeType.Version; - if ((changeType & relevantChanges) != 0) - UpdateModData(mod); + _dirty = (changeType & relevantChanges) != 0; } } diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index 7e3b8a95..d2fbd0cd 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -3,9 +3,9 @@ using OtterGui.Raii; using OtterGui; using OtterGui.Services; using OtterGui.Widgets; -using Penumbra.Collections; using Penumbra.UI.Classes; using Penumbra.Collections.Manager; +using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.Mods.Settings; @@ -16,16 +16,14 @@ namespace Penumbra.UI.ModsTab; public class ModPanelSettingsTab( CollectionManager collectionManager, ModManager modManager, - ModFileSystemSelector selector, + ModSelection selection, TutorialService tutorial, CommunicatorService communicator, ModGroupDrawer modGroupDrawer) : ITab, IUiService { - private bool _inherited; - private ModSettings _settings = null!; - private ModCollection _collection = null!; - private int? _currentPriority; + private bool _inherited; + private int? _currentPriority; public ReadOnlySpan Label => "Settings"u8; @@ -42,12 +40,10 @@ public class ModPanelSettingsTab( if (!child) return; - _settings = selector.SelectedSettings; - _collection = selector.SelectedSettingCollection; - _inherited = _collection != collectionManager.Active.Current; + _inherited = selection.Collection != collectionManager.Active.Current; DrawInheritedWarning(); UiHelpers.DefaultLineSpace(); - communicator.PreSettingsPanelDraw.Invoke(selector.Selected!.Identifier); + communicator.PreSettingsPanelDraw.Invoke(selection.Mod!.Identifier); DrawEnabledInput(); tutorial.OpenTutorial(BasicTutorialSteps.EnablingMods); ImGui.SameLine(); @@ -55,11 +51,11 @@ public class ModPanelSettingsTab( tutorial.OpenTutorial(BasicTutorialSteps.Priority); DrawRemoveSettings(); - communicator.PostEnabledDraw.Invoke(selector.Selected!.Identifier); + communicator.PostEnabledDraw.Invoke(selection.Mod!.Identifier); - modGroupDrawer.Draw(selector.Selected!, _settings); + modGroupDrawer.Draw(selection.Mod!, selection.Settings); UiHelpers.DefaultLineSpace(); - communicator.PostSettingsPanelDraw.Invoke(selector.Selected!.Identifier); + communicator.PostSettingsPanelDraw.Invoke(selection.Mod!.Identifier); } /// Draw a big red bar if the current setting is inherited. @@ -70,8 +66,8 @@ public class ModPanelSettingsTab( using var color = ImRaii.PushColor(ImGuiCol.Button, Colors.PressEnterWarningBg); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); - if (ImGui.Button($"These settings are inherited from {_collection.Name}.", width)) - collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selector.Selected!, false); + if (ImGui.Button($"These settings are inherited from {selection.Collection.Name}.", width)) + collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, false); ImGuiUtil.HoverTooltip("You can click this button to copy the current settings to the current selection.\n" + "You can also just change any setting, which will copy the settings with the single setting changed to the current selection."); @@ -80,12 +76,12 @@ public class ModPanelSettingsTab( /// Draw a checkbox for the enabled status of the mod. private void DrawEnabledInput() { - var enabled = _settings.Enabled; + var enabled = selection.Settings.Enabled; if (!ImGui.Checkbox("Enabled", ref enabled)) return; - modManager.SetKnown(selector.Selected!); - collectionManager.Editor.SetModState(collectionManager.Active.Current, selector.Selected!, enabled); + modManager.SetKnown(selection.Mod!); + collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod!, enabled); } /// @@ -95,15 +91,16 @@ public class ModPanelSettingsTab( private void DrawPriorityInput() { using var group = ImRaii.Group(); - var priority = _currentPriority ?? _settings.Priority.Value; + var settings = selection.Settings; + var priority = _currentPriority ?? settings.Priority.Value; ImGui.SetNextItemWidth(50 * UiHelpers.Scale); if (ImGui.InputInt("##Priority", ref priority, 0, 0)) _currentPriority = priority; if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { - if (_currentPriority != _settings.Priority.Value) - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, + if (_currentPriority != settings.Priority.Value) + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, new ModPriority(_currentPriority.Value)); _currentPriority = null; @@ -120,13 +117,13 @@ public class ModPanelSettingsTab( private void DrawRemoveSettings() { const string text = "Inherit Settings"; - if (_inherited || _settings == ModSettings.Empty) + if (_inherited || selection.Settings == ModSettings.Empty) return; var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0; ImGui.SameLine(ImGui.GetWindowWidth() - ImGui.CalcTextSize(text).X - ImGui.GetStyle().FramePadding.X * 2 - scroll); if (ImGui.Button(text)) - collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selector.Selected!, true); + collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, true); ImGuiUtil.HoverTooltip("Remove current settings from this collection so that it can inherit them.\n" + "If no inherited collection has settings for this mod, it will be disabled."); diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs index 50fdc1d3..87338bdb 100644 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ b/Penumbra/UI/Tabs/ModsTab.cs @@ -82,8 +82,7 @@ public class ModsTab( + $"{selector.SortMode.Name} Sort Mode\n" + $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n" + $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n" - + $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n" - + $"{selector.SelectedSettingCollection.AnonymizedName} Collection\n"); + + $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n"); } } From 4970e571316fc6b6394b029e03bd26119fde98bd Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 18:29:12 +0200 Subject: [PATCH 12/73] Improve tooltip of file redirections tab. --- OtterGui | 2 +- .../UI/AdvancedWindow/ModEditWindow.Files.cs | 28 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/OtterGui b/OtterGui index 276327f8..9217ac56 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 276327f812e2f7e6aac7aee9e5ef0a560b065765 +Subproject commit 9217ac56697bc8285ced483b1fd4734fd36ba64d diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs index ffa7473d..b07633b6 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs @@ -1,8 +1,10 @@ +using System.Linq; using Dalamud.Interface; using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; +using OtterGui.Text; using Penumbra.Mods.Editor; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; @@ -144,22 +146,20 @@ public partial class ModEditWindow private static string DrawFileTooltip(FileRegistry registry, ColorId color) { - (string, int) GetMulti() - { - var groups = registry.SubModUsage.GroupBy(s => s.Item1).ToArray(); - return (string.Join("\n", groups.Select(g => g.Key.GetName())), groups.Length); - } - var (text, groupCount) = color switch { - ColorId.ConflictingMod => (string.Empty, 0), - ColorId.NewMod => (registry.SubModUsage[0].Item1.GetName(), 1), + ColorId.ConflictingMod => (null, 0), + ColorId.NewMod => ([registry.SubModUsage[0].Item1.GetName()], 1), ColorId.InheritedMod => GetMulti(), - _ => (string.Empty, 0), + _ => (null, 0), }; - if (text.Length > 0 && ImGui.IsItemHovered()) - ImGui.SetTooltip(text); + if (text != null && ImGui.IsItemHovered()) + { + using var tt = ImUtf8.Tooltip(); + using var c = ImRaii.DefaultColors(); + ImUtf8.Text(string.Join('\n', text)); + } return (groupCount, registry.SubModUsage.Count) switch @@ -169,6 +169,12 @@ public partial class ModEditWindow (1, > 1) => $"(used {registry.SubModUsage.Count} times in 1 group)", _ => $"(used {registry.SubModUsage.Count} times over {groupCount} groups)", }; + + (IEnumerable, int) GetMulti() + { + var groups = registry.SubModUsage.GroupBy(s => s.Item1).ToArray(); + return (groups.Select(g => g.Key.GetName()), groups.Length); + } } private void DrawSelectable(FileRegistry registry) From 6d408ba695666e67be5d77d5387b676be839a744 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 18:29:47 +0200 Subject: [PATCH 13/73] Clip meta changes. --- Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs | 3 +++ Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs | 3 +++ Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs | 3 +++ .../UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs | 3 +++ Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs | 5 ++++- Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 3 +++ Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs | 15 +++++++++------ Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs | 3 +++ 8 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs index aea2ef78..f9baddbe 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -66,6 +66,9 @@ public sealed class EqdpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFil .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); + protected override int Count + => Editor.Eqdp.Count; + private static bool DrawIdentifierInput(ref EqdpIdentifier identifier) { ImGui.TableNextColumn(); diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs index 733517f3..51b14459 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs @@ -64,6 +64,9 @@ public sealed class EqpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); + protected override int Count + => Editor.Eqp.Count; + private static bool DrawIdentifierInput(ref EqpIdentifier identifier) { ImGui.TableNextColumn(); diff --git a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs index a33f8b7b..09075319 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs @@ -64,6 +64,9 @@ public sealed class EstMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile .ThenBy(kvp => kvp.Key.Slot) .Select(kvp => (kvp.Key, kvp.Value)); + protected override int Count + => Editor.Est.Count; + private static bool DrawIdentifierInput(ref EstIdentifier identifier) { ImGui.TableNextColumn(); diff --git a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs index 5d67ddcf..1aa9060e 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs @@ -52,6 +52,9 @@ public sealed class GlobalEqpMetaDrawer(ModMetaEditor editor, MetaFileManager me .ThenBy(identifier => identifier.Condition.Id) .Select(identifier => (identifier, (byte)0)); + protected override int Count + => Editor.GlobalEqp.Count; + private static void DrawIdentifierInput(ref GlobalEqpManipulation identifier) { ImGui.TableNextColumn(); diff --git a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs index bd42b60a..9532d8e7 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs @@ -59,7 +59,10 @@ public sealed class GmpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile protected override IEnumerable<(GmpIdentifier, GmpEntry)> Enumerate() => Editor.Gmp .OrderBy(kvp => kvp.Key.SetId.Id) - .Select(kvp => (kvp.Key, kvp.Value)); + .Select(kvp => (kvp.Key, kvp.Value)); + + protected override int Count + => Editor.Gmp.Count; private static bool DrawIdentifierInput(ref GmpIdentifier identifier) { diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index 4e949b98..53c61292 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -149,6 +149,9 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile .ThenBy(kvp => kvp.Key.Variant.Id) .Select(kvp => (kvp.Key, kvp.Value)); + protected override int Count + => Editor.Imc.Count; + public static bool DrawObjectType(ref ImcIdentifier identifier, float width = 110) { var ret = Combos.ImcType("##imcType", identifier.ObjectType, out var type, width); diff --git a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs index 229526c4..75de20a7 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs @@ -41,12 +41,14 @@ public abstract class MetaDrawer(ModMetaEditor editor, Meta using var id = ImUtf8.PushId((int)Identifier.Type); DrawNew(); - foreach (var ((identifier, entry), idx) in Enumerate().WithIndex()) - { - id.Push(idx); - DrawEntry(identifier, entry); - id.Pop(); - } + + var height = ImUtf8.FrameHeightSpacing; + var skips = ImGuiClip.GetNecessarySkipsAtPos(height, ImGui.GetCursorPosY()); + var remainder = ImGuiClip.ClippedTableDraw(Enumerate(), skips, DrawLine, Count); + ImGuiClip.DrawEndDummy(remainder, height); + + void DrawLine((TIdentifier Identifier, TEntry Value) pair) + => DrawEntry(pair.Identifier, pair.Value); } public abstract ReadOnlySpan Label { get; } @@ -57,6 +59,7 @@ public abstract class MetaDrawer(ModMetaEditor editor, Meta protected abstract void DrawEntry(TIdentifier identifier, TEntry entry); protected abstract IEnumerable<(TIdentifier, TEntry)> Enumerate(); + protected abstract int Count { get; } /// diff --git a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs index 6d819b16..87e8c5b8 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs @@ -63,6 +63,9 @@ public sealed class RspMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile .ThenBy(kvp => kvp.Key.Attribute) .Select(kvp => (kvp.Key, kvp.Value)); + protected override int Count + => Editor.Rsp.Count; + private static bool DrawIdentifierInput(ref RspIdentifier identifier) { ImGui.TableNextColumn(); From 4117d45d152e9c6c3f29656c0f103f1884a5e38a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 18:32:22 +0200 Subject: [PATCH 14/73] Use ReadWriteDictionary as base for meta changes. --- OtterGui | 2 +- Penumbra/Collections/Cache/GlobalEqpCache.cs | 3 +- Penumbra/Collections/Cache/MetaCache.cs | 6 ++- .../Cache/{IMetaCache.cs => MetaCacheBase.cs} | 37 ++++++------------- 4 files changed, 20 insertions(+), 28 deletions(-) rename Penumbra/Collections/Cache/{IMetaCache.cs => MetaCacheBase.cs} (52%) diff --git a/OtterGui b/OtterGui index 9217ac56..bfbde4f8 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 9217ac56697bc8285ced483b1fd4734fd36ba64d +Subproject commit bfbde4f8aa6acc8eb3ed8bc419d5ae2afc77b5f1 diff --git a/Penumbra/Collections/Cache/GlobalEqpCache.cs b/Penumbra/Collections/Cache/GlobalEqpCache.cs index 1c80b47d..efcab109 100644 --- a/Penumbra/Collections/Cache/GlobalEqpCache.cs +++ b/Penumbra/Collections/Cache/GlobalEqpCache.cs @@ -1,3 +1,4 @@ +using OtterGui.Classes; using OtterGui.Services; using Penumbra.GameData.Structs; using Penumbra.Meta.Manipulations; @@ -5,7 +6,7 @@ using Penumbra.Mods.Editor; namespace Penumbra.Collections.Cache; -public class GlobalEqpCache : Dictionary, IService +public class GlobalEqpCache : ReadWriteDictionary, IService { private readonly HashSet _doNotHideEarrings = []; private readonly HashSet _doNotHideNecklace = []; diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 1a6924a9..05a94ac5 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -3,7 +3,6 @@ using Penumbra.GameData.Structs; using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; -using Penumbra.String.Classes; namespace Penumbra.Collections.Cache; @@ -16,6 +15,7 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) public readonly RspCache Rsp = new(manager, collection); public readonly ImcCache Imc = new(manager, collection); public readonly GlobalEqpCache GlobalEqp = new(); + public bool IsDisposed { get; private set; } public int Count => Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + GlobalEqp.Count; @@ -42,6 +42,10 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) public void Dispose() { + if (IsDisposed) + return; + + IsDisposed = true; Eqp.Dispose(); Eqdp.Dispose(); Est.Dispose(); diff --git a/Penumbra/Collections/Cache/IMetaCache.cs b/Penumbra/Collections/Cache/MetaCacheBase.cs similarity index 52% rename from Penumbra/Collections/Cache/IMetaCache.cs rename to Penumbra/Collections/Cache/MetaCacheBase.cs index fecc6f50..98a87e3f 100644 --- a/Penumbra/Collections/Cache/IMetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCacheBase.cs @@ -1,3 +1,4 @@ +using OtterGui.Classes; using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; @@ -5,27 +6,19 @@ using Penumbra.Mods.Editor; namespace Penumbra.Collections.Cache; public abstract class MetaCacheBase(MetaFileManager manager, ModCollection collection) - : Dictionary + : ReadWriteDictionary where TIdentifier : unmanaged, IMetaIdentifier where TEntry : unmanaged { - protected readonly MetaFileManager Manager = manager; - protected readonly ModCollection Collection = collection; - - public void Dispose() - { - Dispose(true); - } + protected readonly MetaFileManager Manager = manager; + protected readonly ModCollection Collection = collection; public bool ApplyMod(IMod source, TIdentifier identifier, TEntry entry) { - lock (this) - { - if (TryGetValue(identifier, out var pair) && pair.Source == source && EqualityComparer.Default.Equals(pair.Entry, entry)) - return false; + if (TryGetValue(identifier, out var pair) && pair.Source == source && EqualityComparer.Default.Equals(pair.Entry, entry)) + return false; - this[identifier] = (source, entry); - } + this[identifier] = (source, entry); ApplyModInternal(identifier, entry); return true; @@ -33,17 +26,14 @@ public abstract class MetaCacheBase(MetaFileManager manager public bool RevertMod(TIdentifier identifier, [NotNullWhen(true)] out IMod? mod) { - lock (this) + if (!Remove(identifier, out var pair)) { - if (!Remove(identifier, out var pair)) - { - mod = null; - return false; - } - - mod = pair.Source; + mod = null; + return false; } + mod = pair.Source; + RevertModInternal(identifier); return true; } @@ -54,7 +44,4 @@ public abstract class MetaCacheBase(MetaFileManager manager protected virtual void RevertModInternal(TIdentifier identifier) { } - - protected virtual void Dispose(bool _) - { } } From f04331188252806b983adac72aa99134d36bc5c5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 23:10:22 +0200 Subject: [PATCH 15/73] Fix vulnerability warning. --- Penumbra/Penumbra.csproj | 2 ++ Penumbra/packages.lock.json | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index f42d16ad..9b613729 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -86,6 +86,8 @@ + + diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index fd3a0a9e..5b868212 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -51,6 +51,12 @@ "resolved": "3.1.5", "contentHash": "lNtlq7dSI/QEbYey+A0xn48z5w4XHSffF8222cC4F4YwTXfEImuiBavQcWjr49LThT/pRmtWJRcqA/PlL+eJ6g==" }, + "System.Formats.Asn1": { + "type": "Direct", + "requested": "[8.0.1, )", + "resolved": "8.0.1", + "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A==" + }, "JetBrains.Annotations": { "type": "Transitive", "resolved": "2024.2.0", @@ -82,11 +88,6 @@ "SharpGLTF.Core": "1.0.1" } }, - "System.Formats.Asn1": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AJukBuLoe3QeAF+mfaRKQb2dgyrvt340iMBHYv+VdBzCUM06IxGlvl0o/uPOS7lHnXPN6u8fFRHSHudx5aTi8w==" - }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", "resolved": "8.0.0", From f8e3b6777fd347ff5c19899fcf1d0b695486c901 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 28 Aug 2024 23:10:59 +0200 Subject: [PATCH 16/73] Add DeleteDefaultValues on general dicts. --- Penumbra/Mods/Editor/ModMetaEditor.cs | 67 ++++++++++++++++++++------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index d9018ff6..6b5ec378 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -1,6 +1,5 @@ using System.Collections.Frozen; using OtterGui.Services; -using Penumbra.GameData.Structs; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; @@ -65,65 +64,101 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage Changes = false; } - public void DeleteDefaultValues() + public bool DeleteDefaultValues(MetaDictionary dict) { - var clone = Clone(); - Clear(); + var clone = dict.Clone(); + dict.Clear(); + var ret = false; foreach (var (key, value) in clone.Imc) { var defaultEntry = imcChecker.GetDefaultEntry(key, false); if (!defaultEntry.Entry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } foreach (var (key, value) in clone.Eqp) { var defaultEntry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(metaFileManager, key.SetId), key.Slot); if (!defaultEntry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } foreach (var (key, value) in clone.Eqdp) { var defaultEntry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(metaFileManager, key), key.Slot); if (!defaultEntry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } foreach (var (key, value) in clone.Est) { var defaultEntry = EstFile.GetDefault(metaFileManager, key); if (!defaultEntry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } foreach (var (key, value) in clone.Gmp) { var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, key); if (!defaultEntry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } foreach (var (key, value) in clone.Rsp) { var defaultEntry = CmpFile.GetDefault(metaFileManager, key.SubRace, key.Attribute); if (!defaultEntry.Equals(value)) - TryAdd(key, value); + { + dict.TryAdd(key, value); + } else - Changes = true; + { + Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); + ret = true; + } } + + return ret; } + public void DeleteDefaultValues() + => Changes = DeleteDefaultValues(this); + public void Apply(IModDataContainer container) { if (!Changes) From f5e61324627be7e192ce124d2a59154673adb857 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Aug 2024 17:47:51 +0200 Subject: [PATCH 17/73] Delete default meta entries from archives and api added mods if not configured otherwise. --- Penumbra/Api/Api/ModsApi.cs | 2 +- Penumbra/Mods/Editor/DuplicateManager.cs | 2 +- Penumbra/Mods/Editor/ModFileEditor.cs | 2 +- Penumbra/Mods/Editor/ModMerger.cs | 2 +- Penumbra/Mods/Editor/ModMetaEditor.cs | 33 +++++---- Penumbra/Mods/Editor/ModNormalizer.cs | 2 +- Penumbra/Mods/Manager/ModImportManager.cs | 2 +- Penumbra/Mods/Manager/ModManager.cs | 10 +-- Penumbra/Mods/Manager/ModMigration.cs | 6 +- .../Manager/OptionEditor/ModGroupEditor.cs | 30 ++++---- Penumbra/Mods/ModCreator.cs | 71 +++++++++++-------- Penumbra/Mods/TemporaryMod.cs | 2 +- Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 2 +- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 2 +- 14 files changed, 95 insertions(+), 73 deletions(-) diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs index 2acdf031..31f20c5e 100644 --- a/Penumbra/Api/Api/ModsApi.cs +++ b/Penumbra/Api/Api/ModsApi.cs @@ -82,7 +82,7 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable != Path.TrimEndingDirectorySeparator(Path.GetFullPath(dir.Parent.FullName))) return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args); - _modManager.AddMod(dir); + _modManager.AddMod(dir, true); if (_config.MigrateImportedModelsToV6) { _migrationManager.MigrateMdlDirectory(dir.FullName, false); diff --git a/Penumbra/Mods/Editor/DuplicateManager.cs b/Penumbra/Mods/Editor/DuplicateManager.cs index bcecf264..56a19766 100644 --- a/Penumbra/Mods/Editor/DuplicateManager.cs +++ b/Penumbra/Mods/Editor/DuplicateManager.cs @@ -225,7 +225,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co if (!useModManager || !modManager.TryGetMod(modDirectory.Name, string.Empty, out var mod)) { mod = new Mod(modDirectory); - modManager.Creator.ReloadMod(mod, true, out _); + modManager.Creator.ReloadMod(mod, true, true, out _); } Clear(); diff --git a/Penumbra/Mods/Editor/ModFileEditor.cs b/Penumbra/Mods/Editor/ModFileEditor.cs index 55e0e94e..3b765215 100644 --- a/Penumbra/Mods/Editor/ModFileEditor.cs +++ b/Penumbra/Mods/Editor/ModFileEditor.cs @@ -151,7 +151,7 @@ public class ModFileEditor(ModFileCollection files, ModManager modManager, Commu if (deletions <= 0) return; - modManager.Creator.ReloadMod(mod, false, out _); + modManager.Creator.ReloadMod(mod, false, false, out _); files.UpdateAll(mod, option); } diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index d75ac671..e3eb5f54 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -256,7 +256,7 @@ public class ModMerger : IDisposable, IService if (dir == null) throw new Exception($"Could not split off mods, unable to create new mod with name {modName}."); - _mods.AddMod(dir); + _mods.AddMod(dir, false); result = _mods[^1]; if (mods.Count == 1) { diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 6b5ec378..81a33db6 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -3,12 +3,15 @@ using OtterGui.Services; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Manager; +using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; namespace Penumbra.Mods.Editor; -public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManager, ImcChecker imcChecker) : MetaDictionary, IService +public class ModMetaEditor( + ModGroupEditor groupEditor, + MetaFileManager metaFileManager, + ImcChecker imcChecker) : MetaDictionary, IService { public sealed class OtherOptionData : HashSet { @@ -64,11 +67,11 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage Changes = false; } - public bool DeleteDefaultValues(MetaDictionary dict) + public static bool DeleteDefaultValues(MetaFileManager metaFileManager, ImcChecker imcChecker, MetaDictionary dict) { var clone = dict.Clone(); dict.Clear(); - var ret = false; + var count = 0; foreach (var (key, value) in clone.Imc) { var defaultEntry = imcChecker.GetDefaultEntry(key, false); @@ -79,7 +82,7 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } @@ -93,7 +96,7 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } @@ -107,7 +110,7 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } @@ -121,7 +124,7 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } @@ -135,7 +138,7 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } @@ -149,22 +152,26 @@ public class ModMetaEditor(ModManager modManager, MetaFileManager metaFileManage else { Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ret = true; + ++count; } } - return ret; + if (count <= 0) + return false; + + Penumbra.Log.Debug($"Deleted {count} default-valued meta-entries from a mod option."); + return true; } public void DeleteDefaultValues() - => Changes = DeleteDefaultValues(this); + => Changes = DeleteDefaultValues(metaFileManager, imcChecker, this); public void Apply(IModDataContainer container) { if (!Changes) return; - modManager.OptionEditor.SetManipulations(container, this); + groupEditor.SetManipulations(container, this); Changes = false; } } diff --git a/Penumbra/Mods/Editor/ModNormalizer.cs b/Penumbra/Mods/Editor/ModNormalizer.cs index 43cfc1ee..3e367a3b 100644 --- a/Penumbra/Mods/Editor/ModNormalizer.cs +++ b/Penumbra/Mods/Editor/ModNormalizer.cs @@ -46,7 +46,7 @@ public class ModNormalizer(ModManager modManager, Configuration config, SaveServ if (!config.AutoReduplicateUiOnImport) return; - if (modManager.Creator.LoadMod(modDirectory, false) is not { } mod) + if (modManager.Creator.LoadMod(modDirectory, false, false) is not { } mod) return; Dictionary> paths = []; diff --git a/Penumbra/Mods/Manager/ModImportManager.cs b/Penumbra/Mods/Manager/ModImportManager.cs index d984d374..22cc0c86 100644 --- a/Penumbra/Mods/Manager/ModImportManager.cs +++ b/Penumbra/Mods/Manager/ModImportManager.cs @@ -79,7 +79,7 @@ public class ModImportManager(ModManager modManager, Configuration config, ModEd return false; } - modManager.AddMod(directory); + modManager.AddMod(directory, true); mod = modManager.LastOrDefault(); return mod != null && mod.ModPath == directory; } diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 59f8906e..bf1b6637 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -81,13 +81,13 @@ public sealed class ModManager : ModStorage, IDisposable, IService } /// Load a new mod and add it to the manager if successful. - public void AddMod(DirectoryInfo modFolder) + public void AddMod(DirectoryInfo modFolder, bool deleteDefaultMeta) { if (this.Any(m => m.ModPath.Name == modFolder.Name)) return; Creator.SplitMultiGroups(modFolder); - var mod = Creator.LoadMod(modFolder, true); + var mod = Creator.LoadMod(modFolder, true, deleteDefaultMeta); if (mod == null) return; @@ -141,7 +141,7 @@ public sealed class ModManager : ModStorage, IDisposable, IService var oldName = mod.Name; _communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath); - if (!Creator.ReloadMod(mod, true, out var metaChange)) + if (!Creator.ReloadMod(mod, true, false, out var metaChange)) { Penumbra.Log.Warning(mod.Name.Length == 0 ? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead." @@ -206,7 +206,7 @@ public sealed class ModManager : ModStorage, IDisposable, IService dir.Refresh(); mod.ModPath = dir; - if (!Creator.ReloadMod(mod, false, out var metaChange)) + if (!Creator.ReloadMod(mod, false, false, out var metaChange)) { Penumbra.Log.Error($"Error reloading moved mod {mod.Name}."); return; @@ -332,7 +332,7 @@ public sealed class ModManager : ModStorage, IDisposable, IService var queue = new ConcurrentQueue(); Parallel.ForEach(BasePath.EnumerateDirectories(), options, dir => { - var mod = Creator.LoadMod(dir, false); + var mod = Creator.LoadMod(dir, false, false); if (mod != null) queue.Enqueue(mod); }); diff --git a/Penumbra/Mods/Manager/ModMigration.cs b/Penumbra/Mods/Manager/ModMigration.cs index c7eb7cc5..3e58c515 100644 --- a/Penumbra/Mods/Manager/ModMigration.cs +++ b/Penumbra/Mods/Manager/ModMigration.cs @@ -82,7 +82,7 @@ public static partial class ModMigration foreach (var (gamePath, swapPath) in swaps) mod.Default.FileSwaps.Add(gamePath, swapPath); - creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true); + creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true, true); foreach (var group in mod.Groups) saveService.ImmediateSave(new ModSaveGroup(group, creator.Config.ReplaceNonAsciiOnImport)); @@ -182,7 +182,7 @@ public static partial class ModMigration Description = option.OptionDesc, }; AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles); - creator.IncorporateMetaChanges(subMod, mod.ModPath, false); + creator.IncorporateMetaChanges(subMod, mod.ModPath, false, true); return subMod; } @@ -196,7 +196,7 @@ public static partial class ModMigration Priority = priority, }; AddFilesToSubMod(subMod, mod.ModPath, option, seenMetaFiles); - creator.IncorporateMetaChanges(subMod, mod.ModPath, false); + creator.IncorporateMetaChanges(subMod, mod.ModPath, false, true); return subMod; } diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index 712630c6..7f18852d 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -39,7 +39,7 @@ public class ModGroupEditor( ImcModGroupEditor imcEditor, CommunicatorService communicator, SaveService saveService, - Configuration Config) : IService + Configuration config) : IService { public SingleModGroupEditor SingleEditor => singleEditor; @@ -57,7 +57,7 @@ public class ModGroupEditor( return; group.DefaultSettings = defaultOption; - saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.DefaultOptionChanged, group.Mod, group, null, null, -1); } @@ -68,9 +68,9 @@ public class ModGroupEditor( if (oldName == newName || !VerifyFileName(group.Mod, group, newName, true)) return; - saveService.ImmediateDelete(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.ImmediateDelete(new ModSaveGroup(group, config.ReplaceNonAsciiOnImport)); group.Name = newName; - saveService.ImmediateSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.ImmediateSave(new ModSaveGroup(group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupRenamed, group.Mod, group, null, null, -1); } @@ -81,7 +81,7 @@ public class ModGroupEditor( var idx = group.GetIndex(); communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, group, null, null, -1); mod.Groups.RemoveAt(idx); - saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); + saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport); communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupDeleted, mod, null, null, null, idx); } @@ -93,7 +93,7 @@ public class ModGroupEditor( if (!mod.Groups.Move(idxFrom, groupIdxTo)) return; - saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); + saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport); communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupMoved, mod, group, null, null, idxFrom); } @@ -104,7 +104,7 @@ public class ModGroupEditor( return; group.Priority = newPriority; - saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.PriorityChanged, group.Mod, group, null, null, -1); } @@ -115,7 +115,7 @@ public class ModGroupEditor( return; group.Description = newDescription; - saveService.QueueSave(new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, group.Mod, group, null, null, -1); } @@ -126,7 +126,7 @@ public class ModGroupEditor( return; option.Name = newName; - saveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(option.Group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); } @@ -137,7 +137,7 @@ public class ModGroupEditor( return; option.Description = newDescription; - saveService.QueueSave(new ModSaveGroup(option.Group, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(option.Group, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, option.Mod, option.Group, option, null, -1); } @@ -149,7 +149,7 @@ public class ModGroupEditor( communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.Manipulations.SetTo(manipulations); - saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(subMod, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } @@ -161,13 +161,13 @@ public class ModGroupEditor( communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.Files.SetTo(replacements); - saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(subMod, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } /// Forces a file save of the given container's group. public void ForceSave(IModDataContainer subMod, SaveType saveType = SaveType.Queue) - => saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + => saveService.Save(saveType, new ModSaveGroup(subMod, config.ReplaceNonAsciiOnImport)); /// Add additional file redirections to a given option, keeping already existing ones. Only fires an event if anything is actually added. public void AddFiles(IModDataContainer subMod, IReadOnlyDictionary additions) @@ -176,7 +176,7 @@ public class ModGroupEditor( subMod.Files.AddFrom(additions); if (oldCount != subMod.Files.Count) { - saveService.QueueSave(new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + saveService.QueueSave(new ModSaveGroup(subMod, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesAdded, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } } @@ -189,7 +189,7 @@ public class ModGroupEditor( communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); subMod.FileSwaps.SetTo(swaps); - saveService.Save(saveType, new ModSaveGroup(subMod, Config.ReplaceNonAsciiOnImport)); + saveService.Save(saveType, new ModSaveGroup(subMod, config.ReplaceNonAsciiOnImport)); communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, (Mod)subMod.Mod, subMod.Group, null, subMod, -1); } diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 0f4972e3..8cfdc9a7 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -10,6 +10,7 @@ using Penumbra.GameData.Data; using Penumbra.Import; using Penumbra.Import.Structs; using Penumbra.Meta; +using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; using Penumbra.Mods.Manager; using Penumbra.Mods.Settings; @@ -20,11 +21,12 @@ using Penumbra.String.Classes; namespace Penumbra.Mods; public partial class ModCreator( - SaveService _saveService, + SaveService saveService, Configuration config, - ModDataEditor _dataEditor, - MetaFileManager _metaFileManager, - GamePathParser _gamePathParser) : IService + ModDataEditor dataEditor, + MetaFileManager metaFileManager, + GamePathParser gamePathParser, + ImcChecker imcChecker) : IService { public readonly Configuration Config = config; @@ -34,7 +36,7 @@ public partial class ModCreator( try { 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); return newDir; } @@ -46,7 +48,7 @@ public partial class ModCreator( } /// Load a mod by its directory. - public Mod? LoadMod(DirectoryInfo modPath, bool incorporateMetaChanges) + public Mod? LoadMod(DirectoryInfo modPath, bool incorporateMetaChanges, bool deleteDefaultMetaChanges) { modPath.Refresh(); if (!modPath.Exists) @@ -56,7 +58,7 @@ public partial class ModCreator( } var mod = new Mod(modPath); - if (ReloadMod(mod, incorporateMetaChanges, out _)) + if (ReloadMod(mod, incorporateMetaChanges, deleteDefaultMetaChanges, out _)) return mod; // Can not be base path not existing because that is checked before. @@ -65,21 +67,29 @@ public partial class ModCreator( } /// Reload a mod from its mod path. - public bool ReloadMod(Mod mod, bool incorporateMetaChanges, out ModDataChangeType modDataChange) + public bool ReloadMod(Mod mod, bool incorporateMetaChanges, bool deleteDefaultMetaChanges, out ModDataChangeType modDataChange) { modDataChange = ModDataChangeType.Deletion; if (!Directory.Exists(mod.ModPath.FullName)) return false; - modDataChange = _dataEditor.LoadMeta(this, mod); + modDataChange = dataEditor.LoadMeta(this, mod); if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0) return false; - _dataEditor.LoadLocalData(mod); + dataEditor.LoadLocalData(mod); LoadDefaultOption(mod); LoadAllGroups(mod); if (incorporateMetaChanges) IncorporateAllMetaChanges(mod, true); + if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges) + { + foreach (var container in mod.AllDataContainers) + { + if (ModMetaEditor.DeleteDefaultValues(metaFileManager, imcChecker, container.Manipulations)) + saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport)); + } + } return true; } @@ -89,13 +99,13 @@ public partial class ModCreator( { mod.Groups.Clear(); var changes = false; - foreach (var file in _saveService.FileNames.GetOptionGroupFiles(mod)) + foreach (var file in saveService.FileNames.GetOptionGroupFiles(mod)) { var group = LoadModGroup(mod, file); if (group != null && mod.Groups.All(g => g.Name != group.Name)) { changes = changes - || _saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name, true) + || saveService.FileNames.OptionGroupFile(mod.ModPath.FullName, mod.Groups.Count, group.Name, true) != Path.Combine(file.DirectoryName!, ReplaceBadXivSymbols(file.Name, true)); mod.Groups.Add(group); } @@ -106,13 +116,13 @@ public partial class ModCreator( } if (changes) - _saveService.SaveAllOptionGroups(mod, true, Config.ReplaceNonAsciiOnImport); + saveService.SaveAllOptionGroups(mod, true, Config.ReplaceNonAsciiOnImport); } /// Load the default option for a given mod. public void LoadDefaultOption(Mod mod) { - var defaultFile = _saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport); + var defaultFile = saveService.FileNames.OptionGroupFile(mod, -1, Config.ReplaceNonAsciiOnImport); try { var jObject = File.Exists(defaultFile) ? JObject.Parse(File.ReadAllText(defaultFile)) : new JObject(); @@ -157,7 +167,7 @@ public partial class ModCreator( List deleteList = new(); foreach (var subMod in mod.AllDataContainers) { - var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false); + var (localChanges, localDeleteList) = IncorporateMetaChanges(subMod, mod.ModPath, false, true); changes |= localChanges; if (delete) deleteList.AddRange(localDeleteList); @@ -168,8 +178,8 @@ public partial class ModCreator( if (!changes) return; - _saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); - _saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport)); + saveService.SaveAllOptionGroups(mod, false, Config.ReplaceNonAsciiOnImport); + saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport)); } @@ -177,7 +187,7 @@ public partial class ModCreator( /// If .meta or .rgsp files are encountered, parse them and incorporate their meta changes into the mod. /// If delete is true, the files are deleted afterwards. /// - public (bool Changes, List DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete) + public (bool Changes, List DeleteList) IncorporateMetaChanges(IModDataContainer option, DirectoryInfo basePath, bool delete, bool deleteDefault) { var deleteList = new List(); var oldSize = option.Manipulations.Count; @@ -194,7 +204,7 @@ public partial class ModCreator( if (!file.Exists) continue; - var meta = new TexToolsMeta(_metaFileManager, _gamePathParser, File.ReadAllBytes(file.FullName), + var meta = new TexToolsMeta(metaFileManager, gamePathParser, File.ReadAllBytes(file.FullName), Config.KeepDefaultMetaChanges); Penumbra.Log.Verbose( $"Incorporating {file} as Metadata file of {meta.MetaManipulations.Count} manipulations {deleteString}"); @@ -207,7 +217,7 @@ public partial class ModCreator( if (!file.Exists) 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); Penumbra.Log.Verbose( $"Incorporating {file} as racial scaling file of {rgsp.MetaManipulations.Count} manipulations {deleteString}"); @@ -223,7 +233,11 @@ public partial class ModCreator( } DeleteDeleteList(deleteList, delete); - return (oldSize < option.Manipulations.Count, deleteList); + var changes = oldSize < option.Manipulations.Count; + if (deleteDefault && !Config.KeepDefaultMetaChanges) + changes |= ModMetaEditor.DeleteDefaultValues(metaFileManager, imcChecker, option.Manipulations); + + return (changes, deleteList); } /// @@ -250,7 +264,7 @@ public partial class ModCreator( group.Priority = priority; group.DefaultSettings = defaultSettings; group.OptionData.AddRange(subMods.Select(s => s.Clone(group))); - _saveService.ImmediateSaveSync(ModSaveGroup.WithoutMod(baseFolder, group, index, Config.ReplaceNonAsciiOnImport)); + saveService.ImmediateSaveSync(ModSaveGroup.WithoutMod(baseFolder, group, index, Config.ReplaceNonAsciiOnImport)); break; } case GroupType.Single: @@ -260,7 +274,7 @@ public partial class ModCreator( group.Priority = priority; group.DefaultSettings = defaultSettings; group.OptionData.AddRange(subMods.Select(s => s.ConvertToSingle(group))); - _saveService.ImmediateSaveSync(ModSaveGroup.WithoutMod(baseFolder, group, index, Config.ReplaceNonAsciiOnImport)); + saveService.ImmediateSaveSync(ModSaveGroup.WithoutMod(baseFolder, group, index, Config.ReplaceNonAsciiOnImport)); break; } } @@ -277,7 +291,8 @@ public partial class ModCreator( foreach (var (_, gamePath, file) in list) mod.Files.TryAdd(gamePath, file); - IncorporateMetaChanges(mod, baseFolder, true); + IncorporateMetaChanges(mod, baseFolder, true, true); + return mod; } @@ -288,15 +303,15 @@ public partial class ModCreator( internal void CreateDefaultFiles(DirectoryInfo directory) { var mod = new Mod(directory); - ReloadMod(mod, false, out _); + ReloadMod(mod, false, false, out _); foreach (var file in mod.FindUnusedFiles()) { if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath)) mod.Default.Files.TryAdd(gamePath, file); } - IncorporateMetaChanges(mod.Default, directory, true); - _saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport)); + IncorporateMetaChanges(mod.Default, directory, true, true); + saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport)); } /// Return the name of a new valid directory based on the base directory and the given name. @@ -333,7 +348,7 @@ public partial class ModCreator( { var mod = new Mod(baseDir); - var files = _saveService.FileNames.GetOptionGroupFiles(mod).ToList(); + var files = saveService.FileNames.GetOptionGroupFiles(mod).ToList(); var idx = 0; var reorder = false; foreach (var groupFile in files) diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index e4049482..b5499624 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -97,7 +97,7 @@ public class TemporaryMod : IMod defaultMod.Manipulations.UnionWith(manips); saveService.ImmediateSaveSync(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport)); - modManager.AddMod(dir); + modManager.AddMod(dir, false); Penumbra.Log.Information( $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}."); } diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index b75c5aef..6ed1b55d 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -281,7 +281,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService if (newDir == null) return; - _modManager.AddMod(newDir); + _modManager.AddMod(newDir, false); var mod = _modManager[^1]; if (!_swapData.WriteMod(_modManager, mod, mod.Default, _useFileSwaps ? ItemSwapContainer.WriteType.UseSwaps : ItemSwapContainer.WriteType.NoSwaps)) diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 2f76340b..8bdd95ab 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -180,7 +180,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Date: Thu, 29 Aug 2024 18:38:09 +0200 Subject: [PATCH 18/73] Stop raising errors when compressing the deleted files after updating Heliosphere mods. --- OtterGui | 2 +- Penumbra/Api/Api/ModsApi.cs | 2 +- Penumbra/UI/Tabs/SettingsTab.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OtterGui b/OtterGui index bfbde4f8..17bd4b75 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit bfbde4f8aa6acc8eb3ed8bc419d5ae2afc77b5f1 +Subproject commit 17bd4b75b6d7750c92b65caf09715886d4df57cf diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs index 31f20c5e..64e201be 100644 --- a/Penumbra/Api/Api/ModsApi.cs +++ b/Penumbra/Api/Api/ModsApi.cs @@ -91,7 +91,7 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable if (_config.UseFileSystemCompression) new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories), - CompressionAlgorithm.Xpress8K); + CompressionAlgorithm.Xpress8K, false); return ApiHelpers.Return(PenumbraApiEc.Success, args); } diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 27c7f2ed..9d8ea21c 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -816,13 +816,13 @@ public class SettingsTab : ITab, IUiService if (ImGuiUtil.DrawDisabledButton("Compress Existing Files", Vector2.Zero, "Try to compress all files in your root directory. This will take a while.", _compactor.MassCompactRunning || !_modManager.Valid)) - _compactor.StartMassCompact(_modManager.BasePath.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.Xpress8K); + _compactor.StartMassCompact(_modManager.BasePath.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.Xpress8K, true); ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Decompress Existing Files", Vector2.Zero, "Try to decompress all files in your root directory. This will take a while.", _compactor.MassCompactRunning || !_modManager.Valid)) - _compactor.StartMassCompact(_modManager.BasePath.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.None); + _compactor.StartMassCompact(_modManager.BasePath.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.None, true); if (_compactor.MassCompactRunning) { From 5c5e45114f25f9429d8757b6edf852ecc37173c9 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Aug 2024 18:38:37 +0200 Subject: [PATCH 19/73] Make loading mods for advanced editing async. --- Penumbra/Mods/Editor/ModEditor.cs | 65 +++++++++---- Penumbra/UI/AdvancedWindow/ModEditWindow.cs | 102 +++++++++++++++----- 2 files changed, 124 insertions(+), 43 deletions(-) diff --git a/Penumbra/Mods/Editor/ModEditor.cs b/Penumbra/Mods/Editor/ModEditor.cs index cacb7f88..19ca7022 100644 --- a/Penumbra/Mods/Editor/ModEditor.cs +++ b/Penumbra/Mods/Editor/ModEditor.cs @@ -25,6 +25,21 @@ public class ModEditor( public readonly MdlMaterialEditor MdlMaterialEditor = mdlMaterialEditor; public readonly FileCompactor Compactor = compactor; + + public bool IsLoading + { + get + { + lock (_lock) + { + return _loadingMod is { IsCompleted: false }; + } + } + } + + private readonly object _lock = new(); + private Task? _loadingMod; + public Mod? Mod { get; private set; } public int GroupIdx { get; private set; } public int DataIdx { get; private set; } @@ -32,28 +47,42 @@ public class ModEditor( public IModGroup? Group { get; private set; } public IModDataContainer? Option { get; private set; } - public void LoadMod(Mod mod) - => LoadMod(mod, -1, 0); - - public void LoadMod(Mod mod, int groupIdx, int dataIdx) + public async Task LoadMod(Mod mod, int groupIdx, int dataIdx) { - Mod = mod; - LoadOption(groupIdx, dataIdx, true); - Files.UpdateAll(mod, Option!); - SwapEditor.Revert(Option!); - MetaEditor.Load(Mod!, Option!); - Duplicates.Clear(); - MdlMaterialEditor.ScanModels(Mod!); + await AppendTask(() => + { + Mod = mod; + LoadOption(groupIdx, dataIdx, true); + Files.UpdateAll(mod, Option!); + SwapEditor.Revert(Option!); + MetaEditor.Load(Mod!, Option!); + Duplicates.Clear(); + MdlMaterialEditor.ScanModels(Mod!); + }); } - public void LoadOption(int groupIdx, int dataIdx) + private Task AppendTask(Action run) { - LoadOption(groupIdx, dataIdx, true); - SwapEditor.Revert(Option!); - Files.UpdatePaths(Mod!, Option!); - MetaEditor.Load(Mod!, Option!); - FileEditor.Clear(); - Duplicates.Clear(); + lock (_lock) + { + if (_loadingMod == null || _loadingMod.IsCompleted) + return _loadingMod = Task.Run(run); + + return _loadingMod = _loadingMod.ContinueWith(_ => run()); + } + } + + public async Task LoadOption(int groupIdx, int dataIdx) + { + await AppendTask(() => + { + LoadOption(groupIdx, dataIdx, true); + SwapEditor.Revert(Option!); + Files.UpdatePaths(Mod!, Option!); + MetaEditor.Load(Mod!, Option!); + FileEditor.Clear(); + Duplicates.Clear(); + }); } /// Load the correct option by indices for the currently loaded mod if possible, unload if not. diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 7bb067d8..f2fe8b9e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -8,6 +8,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; using Penumbra.Communication; @@ -51,34 +52,68 @@ public partial class ModEditWindow : Window, IDisposable, IUiService private Vector2 _iconSize = Vector2.Zero; private bool _allowReduplicate; - public Mod? Mod { get; private set; } + public Mod? Mod { get; private set; } + + + public bool IsLoading + { + get + { + lock (_lock) + { + return _editor.IsLoading || _loadingMod is { IsCompleted: false }; + } + } + } + + private readonly object _lock = new(); + private Task? _loadingMod; + + + private void AppendTask(Action run) + { + lock (_lock) + { + if (_loadingMod == null || _loadingMod.IsCompleted) + _loadingMod = Task.Run(run); + else + _loadingMod = _loadingMod.ContinueWith(_ => run()); + } + } public void ChangeMod(Mod mod) { if (mod == Mod) return; - _editor.LoadMod(mod, -1, 0); - Mod = mod; - - SizeConstraints = new WindowSizeConstraints + WindowName = $"{mod.Name} (LOADING){WindowBaseLabel}"; + AppendTask(() => { - MinimumSize = new Vector2(1240, 600), - MaximumSize = 4000 * Vector2.One, - }; - _selectedFiles.Clear(); - _modelTab.Reset(); - _materialTab.Reset(); - _shaderPackageTab.Reset(); - _itemSwapTab.UpdateMod(mod, _activeCollections.Current[mod.Index].Settings); - UpdateModels(); - _forceTextureStartPath = true; + _editor.LoadMod(mod, -1, 0).Wait(); + Mod = mod; + + SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(1240, 600), + MaximumSize = 4000 * Vector2.One, + }; + _selectedFiles.Clear(); + _modelTab.Reset(); + _materialTab.Reset(); + _shaderPackageTab.Reset(); + _itemSwapTab.UpdateMod(mod, _activeCollections.Current[mod.Index].Settings); + UpdateModels(); + _forceTextureStartPath = true; + }); } public void ChangeOption(IModDataContainer? subMod) { - var (groupIdx, dataIdx) = subMod?.GetDataIndices() ?? (-1, 0); - _editor.LoadOption(groupIdx, dataIdx); + AppendTask(() => + { + var (groupIdx, dataIdx) = subMod?.GetDataIndices() ?? (-1, 0); + _editor.LoadOption(groupIdx, dataIdx).Wait(); + }); } public void UpdateModels() @@ -92,6 +127,9 @@ public partial class ModEditWindow : Window, IDisposable, IUiService public override void PreDraw() { + if (IsLoading) + return; + using var performance = _performance.Measure(PerformanceType.UiAdvancedWindow); var sb = new StringBuilder(256); @@ -144,13 +182,16 @@ public partial class ModEditWindow : Window, IDisposable, IUiService public override void OnClose() { - _left.Dispose(); - _right.Dispose(); - _materialTab.Reset(); - _modelTab.Reset(); - _shaderPackageTab.Reset(); _config.Ephemeral.AdvancedEditingOpen = false; _config.Ephemeral.Save(); + AppendTask(() => + { + _left.Dispose(); + _right.Dispose(); + _materialTab.Reset(); + _modelTab.Reset(); + _shaderPackageTab.Reset(); + }); } public override void Draw() @@ -163,6 +204,17 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _config.Ephemeral.Save(); } + if (IsLoading) + { + var radius = 100 * ImUtf8.GlobalScale; + var thickness = (int) (20 * ImUtf8.GlobalScale); + var offsetX = ImGui.GetContentRegionAvail().X / 2 - radius; + var offsetY = ImGui.GetContentRegionAvail().Y / 2 - radius; + ImGui.SetCursorPos(ImGui.GetCursorPos() + new Vector2(offsetX, offsetY)); + ImUtf8.Spinner("##spinner"u8, radius, thickness, ImGui.GetColorU32(ImGuiCol.Text)); + return; + } + using var tabBar = ImRaii.TabBar("##tabs"); if (!tabBar) return; @@ -405,14 +457,14 @@ public partial class ModEditWindow : Window, IDisposable, IUiService if (ImGuiUtil.DrawDisabledButton(defaultOption, width, "Switch to the default option for the mod.\nThis resets unsaved changes.", _editor.Option is DefaultSubMod)) { - _editor.LoadOption(-1, 0); + _editor.LoadOption(-1, 0).Wait(); ret = true; } ImGui.SameLine(); if (ImGuiUtil.DrawDisabledButton("Refresh Data", width, "Refresh data for the current option.\nThis resets unsaved changes.", false)) { - _editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx); + _editor.LoadMod(_editor.Mod!, _editor.GroupIdx, _editor.DataIdx).Wait(); ret = true; } @@ -430,7 +482,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService if (ImGui.Selectable(option.GetFullName(), option == _editor.Option)) { var (groupIdx, dataIdx) = option.GetDataIndices(); - _editor.LoadOption(groupIdx, dataIdx); + _editor.LoadOption(groupIdx, dataIdx).Wait(); ret = true; } } From de3644e9e131baf5f4f953bc0d034a116c7da4d3 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Aug 2024 18:46:37 +0200 Subject: [PATCH 20/73] Make BC4 textures importable. --- Penumbra/Import/Textures/TexFileParser.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index ae4a39c0..1bf282e5 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -177,6 +177,7 @@ public static class TexFileParser DXGIFormat.BC1UNorm => TexFile.TextureFormat.BC1, DXGIFormat.BC2UNorm => TexFile.TextureFormat.BC2, DXGIFormat.BC3UNorm => TexFile.TextureFormat.BC3, + DXGIFormat.BC4UNorm => (TexFile.TextureFormat)0x6120, DXGIFormat.BC5UNorm => TexFile.TextureFormat.BC5, DXGIFormat.BC7UNorm => TexFile.TextureFormat.BC7, DXGIFormat.R16G16B16A16Typeless => TexFile.TextureFormat.D16, @@ -202,6 +203,7 @@ public static class TexFileParser TexFile.TextureFormat.BC1 => DXGIFormat.BC1UNorm, TexFile.TextureFormat.BC2 => DXGIFormat.BC2UNorm, TexFile.TextureFormat.BC3 => DXGIFormat.BC3UNorm, + (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, TexFile.TextureFormat.BC5 => DXGIFormat.BC5UNorm, TexFile.TextureFormat.BC7 => DXGIFormat.BC7UNorm, TexFile.TextureFormat.D16 => DXGIFormat.R16G16B16A16Typeless, From 2a7d2ef0d5cef009c60a701235a1786e56d191b2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Aug 2024 18:58:30 +0200 Subject: [PATCH 21/73] Allow reading BC6. --- Penumbra/Import/Textures/TexFileParser.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 1bf282e5..0d817fa1 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -177,8 +177,9 @@ public static class TexFileParser DXGIFormat.BC1UNorm => TexFile.TextureFormat.BC1, DXGIFormat.BC2UNorm => TexFile.TextureFormat.BC2, DXGIFormat.BC3UNorm => TexFile.TextureFormat.BC3, - DXGIFormat.BC4UNorm => (TexFile.TextureFormat)0x6120, + DXGIFormat.BC4UNorm => (TexFile.TextureFormat)0x6120, // TODO: upstream to Lumina DXGIFormat.BC5UNorm => TexFile.TextureFormat.BC5, + DXGIFormat.BC6HUF16 => (TexFile.TextureFormat)0x6330, // TODO: upstream to Lumina DXGIFormat.BC7UNorm => TexFile.TextureFormat.BC7, DXGIFormat.R16G16B16A16Typeless => TexFile.TextureFormat.D16, DXGIFormat.R24G8Typeless => TexFile.TextureFormat.D24S8, @@ -203,8 +204,9 @@ public static class TexFileParser TexFile.TextureFormat.BC1 => DXGIFormat.BC1UNorm, TexFile.TextureFormat.BC2 => DXGIFormat.BC2UNorm, TexFile.TextureFormat.BC3 => DXGIFormat.BC3UNorm, - (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, + (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, // TODO: upstream to Lumina TexFile.TextureFormat.BC5 => DXGIFormat.BC5UNorm, + (TexFile.TextureFormat)0x6330 => DXGIFormat.BC6HUF16, // TODO: upstream to Lumina TexFile.TextureFormat.BC7 => DXGIFormat.BC7UNorm, TexFile.TextureFormat.D16 => DXGIFormat.R16G16B16A16Typeless, TexFile.TextureFormat.D24S8 => DXGIFormat.R24G8Typeless, From 176001195ba16d69c4540d2dbed9607932337ee6 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 29 Aug 2024 21:13:33 +0200 Subject: [PATCH 22/73] Improve mod filters. --- OtterGui | 2 +- Penumbra/Import/Textures/TexFileParser.cs | 2 +- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 23 +++++---- Penumbra/UI/ModsTab/ModFilter.cs | 49 ++++++++++---------- 4 files changed, 41 insertions(+), 35 deletions(-) diff --git a/OtterGui b/OtterGui index 17bd4b75..3e6b0857 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit 17bd4b75b6d7750c92b65caf09715886d4df57cf +Subproject commit 3e6b085749741f35dd6732c33d0720c6a51ebb97 diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 0d817fa1..979e4d3c 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -204,7 +204,7 @@ public static class TexFileParser TexFile.TextureFormat.BC1 => DXGIFormat.BC1UNorm, TexFile.TextureFormat.BC2 => DXGIFormat.BC2UNorm, TexFile.TextureFormat.BC3 => DXGIFormat.BC3UNorm, - (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, // TODO: upstream to Lumina + (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, // TODO: upstream to Lumina TexFile.TextureFormat.BC5 => DXGIFormat.BC5UNorm, (TexFile.TextureFormat)0x6330 => DXGIFormat.BC6HUF16, // TODO: upstream to Lumina TexFile.TextureFormat.BC7 => DXGIFormat.BC7UNorm, diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 8bdd95ab..7a165feb 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -9,6 +9,8 @@ using OtterGui.Filesystem; using OtterGui.FileSystem.Selector; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; +using OtterGui.Text.Widget; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -84,8 +86,8 @@ public sealed class ModFileSystemSelector : FileSystemSelector filter switch - { - ModFilter.Enabled => "Enabled", - ModFilter.Disabled => "Disabled", - ModFilter.Favorite => "Favorite", - ModFilter.NotFavorite => "No Favorite", - ModFilter.NoConflict => "No Conflicts", - ModFilter.SolvedConflict => "Solved Conflicts", - ModFilter.UnsolvedConflict => "Unsolved Conflicts", - ModFilter.HasNoMetaManipulations => "No Meta Manipulations", - ModFilter.HasMetaManipulations => "Meta Manipulations", - ModFilter.HasNoFileSwaps => "No File Swaps", - ModFilter.HasFileSwaps => "File Swaps", - ModFilter.HasNoConfig => "No Configuration", - ModFilter.HasConfig => "Configuration", - ModFilter.HasNoFiles => "No Files", - ModFilter.HasFiles => "Files", - ModFilter.IsNew => "Newly Imported", - ModFilter.NotNew => "Not Newly Imported", - ModFilter.Inherited => "Inherited Configuration", - ModFilter.Uninherited => "Own Configuration", - ModFilter.Undefined => "Not Configured", - _ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null), - }; + public static IReadOnlyList<(ModFilter On, ModFilter Off, string Name)> TriStatePairs = + [ + (ModFilter.Enabled, ModFilter.Disabled, "Enabled"), + (ModFilter.IsNew, ModFilter.NotNew, "Newly Imported"), + (ModFilter.Favorite, ModFilter.NotFavorite, "Favorite"), + (ModFilter.HasConfig, ModFilter.HasNoConfig, "Has Options"), + (ModFilter.HasFiles, ModFilter.HasNoFiles, "Has Redirections"), + (ModFilter.HasMetaManipulations, ModFilter.HasNoMetaManipulations, "Has Meta Manipulations"), + (ModFilter.HasFileSwaps, ModFilter.HasNoFileSwaps, "Has File Swaps"), + ]; + + public static IReadOnlyList> Groups = + [ + [ + (ModFilter.NoConflict, "Has No Conflicts"), + (ModFilter.SolvedConflict, "Has Solved Conflicts"), + (ModFilter.UnsolvedConflict, "Has Unsolved Conflicts"), + ], + [ + (ModFilter.Undefined, "Not Configured"), + (ModFilter.Inherited, "Inherited Configuration"), + (ModFilter.Uninherited, "Own Configuration"), + ], + ]; } From ff3e5410aac9e23606317e179f6278e710cb11ee Mon Sep 17 00:00:00 2001 From: Actions User Date: Thu, 29 Aug 2024 19:18:17 +0000 Subject: [PATCH 23/73] [CI] Updating repo.json for testing_1.2.1.2 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 5a274d73..6f9b8c69 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.1", + "TestingAssemblyVersion": "1.2.1.2", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.2/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From fb144d0b74ce1b263eb3e69625c37518e3725a1b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 14:20:19 +0200 Subject: [PATCH 24/73] Cleanup. --- Penumbra/Import/Textures/TexFileParser.cs | 4 ++-- Penumbra/Mods/Editor/ModMetaEditor.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 979e4d3c..220095c1 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -179,7 +179,7 @@ public static class TexFileParser DXGIFormat.BC3UNorm => TexFile.TextureFormat.BC3, DXGIFormat.BC4UNorm => (TexFile.TextureFormat)0x6120, // TODO: upstream to Lumina DXGIFormat.BC5UNorm => TexFile.TextureFormat.BC5, - DXGIFormat.BC6HUF16 => (TexFile.TextureFormat)0x6330, // TODO: upstream to Lumina + DXGIFormat.BC6HSF16 => (TexFile.TextureFormat)0x6330, // TODO: upstream to Lumina DXGIFormat.BC7UNorm => TexFile.TextureFormat.BC7, DXGIFormat.R16G16B16A16Typeless => TexFile.TextureFormat.D16, DXGIFormat.R24G8Typeless => TexFile.TextureFormat.D24S8, @@ -206,7 +206,7 @@ public static class TexFileParser TexFile.TextureFormat.BC3 => DXGIFormat.BC3UNorm, (TexFile.TextureFormat)0x6120 => DXGIFormat.BC4UNorm, // TODO: upstream to Lumina TexFile.TextureFormat.BC5 => DXGIFormat.BC5UNorm, - (TexFile.TextureFormat)0x6330 => DXGIFormat.BC6HUF16, // TODO: upstream to Lumina + (TexFile.TextureFormat)0x6330 => DXGIFormat.BC6HSF16, // TODO: upstream to Lumina TexFile.TextureFormat.BC7 => DXGIFormat.BC7UNorm, TexFile.TextureFormat.D16 => DXGIFormat.R16G16B16A16Typeless, TexFile.TextureFormat.D24S8 => DXGIFormat.R24G8Typeless, diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 81a33db6..07a54391 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -156,7 +156,7 @@ public class ModMetaEditor( } } - if (count <= 0) + if (count == 0) return false; Penumbra.Log.Debug($"Deleted {count} default-valued meta-entries from a mod option."); From 04582ba00b8fedfb32a8ad7fbed0230ea89126f7 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 14:20:31 +0200 Subject: [PATCH 25/73] Add CustomArmor to UI events. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index a38e9bcf..97e9f427 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit a38e9bcfb80c456102945bbb4c59f5621cae0442 +Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623 diff --git a/Penumbra.GameData b/Penumbra.GameData index c43c5cac..bb281fb0 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c43c5cac4cee092bf0aed8d46bab112b037ef8f2 +Subproject commit bb281fb01d88d6fd815a286f87049978ef05de59 From 75858a61b5092b1567e332417610ef36a4f7e122 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 14:20:44 +0200 Subject: [PATCH 26/73] Fix MetaManipulations not resetting count when clearing. --- Penumbra/Meta/Manipulations/MetaDictionary.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 1093c6c5..70d4fd47 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -69,6 +69,7 @@ public class MetaDictionary public void Clear() { + Count = 0; _imc.Clear(); _eqp.Clear(); _eqdp.Clear(); From 6b858dc5ac9d9cd967601a3fdac91048c87bf7c5 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 14:23:34 +0200 Subject: [PATCH 27/73] Hmpf. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index bb281fb0..66bc00dc 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit bb281fb01d88d6fd815a286f87049978ef05de59 +Subproject commit 66bc00dc8517204e58c6515af5aec0ba6d196716 From 1b17404876d9248c77649b7831eda57332f84f96 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 31 Aug 2024 20:52:01 +0200 Subject: [PATCH 28/73] Fix small issue with changed item tooltips. --- Penumbra/Penumbra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index 6f0b63ce..b6b19ef2 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -114,7 +114,7 @@ public class Penumbra : IDalamudPlugin var itemSheet = _services.GetService().GetExcelSheet()!; _communicatorService.ChangedItemHover.Subscribe(it => { - if (it is IdentifiedItem) + if (it is IdentifiedItem { Item.Id.IsItem: true }) ImGui.TextUnformatted("Left Click to create an item link in chat."); }, ChangedItemHover.Priority.Link); From 22cbecc6a459a3700b0d5f663847f098c69963aa Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 8 Sep 2024 22:48:15 +0200 Subject: [PATCH 29/73] Add Page to mod group data for TT interop. --- Penumbra/Mods/Groups/IModGroup.cs | 23 +++++++++++++++-------- Penumbra/Mods/Groups/ImcModGroup.cs | 1 + Penumbra/Mods/Groups/MultiModGroup.cs | 1 + Penumbra/Mods/Groups/SingleModGroup.cs | 1 + 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Penumbra/Mods/Groups/IModGroup.cs b/Penumbra/Mods/Groups/IModGroup.cs index c5654019..a6f6e20d 100644 --- a/Penumbra/Mods/Groups/IModGroup.cs +++ b/Penumbra/Mods/Groups/IModGroup.cs @@ -24,14 +24,21 @@ public interface IModGroup { public const int MaxMultiOptions = 32; - public Mod Mod { get; } - public string Name { get; set; } - public string Description { get; set; } - public string Image { get; set; } - public GroupType Type { get; } - public GroupDrawBehaviour Behaviour { get; } - public ModPriority Priority { get; set; } - public Setting DefaultSettings { get; set; } + public Mod Mod { get; } + public string Name { get; set; } + public string Description { get; set; } + + /// Unused in Penumbra but for better TexTools interop. + public string Image { get; set; } + + public GroupType Type { get; } + public GroupDrawBehaviour Behaviour { get; } + public ModPriority Priority { get; set; } + + /// Unused in Penumbra but for better TexTools interop. + public int Page { get; set; } + + public Setting DefaultSettings { get; set; } public FullPath? FindBestMatch(Utf8GamePath gamePath); public IModOption? AddOption(string name, string description = ""); diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index d42804ba..2b020184 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -29,6 +29,7 @@ public class ImcModGroup(Mod mod) : IModGroup => GroupDrawBehaviour.MultiSelection; public ModPriority Priority { get; set; } = ModPriority.Default; + public int Page { get; set; } public Setting DefaultSettings { get; set; } = Setting.Zero; public ImcIdentifier Identifier; diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index 9cf7e6a3..24dcc849 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -28,6 +28,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup public string Description { get; set; } = string.Empty; public string Image { get; set; } = string.Empty; public ModPriority Priority { get; set; } + public int Page { get; set; } public Setting DefaultSettings { get; set; } public readonly List OptionData = []; diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index 723cd5b1..fddb96d6 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -26,6 +26,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup public string Description { get; set; } = string.Empty; public string Image { get; set; } = string.Empty; public ModPriority Priority { get; set; } + public int Page { get; set; } public Setting DefaultSettings { get; set; } public readonly List OptionData = []; From bd59591ed8650c5ae544fa3546fbc5e4fa5e813b Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 8 Sep 2024 23:42:19 +0200 Subject: [PATCH 30/73] Add display of ImportDate and allow resetting it, add button to open local data json. --- Penumbra/Mods/Manager/ModDataEditor.cs | 11 ++++++ Penumbra/Mods/ModCreator.cs | 2 +- Penumbra/UI/ModsTab/ModFileSystemSelector.cs | 19 +++++----- Penumbra/UI/ModsTab/ModPanelEditTab.cs | 37 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index 7a0467d0..933620d9 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -249,6 +249,17 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic communicatorService.ModDataChanged.Invoke(ModDataChangeType.Favorite, mod, null); } + public void ResetModImportDate(Mod mod) + { + var newDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (mod.ImportDate == newDate) + return; + + mod.ImportDate = newDate; + saveService.QueueSave(new ModLocalData(mod)); + communicatorService.ModDataChanged.Invoke(ModDataChangeType.ImportDate, mod, null); + } + public void ChangeModNote(Mod mod, string newNote) { if (mod.Note == newNote) diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 8cfdc9a7..fe027ca4 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -77,7 +77,7 @@ public partial class ModCreator( if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0) return false; - dataEditor.LoadLocalData(mod); + modDataChange |= dataEditor.LoadLocalData(mod); LoadDefaultOption(mod); LoadAllGroups(mod); if (incorporateMetaChanges) diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 7a165feb..0781312c 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -447,16 +447,15 @@ public sealed class ModFileSystemSelector : FileSystemSelector Date: Mon, 9 Sep 2024 14:10:54 +0200 Subject: [PATCH 31/73] Allow copying paths out of the resource logger. --- .../ResourceWatcher/ResourceWatcherTable.cs | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs index 33e301ae..2bb71b87 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs @@ -4,6 +4,7 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Table; +using OtterGui.Text; using Penumbra.Enums; using Penumbra.Interop.Structs; using Penumbra.String; @@ -52,36 +53,41 @@ internal sealed class ResourceWatcherTable : Table private static unsafe void DrawByteString(CiByteString path, float length) { - Vector2 vec; - ImGuiNative.igCalcTextSize(&vec, path.Path, path.Path + path.Length, 0, 0); - if (vec.X <= length) + if (path.IsEmpty) + return; + + var size = ImUtf8.CalcTextSize(path.Span); + var clicked = false; + if (size.X <= length) { - ImGuiNative.igTextUnformatted(path.Path, path.Path + path.Length); + clicked = ImUtf8.Selectable(path.Span); } else { - var fileName = path.LastIndexOf((byte)'/'); - CiByteString shortPath; - if (fileName != -1) + var fileName = path.LastIndexOf((byte)'/'); + using (ImRaii.Group()) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale)); - using var font = ImRaii.PushFont(UiBuilder.IconFont); - ImGui.TextUnformatted(FontAwesomeIcon.EllipsisH.ToIconString()); - ImGui.SameLine(); - shortPath = path.Substring(fileName, path.Length - fileName); - } - else - { - shortPath = path; + CiByteString shortPath; + if (fileName != -1) + { + using var font = ImRaii.PushFont(UiBuilder.IconFont); + clicked = ImUtf8.Selectable(FontAwesomeIcon.EllipsisH.ToIconString()); + ImUtf8.SameLineInner(); + shortPath = path.Substring(fileName, path.Length - fileName); + } + else + { + shortPath = path; + } + + clicked |= ImUtf8.Selectable(shortPath.Span, false, ImGuiSelectableFlags.AllowItemOverlap); } - ImGuiNative.igTextUnformatted(shortPath.Path, shortPath.Path + shortPath.Length); - if (ImGui.IsItemClicked()) - ImGuiNative.igSetClipboardText(path.Path); - - if (ImGui.IsItemHovered()) - ImGuiNative.igSetTooltip(path.Path); + ImUtf8.HoverTooltip(path.Span); } + + if (clicked) + ImUtf8.SetClipboardText(path.Span); } private sealed class RecordTypeColumn : ColumnFlags From 10ce5da8c9bb5d2b226a940a23f1ef57026aec81 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 9 Sep 2024 13:42:42 +0000 Subject: [PATCH 32/73] [CI] Updating repo.json for testing_1.2.1.3 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 6f9b8c69..cdd622c9 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.2", + "TestingAssemblyVersion": "1.2.1.3", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.2/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.3/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 26371d42f75768a0bda28da0f2fc8976075fdb82 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 9 Sep 2024 16:51:23 +0200 Subject: [PATCH 33/73] Be less dumb. --- Penumbra/Mods/Groups/ImcModGroup.cs | 7 +------ Penumbra/Mods/Groups/ModSaveGroup.cs | 17 +++++++++++++++++ Penumbra/Mods/Groups/MultiModGroup.cs | 13 ++++--------- Penumbra/Mods/Groups/SingleModGroup.cs | 13 ++++--------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index 2b020184..f8b4b2ef 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -170,14 +170,10 @@ public class ImcModGroup(Mod mod) : IModGroup var identifier = ImcIdentifier.FromJson(json[nameof(Identifier)] as JObject); var ret = new ImcModGroup(mod) { - Name = json[nameof(Name)]?.ToObject() ?? string.Empty, - Description = json[nameof(Description)]?.ToObject() ?? string.Empty, - Image = json[nameof(Image)]?.ToObject() ?? string.Empty, - Priority = json[nameof(Priority)]?.ToObject() ?? ModPriority.Default, DefaultEntry = json[nameof(DefaultEntry)]?.ToObject() ?? new ImcEntry(), AllVariants = json[nameof(AllVariants)]?.ToObject() ?? false, }; - if (ret.Name.Length == 0) + if (!ModSaveGroup.ReadJsonBase(json, ret)) return null; if (!identifier.HasValue || ret.DefaultEntry.MaterialId == 0) @@ -216,7 +212,6 @@ public class ImcModGroup(Mod mod) : IModGroup } ret.Identifier = identifier.Value; - ret.DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero; ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); return ret; } diff --git a/Penumbra/Mods/Groups/ModSaveGroup.cs b/Penumbra/Mods/Groups/ModSaveGroup.cs index c465822b..bda70b54 100644 --- a/Penumbra/Mods/Groups/ModSaveGroup.cs +++ b/Penumbra/Mods/Groups/ModSaveGroup.cs @@ -1,4 +1,7 @@ using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Penumbra.GameData.Files.ShaderStructs; +using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; using Penumbra.Services; @@ -90,6 +93,8 @@ public readonly struct ModSaveGroup : ISavable jWriter.WriteValue(group.Description); jWriter.WritePropertyName(nameof(group.Image)); jWriter.WriteValue(group.Image); + jWriter.WritePropertyName(nameof(group.Page)); + jWriter.WriteValue(group.Page); jWriter.WritePropertyName(nameof(group.Priority)); jWriter.WriteValue(group.Priority.Value); jWriter.WritePropertyName(nameof(group.Type)); @@ -97,4 +102,16 @@ public readonly struct ModSaveGroup : ISavable jWriter.WritePropertyName(nameof(group.DefaultSettings)); jWriter.WriteValue(group.DefaultSettings.Value); } + + public static bool ReadJsonBase(JObject json, IModGroup group) + { + group.Name = json[nameof(IModGroup.Name)]?.ToObject() ?? string.Empty; + group.Description = json[nameof(IModGroup.Description)]?.ToObject() ?? string.Empty; + group.Image = json[nameof(IModGroup.Image)]?.ToObject() ?? string.Empty; + group.Page = json[nameof(IModGroup.Page)]?.ToObject() ?? 0; + group.Priority = json[nameof(IModGroup.Priority)]?.ToObject() ?? ModPriority.Default; + group.DefaultSettings = json[nameof(IModGroup.DefaultSettings)]?.ToObject() ?? Setting.Zero; + + return group.Name.Length > 0; + } } diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index 24dcc849..0c9aa805 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -67,15 +67,8 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup public static MultiModGroup? Load(Mod mod, JObject json) { - var ret = new MultiModGroup(mod) - { - Name = json[nameof(Name)]?.ToObject() ?? string.Empty, - Description = json[nameof(Description)]?.ToObject() ?? string.Empty, - Image = json[nameof(Image)]?.ToObject() ?? string.Empty, - Priority = json[nameof(Priority)]?.ToObject() ?? ModPriority.Default, - DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero, - }; - if (ret.Name.Length == 0) + var ret = new MultiModGroup(mod); + if (!ModSaveGroup.ReadJsonBase(json, ret)) return null; var options = json["Options"]; @@ -106,6 +99,8 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup Name = Name, Description = Description, Priority = Priority, + Image = Image, + Page = Page, DefaultSettings = DefaultSettings.TurnMulti(OptionData.Count), }; single.OptionData.AddRange(OptionData.Select(o => o.ConvertToSingle(single))); diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index fddb96d6..ab0c2d4f 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -63,15 +63,8 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup public static SingleModGroup? Load(Mod mod, JObject json) { var options = json["Options"]; - var ret = new SingleModGroup(mod) - { - Name = json[nameof(Name)]?.ToObject() ?? string.Empty, - Description = json[nameof(Description)]?.ToObject() ?? string.Empty, - Image = json[nameof(Image)]?.ToObject() ?? string.Empty, - Priority = json[nameof(Priority)]?.ToObject() ?? ModPriority.Default, - DefaultSettings = json[nameof(DefaultSettings)]?.ToObject() ?? Setting.Zero, - }; - if (ret.Name.Length == 0) + var ret = new SingleModGroup(mod); + if (!ModSaveGroup.ReadJsonBase(json, ret)) return null; if (options != null) @@ -92,6 +85,8 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup Name = Name, Description = Description, Priority = Priority, + Image = Image, + Page = Page, DefaultSettings = Setting.Multi((int)DefaultSettings.Value), }; multi.OptionData.AddRange(OptionData.Select((o, i) => o.ConvertToMulti(multi, new ModPriority(i)))); From ac1ea124d93e9a17e6fe0fe71b920d31d4c5fe99 Mon Sep 17 00:00:00 2001 From: Actions User Date: Mon, 9 Sep 2024 14:53:17 +0000 Subject: [PATCH 34/73] [CI] Updating repo.json for testing_1.2.1.4 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index cdd622c9..6625cb24 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.3", + "TestingAssemblyVersion": "1.2.1.4", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.3/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.4/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 00fbb2686b864dcfe91bb9e67415b9059fc53a55 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Sep 2024 22:54:06 +0200 Subject: [PATCH 35/73] Add option to apply only attributes from IMC group. --- Penumbra/Meta/ImcChecker.cs | 25 +++++++--------- Penumbra/Mods/Editor/ModMetaEditor.cs | 9 +++--- Penumbra/Mods/Groups/ImcModGroup.cs | 30 +++++++++++++------ .../Manager/OptionEditor/ImcModGroupEditor.cs | 10 +++++++ Penumbra/Mods/ModCreator.cs | 7 ++--- .../UI/AdvancedWindow/Meta/ImcMetaDrawer.cs | 4 +-- Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs | 6 ++-- .../ModsTab/Groups/ImcModGroupEditDrawer.cs | 12 ++++++-- 8 files changed, 63 insertions(+), 40 deletions(-) diff --git a/Penumbra/Meta/ImcChecker.cs b/Penumbra/Meta/ImcChecker.cs index 4e3ff11b..a415c9b0 100644 --- a/Penumbra/Meta/ImcChecker.cs +++ b/Penumbra/Meta/ImcChecker.cs @@ -6,9 +6,9 @@ namespace Penumbra.Meta; public class ImcChecker { - private static readonly Dictionary VariantCounts = []; - private static MetaFileManager? _dataManager; - + private static readonly Dictionary VariantCounts = []; + private static MetaFileManager? _dataManager; + private static readonly ConcurrentDictionary GlobalCachedDefaultEntries = []; public static int GetVariantCount(ImcIdentifier identifier) { @@ -26,23 +26,20 @@ public class ImcChecker public readonly record struct CachedEntry(ImcEntry Entry, bool FileExists, bool VariantExists); - private readonly Dictionary _cachedDefaultEntries = new(); - private readonly MetaFileManager _metaFileManager; - public ImcChecker(MetaFileManager metaFileManager) - { - _metaFileManager = metaFileManager; - _dataManager = metaFileManager; - } + => _dataManager = metaFileManager; - public CachedEntry GetDefaultEntry(ImcIdentifier identifier, bool storeCache) + public static CachedEntry GetDefaultEntry(ImcIdentifier identifier, bool storeCache) { - if (_cachedDefaultEntries.TryGetValue(identifier, out var entry)) + if (GlobalCachedDefaultEntries.TryGetValue(identifier, out var entry)) return entry; + if (_dataManager == null) + return new CachedEntry(default, false, false); + try { - var e = ImcFile.GetDefault(_metaFileManager, identifier.GamePath(), identifier.EquipSlot, identifier.Variant, out var entryExists); + var e = ImcFile.GetDefault(_dataManager, identifier.GamePath(), identifier.EquipSlot, identifier.Variant, out var entryExists); entry = new CachedEntry(e, true, entryExists); } catch (Exception) @@ -51,7 +48,7 @@ public class ImcChecker } if (storeCache) - _cachedDefaultEntries.Add(identifier, entry); + GlobalCachedDefaultEntries.TryAdd(identifier, entry); return entry; } diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index 07a54391..64c585ea 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -10,8 +10,7 @@ namespace Penumbra.Mods.Editor; public class ModMetaEditor( ModGroupEditor groupEditor, - MetaFileManager metaFileManager, - ImcChecker imcChecker) : MetaDictionary, IService + MetaFileManager metaFileManager) : MetaDictionary, IService { public sealed class OtherOptionData : HashSet { @@ -67,14 +66,14 @@ public class ModMetaEditor( Changes = false; } - public static bool DeleteDefaultValues(MetaFileManager metaFileManager, ImcChecker imcChecker, MetaDictionary dict) + public static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict) { var clone = dict.Clone(); dict.Clear(); var count = 0; foreach (var (key, value) in clone.Imc) { - var defaultEntry = imcChecker.GetDefaultEntry(key, false); + var defaultEntry = ImcChecker.GetDefaultEntry(key, false); if (!defaultEntry.Entry.Equals(value)) { dict.TryAdd(key, value); @@ -164,7 +163,7 @@ public class ModMetaEditor( } public void DeleteDefaultValues() - => Changes = DeleteDefaultValues(metaFileManager, imcChecker, this); + => Changes = DeleteDefaultValues(metaFileManager, this); public void Apply(IModDataContainer container) { diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index f8b4b2ef..2a1854ed 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -35,6 +35,7 @@ public class ImcModGroup(Mod mod) : IModGroup public ImcIdentifier Identifier; public ImcEntry DefaultEntry; public bool AllVariants; + public bool OnlyAttributes; public FullPath? FindBestMatch(Utf8GamePath gamePath) @@ -97,28 +98,36 @@ public class ImcModGroup(Mod mod) : IModGroup public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) => new ImcModGroupEditDrawer(editDrawer, this); - public ImcEntry GetEntry(ushort mask) - => DefaultEntry with { AttributeMask = mask }; + private ImcEntry GetEntry(Variant variant, ushort mask) + { + if (!OnlyAttributes) + return DefaultEntry with { AttributeMask = mask }; + + var defaultEntry = ImcChecker.GetDefaultEntry(Identifier with { Variant = variant }, true); + if (defaultEntry.VariantExists) + return defaultEntry.Entry with { AttributeMask = mask }; + + return DefaultEntry with { AttributeMask = mask }; + } public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) { if (IsDisabled(setting)) return; - var mask = GetCurrentMask(setting); - var entry = GetEntry(mask); + var mask = GetCurrentMask(setting); if (AllVariants) { var count = ImcChecker.GetVariantCount(Identifier); if (count == 0) - manipulations.TryAdd(Identifier, entry); + manipulations.TryAdd(Identifier, GetEntry(Identifier.Variant, mask)); else for (var i = 0; i <= count; ++i) - manipulations.TryAdd(Identifier with { Variant = (Variant)i }, entry); + manipulations.TryAdd(Identifier with { Variant = (Variant)i }, GetEntry((Variant)i, mask)); } else { - manipulations.TryAdd(Identifier, entry); + manipulations.TryAdd(Identifier, GetEntry(Identifier.Variant, mask)); } } @@ -138,6 +147,8 @@ public class ImcModGroup(Mod mod) : IModGroup serializer.Serialize(jWriter, DefaultEntry); jWriter.WritePropertyName(nameof(AllVariants)); jWriter.WriteValue(AllVariants); + jWriter.WritePropertyName(nameof(OnlyAttributes)); + jWriter.WriteValue(OnlyAttributes); jWriter.WritePropertyName("Options"); jWriter.WriteStartArray(); foreach (var option in OptionData) @@ -170,8 +181,9 @@ public class ImcModGroup(Mod mod) : IModGroup var identifier = ImcIdentifier.FromJson(json[nameof(Identifier)] as JObject); var ret = new ImcModGroup(mod) { - DefaultEntry = json[nameof(DefaultEntry)]?.ToObject() ?? new ImcEntry(), - AllVariants = json[nameof(AllVariants)]?.ToObject() ?? false, + DefaultEntry = json[nameof(DefaultEntry)]?.ToObject() ?? new ImcEntry(), + AllVariants = json[nameof(AllVariants)]?.ToObject() ?? false, + OnlyAttributes = json[nameof(OnlyAttributes)]?.ToObject() ?? false, }; if (!ModSaveGroup.ReadJsonBase(json, ret)) return null; diff --git a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs index 515f6ff4..dc94c881 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ImcModGroupEditor.cs @@ -89,6 +89,16 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, group.Mod, group, null, null, -1); } + public void ChangeOnlyAttributes(ImcModGroup group, bool onlyAttributes, SaveType saveType = SaveType.Queue) + { + if (group.OnlyAttributes == onlyAttributes) + return; + + group.OnlyAttributes = onlyAttributes; + SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); + Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, group.Mod, group, null, null, -1); + } + public void ChangeCanBeDisabled(ImcModGroup group, bool canBeDisabled, SaveType saveType = SaveType.Queue) { if (group.CanBeDisabled == canBeDisabled) diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index fe027ca4..1af9c1db 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -25,8 +25,7 @@ public partial class ModCreator( Configuration config, ModDataEditor dataEditor, MetaFileManager metaFileManager, - GamePathParser gamePathParser, - ImcChecker imcChecker) : IService + GamePathParser gamePathParser) : IService { public readonly Configuration Config = config; @@ -86,7 +85,7 @@ public partial class ModCreator( { foreach (var container in mod.AllDataContainers) { - if (ModMetaEditor.DeleteDefaultValues(metaFileManager, imcChecker, container.Manipulations)) + if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations)) saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport)); } } @@ -235,7 +234,7 @@ public partial class ModCreator( DeleteDeleteList(deleteList, delete); var changes = oldSize < option.Manipulations.Count; if (deleteDefault && !Config.KeepDefaultMetaChanges) - changes |= ModMetaEditor.DeleteDefaultValues(metaFileManager, imcChecker, option.Manipulations); + changes |= ModMetaEditor.DeleteDefaultValues(metaFileManager, option.Manipulations); return (changes, deleteList); } diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index 53c61292..c8310cf7 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -30,7 +30,7 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile } private void UpdateEntry() - => (Entry, _fileExists, _) = MetaFiles.ImcChecker.GetDefaultEntry(Identifier, true); + => (Entry, _fileExists, _) = ImcChecker.GetDefaultEntry(Identifier, true); protected override void DrawNew() { @@ -54,7 +54,7 @@ public sealed class ImcMetaDrawer(ModMetaEditor editor, MetaFileManager metaFile DrawMetaButtons(identifier, entry); DrawIdentifier(identifier); - var defaultEntry = MetaFiles.ImcChecker.GetDefaultEntry(identifier, true).Entry; + var defaultEntry = ImcChecker.GetDefaultEntry(identifier, true).Entry; if (DrawEntry(defaultEntry, ref entry, true)) Editor.Changes |= Editor.Update(identifier, entry); } diff --git a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs index 689571f3..c30239bc 100644 --- a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs @@ -24,13 +24,11 @@ public class AddGroupDrawer : IUiService private bool _imcFileExists; private bool _entryExists; private bool _entryInvalid; - private readonly ImcChecker _imcChecker; private readonly ModManager _modManager; - public AddGroupDrawer(ModManager modManager, ImcChecker imcChecker) + public AddGroupDrawer(ModManager modManager) { _modManager = modManager; - _imcChecker = imcChecker; UpdateEntry(); } @@ -142,7 +140,7 @@ public class AddGroupDrawer : IUiService private void UpdateEntry() { - (_defaultEntry, _imcFileExists, _entryExists) = _imcChecker.GetDefaultEntry(_imcIdentifier, false); + (_defaultEntry, _imcFileExists, _entryExists) = ImcChecker.GetDefaultEntry(_imcIdentifier, false); _entryInvalid = !_imcIdentifier.Validate() || _defaultEntry.MaterialId == 0 || !_entryExists; } } diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs index 4ab1c6aa..786bb8ff 100644 --- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -6,6 +6,7 @@ using OtterGui.Text; using OtterGui.Text.Widget; using OtterGuiInternal.Utility; using Penumbra.GameData.Structs; +using Penumbra.Meta; using Penumbra.Mods.Groups; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; @@ -18,18 +19,25 @@ public readonly struct ImcModGroupEditDrawer(ModGroupEditDrawer editor, ImcModGr public void Draw() { var identifier = group.Identifier; - var defaultEntry = editor.ImcChecker.GetDefaultEntry(identifier, true).Entry; + var defaultEntry = ImcChecker.GetDefaultEntry(identifier, true).Entry; var entry = group.DefaultEntry; var changes = false; - var width = editor.AvailableWidth.X - ImUtf8.ItemInnerSpacing.X - ImUtf8.CalcTextSize("All Variants"u8).X; + var width = editor.AvailableWidth.X - 3 * ImUtf8.ItemInnerSpacing.X - ImUtf8.ItemSpacing.X - ImUtf8.CalcTextSize("All Variants"u8).X - ImUtf8.CalcTextSize("Only Attributes"u8).X - 2 * ImUtf8.FrameHeight; ImUtf8.TextFramed(identifier.ToString(), 0, new Vector2(width, 0), borderColor: ImGui.GetColorU32(ImGuiCol.Border)); + ImUtf8.SameLineInner(); var allVariants = group.AllVariants; if (ImUtf8.Checkbox("All Variants"u8, ref allVariants)) editor.ModManager.OptionEditor.ImcEditor.ChangeAllVariants(group, allVariants); ImUtf8.HoverTooltip("Make this group overwrite all corresponding variants for this identifier, not just the one specified."u8); + ImGui.SameLine(); + var onlyAttributes = group.OnlyAttributes; + if (ImUtf8.Checkbox("Only Attributes"u8, ref onlyAttributes)) + editor.ModManager.OptionEditor.ImcEditor.ChangeOnlyAttributes(group, onlyAttributes); + ImUtf8.HoverTooltip("Only overwrite the attribute flags and take all the other values from the game's default entry instead of the one configured here.\n\nMainly useful if used with All Variants to keep the material IDs for each variant."u8); + using (ImUtf8.Group()) { ImUtf8.TextFrameAligned("Material ID"u8); From 9b958a9d37856e060f66643ad306de3ea63b0bf2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 16 Sep 2024 23:16:43 +0200 Subject: [PATCH 36/73] Update actions. --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/test_release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b40b2538..1783c9a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - name: Archive run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: | ./Penumbra/bin/Release/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c9e2909..4799cbed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: - name: Archive run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: | ./Penumbra/bin/Release/* diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index 91361646..0718ded2 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -37,7 +37,7 @@ jobs: - name: Archive run: Compress-Archive -Path Penumbra/bin/Debug/* -DestinationPath Penumbra.zip - name: Upload a Build Artifact - uses: actions/upload-artifact@v2.2.1 + uses: actions/upload-artifact@v4 with: path: | ./Penumbra/bin/Debug/* From af2a14826cdee2472b9ac872e1808d505aae14e2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 19 Sep 2024 22:50:00 +0200 Subject: [PATCH 37/73] Add potential hidden priorities. --- Penumbra/Mods/Settings/ModPriority.cs | 6 ++++++ Penumbra/UI/ModsTab/ModPanelConflictsTab.cs | 5 +++-- Penumbra/UI/ModsTab/ModPanelSettingsTab.cs | 4 ++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Penumbra/Mods/Settings/ModPriority.cs b/Penumbra/Mods/Settings/ModPriority.cs index 993bd577..cf234c00 100644 --- a/Penumbra/Mods/Settings/ModPriority.cs +++ b/Penumbra/Mods/Settings/ModPriority.cs @@ -66,4 +66,10 @@ public readonly record struct ModPriority(int Value) : public int CompareTo(ModPriority other) => Value.CompareTo(other.Value); + + public const int HiddenMin = -84037; + public const int HiddenMax = HiddenMin + 1000; + + public bool IsHidden + => Value is > HiddenMin and < HiddenMax; } diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index bee48068..bc18ac51 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -25,7 +25,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy => "Conflicts"u8; public bool IsVisible - => collectionManager.Active.Current.Conflicts(selector.Selected!).Count > 0; + => collectionManager.Active.Current.Conflicts(selector.Selected!).Any(c => !GetPriority(c).IsHidden); private readonly ConditionalWeakTable _expandedMods = []; @@ -58,7 +58,8 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy // Can not be null because otherwise the tab bar is never drawn. var mod = selector.Selected!; - foreach (var (conflict, index) in collectionManager.Active.Current.Conflicts(mod).OrderByDescending(GetPriority) + foreach (var (conflict, index) in collectionManager.Active.Current.Conflicts(mod).Where(c => !c.Mod2.Priority.IsHidden) + .OrderByDescending(GetPriority) .ThenBy(c => c.Mod2.Name.Lower).WithIndex()) { using var id = ImRaii.PushId(index); diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index d2fbd0cd..8d889c3b 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -2,6 +2,7 @@ using ImGuiNET; using OtterGui.Raii; using OtterGui; using OtterGui.Services; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.UI.Classes; using Penumbra.Collections.Manager; @@ -96,6 +97,9 @@ public class ModPanelSettingsTab( ImGui.SetNextItemWidth(50 * UiHelpers.Scale); if (ImGui.InputInt("##Priority", ref priority, 0, 0)) _currentPriority = priority; + if (new ModPriority(priority).IsHidden) + ImUtf8.HoverTooltip($"This priority is special-cased to hide this mod in conflict tabs ({ModPriority.HiddenMin}, {ModPriority.HiddenMax})."); + if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { From 22aca49112b7f9d6feca07c999507a08ff41935c Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 22 Sep 2024 19:47:34 +0000 Subject: [PATCH 38/73] [CI] Updating repo.json for testing_1.2.1.5 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 6625cb24..36b27682 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.4", + "TestingAssemblyVersion": "1.2.1.5", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.4/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.5/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From caf4382e1f17b8fccd1bfc2f03293fd149596971 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 11:50:49 +0200 Subject: [PATCH 39/73] Update BNPCs --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 66bc00dc..fd50cb3d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 66bc00dc8517204e58c6515af5aec0ba6d196716 +Subproject commit fd50cb3d33e8f59e8b60474c3def914a6952c485 From 776b4e9efbd1835cc0332d7f1ea9b68c4267bf72 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 11:58:00 +0200 Subject: [PATCH 40/73] Update obsolete properties from CS. --- Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs | 4 ++-- .../UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs index 61ccc95c..c459a67a 100644 --- a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs @@ -40,8 +40,8 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase if (_originalColorTableTexture.Texture == null) throw new InvalidOperationException("Material doesn't have a color table"); - Width = (int)_originalColorTableTexture.Texture->Width; - Height = (int)_originalColorTableTexture.Texture->Height; + Width = (int)_originalColorTableTexture.Texture->ActualWidth; + Height = (int)_originalColorTableTexture.Texture->ActualHeight; ColorTable = new Half[Width * Height * 4]; _updatePending = true; diff --git a/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs b/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs index 6ffd1f88..5c636b1d 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs @@ -56,7 +56,7 @@ public sealed unsafe class MaterialTemplatePickers : IUiService } var firstNonNullTexture = firstNonNullTextureRH != null ? firstNonNullTextureRH->CsHandle.Texture : null; - var textureSize = firstNonNullTexture != null ? new Vector2(firstNonNullTexture->Width, firstNonNullTexture->Height).Contain(new Vector2(MaximumTextureSize)) : Vector2.Zero; + var textureSize = firstNonNullTexture != null ? new Vector2(firstNonNullTexture->ActualWidth, firstNonNullTexture->ActualHeight).Contain(new Vector2(MaximumTextureSize)) : Vector2.Zero; var count = firstNonNullTexture != null ? firstNonNullTexture->ArraySize : 0; var ret = false; @@ -135,10 +135,10 @@ public sealed unsafe class MaterialTemplatePickers : IUiService continue; var position = regionStart with { X = regionStart.X + (itemSize.X + itemSpacing) * j }; - var size = new Vector2(texture->Width, texture->Height).Contain(itemSize); + var size = new Vector2(texture->ActualWidth, texture->ActualHeight).Contain(itemSize); position += (itemSize - size) * 0.5f; ImGui.GetWindowDrawList().AddImage(handle, position, position + size, Vector2.Zero, - new Vector2(texture->Width / (float)texture->Width2, texture->Height / (float)texture->Height2)); + new Vector2(texture->ActualWidth / (float)texture->AllocatedWidth, texture->ActualHeight / (float)texture->AllocatedHeight)); } } From 389c42e68f2d65eaa7ec8eff6a1fd38c955faf54 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 13:02:28 +0200 Subject: [PATCH 41/73] Update GameData. --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index fd50cb3d..dd86dafb 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit fd50cb3d33e8f59e8b60474c3def914a6952c485 +Subproject commit dd86dafb88ca4c7b662938bbc1310729ba7f788d From 8084f481446dd601761c0c96bf6baf1d1366fb63 Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:29:39 +1000 Subject: [PATCH 42/73] Init support for DT model i/o --- Penumbra/Import/Models/Export/MeshExporter.cs | 62 +++- Penumbra/Import/Models/Import/MeshImporter.cs | 32 ++- .../Import/Models/Import/VertexAttribute.cs | 265 ++++++++++++++---- 3 files changed, 281 insertions(+), 78 deletions(-) diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 3a57ab55..20158776 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -311,15 +311,28 @@ public class MeshExporter MdlFile.VertexType.Single3 => new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.Single4 => new Vector4(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()), MdlFile.VertexType.UByte4 => reader.ReadBytes(4), - MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, - reader.ReadByte() / 255f), + MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f), MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()), - MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), - (float)reader.ReadHalf()), - + MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf()), + MdlFile.VertexType.UShort4 => ReadUShort4(reader), var other => throw _notifier.Exception($"Unhandled vertex type {other}"), }; } + + private byte[] ReadUShort4(BinaryReader reader) + { + var buffer = reader.ReadBytes(8); + var byteValues = new byte[8]; + byteValues[0] = buffer[0]; + byteValues[4] = buffer[1]; + byteValues[1] = buffer[2]; + byteValues[5] = buffer[3]; + byteValues[2] = buffer[4]; + byteValues[6] = buffer[5]; + byteValues[3] = buffer[6]; + byteValues[7] = buffer[7]; + return byteValues; + } /// Get the vertex geometry type for this mesh's vertex usages. private Type GetGeometryType(IReadOnlyDictionary usages) @@ -444,7 +457,16 @@ public class MeshExporter private static Type GetSkinningType(IReadOnlyDictionary usages) { if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices)) - return typeof(VertexJoints4); + { + if (usages[MdlFile.VertexUsage.BlendWeights] == MdlFile.VertexType.UShort4) + { + return typeof(VertexJoints8); + } + else + { + return typeof(VertexJoints4); + } + } return typeof(VertexEmpty); } @@ -455,15 +477,17 @@ public class MeshExporter if (_skinningType == typeof(VertexEmpty)) return new VertexEmpty(); - if (_skinningType == typeof(VertexJoints4)) + if (_skinningType == typeof(VertexJoints4) || _skinningType == typeof(VertexJoints8)) { if (_boneIndexMap == null) throw _notifier.Exception("Tried to build skinned vertex but no bone mappings are available."); - var indices = ToByteArray(attributes[MdlFile.VertexUsage.BlendIndices]); - var weights = ToVector4(attributes[MdlFile.VertexUsage.BlendWeights]); - - var bindings = Enumerable.Range(0, 4) + var indiciesData = attributes[MdlFile.VertexUsage.BlendIndices]; + var weightsData = attributes[MdlFile.VertexUsage.BlendWeights]; + var indices = ToByteArray(indiciesData); + var weights = ToFloatArray(weightsData); + + var bindings = Enumerable.Range(0, indices.Length) .Select(bindingIndex => { // NOTE: I've not seen any files that throw this error that aren't completely broken. @@ -474,7 +498,13 @@ public class MeshExporter return (jointIndex, weights[bindingIndex]); }) .ToArray(); - return new VertexJoints4(bindings); + + return bindings.Length switch + { + 4 => new VertexJoints4(bindings), + 8 => new VertexJoints8(bindings), + _ => throw _notifier.Exception($"Invalid number of bone bindings {bindings.Length}.") + }; } throw _notifier.Exception($"Unknown skinning type {_skinningType}"); @@ -517,4 +547,12 @@ public class MeshExporter byte[] value => value, _ => throw new ArgumentOutOfRangeException($"Invalid byte[] input {data}"), }; + + private static float[] ToFloatArray(object data) + => data switch + { + byte[] value => value.Select(x => x / 255f).ToArray(), + _ => throw new ArgumentOutOfRangeException($"Invalid float[] input {data}"), + }; } + diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index 1df97907..e3567780 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -194,17 +194,37 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) foreach (var (primitive, primitiveIndex) in node.Mesh.Primitives.WithIndex()) { // Per glTF specification, an asset with a skin MUST contain skinning attributes on its meshes. - var jointsAccessor = primitive.GetVertexAccessor("JOINTS_0")?.AsVector4Array(); - var weightsAccessor = primitive.GetVertexAccessor("WEIGHTS_0")?.AsVector4Array(); + var joints0Accessor = primitive.GetVertexAccessor("JOINTS_0")?.AsVector4Array(); + var weights0Accessor = primitive.GetVertexAccessor("WEIGHTS_0")?.AsVector4Array(); - if (jointsAccessor == null || weightsAccessor == null) + if (joints0Accessor == null || weights0Accessor == null) throw notifier.Exception($"Primitive {primitiveIndex} is skinned but does not contain skinning vertex attributes."); // Build a set of joints that are referenced by this mesh. - for (var i = 0; i < jointsAccessor.Count; i++) + for (var i = 0; i < joints0Accessor.Count; i++) { - var joints = jointsAccessor[i]; - var weights = weightsAccessor[i]; + var joints = joints0Accessor[i]; + var weights = weights0Accessor[i]; + for (var index = 0; index < 4; index++) + { + // If a joint has absolutely no weight, we omit the bone entirely. + if (weights[index] == 0) + continue; + + usedJoints.Add((ushort)joints[index]); + } + } + + var joints1Accessor = primitive.GetVertexAccessor("JOINTS_1")?.AsVector4Array(); + var weights1Accessor = primitive.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array(); + + if (joints1Accessor == null || weights1Accessor == null) + continue; + + for (var i = 0; i < joints1Accessor.Count; i++) + { + var joints = joints1Accessor[i]; + var weights = weights1Accessor[i]; for (var index = 0; index < 4; index++) { // If a joint has absolutely no weight, we omit the bone entirely. diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index af401ec1..b71ad429 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -40,6 +40,7 @@ public class VertexAttribute MdlFile.VertexType.NByte4 => 4, MdlFile.VertexType.Half2 => 4, MdlFile.VertexType.Half4 => 8, + MdlFile.VertexType.UShort4 => 8, _ => throw new Exception($"Unhandled vertex type {(MdlFile.VertexType)Element.Type}"), }; @@ -121,89 +122,219 @@ public class VertexAttribute public static VertexAttribute? BlendWeight(Accessors accessors, IoNotifier notifier) { - if (!accessors.TryGetValue("WEIGHTS_0", out var accessor)) + if (!accessors.TryGetValue("WEIGHTS_0", out var weights0Accessor)) return null; if (!accessors.ContainsKey("JOINTS_0")) throw notifier.Exception("Mesh contained WEIGHTS_0 attribute but no corresponding JOINTS_0 attribute."); - var element = new MdlStructs.VertexElement() + if (accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor)) { - Stream = 0, - Type = (byte)MdlFile.VertexType.NByte4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; + if (!accessors.ContainsKey("JOINTS_1")) + throw notifier.Exception("Mesh contained WEIGHTS_1 attribute but no corresponding JOINTS_1 attribute."); + + var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; - var values = accessor.AsVector4Array(); + var weights0 = weights0Accessor.AsVector4Array(); + var weights1 = weights1Accessor.AsVector4Array(); - return new VertexAttribute( - element, - index => { - // Blend weights are _very_ sensitive to float imprecision - a vertex sum being off - // by one, such as 256, is enough to cause a visible defect. To avoid this, we tweak - // the converted values to have the expected sum, preferencing values with minimal differences. - var originalValues = values[index]; - var byteValues = BuildNByte4(originalValues); - - var adjustment = 255 - byteValues.Select(value => (int)value).Sum(); - while (adjustment != 0) - { - var convertedValues = byteValues.Select(value => value * (1f / 255f)).ToArray(); - var closestIndex = Enumerable.Range(0, 4) - .Where(index => { - var byteValue = byteValues[index]; - if (adjustment < 0) return byteValue > 0; - if (adjustment > 0) return byteValue < 255; - return true; - }) - .Select(index => (index, delta: Math.Abs(originalValues[index] - convertedValues[index]))) - .MinBy(x => x.delta) - .index; - byteValues[closestIndex] = (byte)(byteValues[closestIndex] + Math.CopySign(1, adjustment)); - adjustment = 255 - byteValues.Select(value => (int)value).Sum(); + return new VertexAttribute( + element, + index => { + var weight0 = weights0[index]; + var weight1 = weights1[index]; + var originalData = BuildUshort4(weight0, weight1); + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + return AdjustByteArray(byteValues, originalData); } - - return byteValues; - } - ); + ); + } + else + { + var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + + return new VertexAttribute( + element, + index => { + var weight0 = weights0[index]; + var weight1 = Vector4.Zero; + var originalData = BuildUshort4(weight0, weight1); + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + return AdjustByteArray(byteValues, originalData); + } + ); + + /*var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.NByte4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + + return new VertexAttribute( + element, + index => + { + var weight0 = weights0[index]; + var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + var newByteValues = AdjustByteArray(byteValues, originalData); + if (!newByteValues.SequenceEqual(byteValues)) + notifier.Warning("Adjusted blend weights to maintain precision."); + return newByteValues; + });*/ + } + } + + private static byte[] AdjustByteArray(byte[] byteValues, float[] originalValues) + { + // Blend weights are _very_ sensitive to float imprecision - a vertex sum being off + // by one, such as 256, is enough to cause a visible defect. To avoid this, we tweak + // the converted values to have the expected sum, preferencing values with minimal differences. + var adjustment = 255 - byteValues.Select(value => (int)value).Sum(); + while (adjustment != 0) + { + var convertedValues = byteValues.Select(value => value * (1f / 255f)).ToArray(); + var closestIndex = Enumerable.Range(0, byteValues.Length) + .Where(index => + { + var byteValue = byteValues[index]; + if (adjustment < 0) + return byteValue > 0; + if (adjustment > 0) + return byteValue < 255; + + return true; + }) + .Select(index => (index, delta: Math.Abs(originalValues[index] - convertedValues[index]))) + .MinBy(x => x.delta) + .index; + byteValues[closestIndex] = (byte)(byteValues[closestIndex] + Math.CopySign(1, adjustment)); + adjustment = 255 - byteValues.Select(value => (int)value).Sum(); + } + + return byteValues; } public static VertexAttribute? BlendIndex(Accessors accessors, IDictionary? boneMap, IoNotifier notifier) { - if (!accessors.TryGetValue("JOINTS_0", out var jointsAccessor)) + if (!accessors.TryGetValue("JOINTS_0", out var joints0Accessor)) return null; - if (!accessors.TryGetValue("WEIGHTS_0", out var weightsAccessor)) + if (!accessors.TryGetValue("WEIGHTS_0", out var weights0Accessor)) throw notifier.Exception("Mesh contained JOINTS_0 attribute but no corresponding WEIGHTS_0 attribute."); if (boneMap == null) throw notifier.Exception("Mesh contained JOINTS_0 attribute but no bone mapping was created."); - var element = new MdlStructs.VertexElement() + var joints0 = joints0Accessor.AsVector4Array(); + var weights0 = weights0Accessor.AsVector4Array(); + + if (accessors.TryGetValue("JOINTS_1", out var joints1Accessor)) { - Stream = 0, - Type = (byte)MdlFile.VertexType.UByte4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; + if (!accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor)) + throw notifier.Exception("Mesh contained JOINTS_1 attribute but no corresponding WEIGHTS_1 attribute."); - var joints = jointsAccessor.AsVector4Array(); - var weights = weightsAccessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => + var element = new MdlStructs.VertexElement { - var gltfIndices = joints[index]; - var gltfWeights = weights[index]; + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; - return BuildUByte4(new Vector4( - gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], - gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], - gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], - gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] - )); - } - ); + var joints1 = joints1Accessor.AsVector4Array(); + var weights1 = weights1Accessor.AsVector4Array(); + + return new VertexAttribute( + element, + index => + { + var gltfIndices0 = joints0[index]; + var gltfWeights0 = weights0[index]; + var gltfIndices1 = joints1[index]; + var gltfWeights1 = weights1[index]; + var v0 = new Vector4( + gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], + gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], + gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], + gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + ); + var v1 = new Vector4( + gltfWeights1.X == 0 ? 0 : boneMap[(ushort)gltfIndices1.X], + gltfWeights1.Y == 0 ? 0 : boneMap[(ushort)gltfIndices1.Y], + gltfWeights1.Z == 0 ? 0 : boneMap[(ushort)gltfIndices1.Z], + gltfWeights1.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.W] + ); + + var byteValues = BuildUshort4(v0, v1); + + return byteValues.Select(x => (byte)x).ToArray(); + } + ); + } + else + { + var element = new MdlStructs.VertexElement + { + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; + + return new VertexAttribute( + element, + index => + { + var gltfIndices0 = joints0[index]; + var gltfWeights0 = weights0[index]; + var v0 = new Vector4( + gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], + gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], + gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], + gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + ); + var v1 = Vector4.Zero; + var byteValues = BuildUshort4(v0, v1); + + return byteValues.Select(x => (byte)x).ToArray(); + } + ); + /*var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.UByte4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; + + return new VertexAttribute( + element, + index => + { + var gltfIndices = joints0[index]; + var gltfWeights = weights0[index]; + return BuildUByte4(new Vector4( + gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], + gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], + gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], + gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] + )); + } + );*/ + } } public static VertexAttribute? Normal(Accessors accessors, IEnumerable morphAccessors) @@ -232,7 +363,7 @@ public class VertexAttribute var value = values[vertexIndex]; var delta = morphValues[morphIndex]?[vertexIndex]; - if (delta != null) + if (delta != null) value += delta.Value; return BuildSingle3(value); @@ -489,4 +620,18 @@ public class VertexAttribute (byte)Math.Round(input.Z * 255f), (byte)Math.Round(input.W * 255f), ]; + + private static float[] BuildUshort4(Vector4 v0, Vector4 v1) + { + var buf = new float[8]; + buf[0] = v0.X; + buf[4] = v1.X; + buf[1] = v0.Y; + buf[5] = v1.Y; + buf[2] = v0.Z; + buf[6] = v1.Z; + buf[3] = v0.W; + buf[7] = v1.W; + return buf; + } } From fecdee05bda2de924b4b46aebf7c6f7d9ff3ccdd Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:35:35 +1000 Subject: [PATCH 43/73] Cleanup --- Penumbra/Import/Models/Export/MeshExporter.cs | 17 +- Penumbra/Import/Models/Import/MeshImporter.cs | 31 +- .../Import/Models/Import/VertexAttribute.cs | 272 +++++++----------- 3 files changed, 121 insertions(+), 199 deletions(-) diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 20158776..3707ff79 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -314,25 +314,10 @@ public class MeshExporter MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f), MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()), MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf()), - MdlFile.VertexType.UShort4 => ReadUShort4(reader), + MdlFile.VertexType.UShort4 => reader.ReadBytes(8), var other => throw _notifier.Exception($"Unhandled vertex type {other}"), }; } - - private byte[] ReadUShort4(BinaryReader reader) - { - var buffer = reader.ReadBytes(8); - var byteValues = new byte[8]; - byteValues[0] = buffer[0]; - byteValues[4] = buffer[1]; - byteValues[1] = buffer[2]; - byteValues[5] = buffer[3]; - byteValues[2] = buffer[4]; - byteValues[6] = buffer[5]; - byteValues[3] = buffer[6]; - byteValues[7] = buffer[7]; - return byteValues; - } /// Get the vertex geometry type for this mesh's vertex usages. private Type GetGeometryType(IReadOnlyDictionary usages) diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index e3567780..813ef422 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -196,7 +196,9 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) // Per glTF specification, an asset with a skin MUST contain skinning attributes on its meshes. var joints0Accessor = primitive.GetVertexAccessor("JOINTS_0")?.AsVector4Array(); var weights0Accessor = primitive.GetVertexAccessor("WEIGHTS_0")?.AsVector4Array(); - + var joints1Accessor = primitive.GetVertexAccessor("JOINTS_1")?.AsVector4Array(); + var weights1Accessor = primitive.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array(); + if (joints0Accessor == null || weights0Accessor == null) throw notifier.Exception($"Primitive {primitiveIndex} is skinned but does not contain skinning vertex attributes."); @@ -205,6 +207,8 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) { var joints = joints0Accessor[i]; var weights = weights0Accessor[i]; + var joints1 = joints1Accessor?[i]; + var weights1 = weights1Accessor?[i]; for (var index = 0; index < 4; index++) { // If a joint has absolutely no weight, we omit the bone entirely. @@ -212,26 +216,11 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) continue; usedJoints.Add((ushort)joints[index]); - } - } - - var joints1Accessor = primitive.GetVertexAccessor("JOINTS_1")?.AsVector4Array(); - var weights1Accessor = primitive.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array(); - - if (joints1Accessor == null || weights1Accessor == null) - continue; - - for (var i = 0; i < joints1Accessor.Count; i++) - { - var joints = joints1Accessor[i]; - var weights = weights1Accessor[i]; - for (var index = 0; index < 4; index++) - { - // If a joint has absolutely no weight, we omit the bone entirely. - if (weights[index] == 0) - continue; - - usedJoints.Add((ushort)joints[index]); + + if (joints1 != null && weights1 != null && weights1.Value[index] != 0) + { + usedJoints.Add((ushort)joints1.Value[index]); + } } } } diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index b71ad429..12ceba23 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -132,72 +132,50 @@ public class VertexAttribute { if (!accessors.ContainsKey("JOINTS_1")) throw notifier.Exception("Mesh contained WEIGHTS_1 attribute but no corresponding JOINTS_1 attribute."); - - var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - var weights1 = weights1Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => { - var weight0 = weights0[index]; - var weight1 = weights1[index]; - var originalData = BuildUshort4(weight0, weight1); - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - return AdjustByteArray(byteValues, originalData); - } - ); } - else + + var element = new MdlStructs.VertexElement() { - var element = new MdlStructs.VertexElement() + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + var weights1 = weights1Accessor?.AsVector4Array(); + + return new VertexAttribute( + element, + index => { + var weight0 = weights0[index]; + var weight1 = weights1?[index]; + var originalData = BuildUshort4(weight0, weight1 ?? Vector4.Zero); + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + return AdjustByteArray(byteValues, originalData); + } + ); + + /*var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.NByte4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => { - var weight0 = weights0[index]; - var weight1 = Vector4.Zero; - var originalData = BuildUshort4(weight0, weight1); - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - return AdjustByteArray(byteValues, originalData); - } - ); - - /*var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.NByte4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => - { - var weight0 = weights0[index]; - var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - var newByteValues = AdjustByteArray(byteValues, originalData); - if (!newByteValues.SequenceEqual(byteValues)) - notifier.Warning("Adjusted blend weights to maintain precision."); - return newByteValues; - });*/ - } + var weight0 = weights0[index]; + var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + var newByteValues = AdjustByteArray(byteValues, originalData); + if (!newByteValues.SequenceEqual(byteValues)) + notifier.Warning("Adjusted blend weights to maintain precision."); + return newByteValues; + });*/ } private static byte[] AdjustByteArray(byte[] byteValues, float[] originalValues) @@ -241,100 +219,77 @@ public class VertexAttribute if (boneMap == null) throw notifier.Exception("Mesh contained JOINTS_0 attribute but no bone mapping was created."); - var joints0 = joints0Accessor.AsVector4Array(); - var weights0 = weights0Accessor.AsVector4Array(); - - if (accessors.TryGetValue("JOINTS_1", out var joints1Accessor)) + var joints0 = joints0Accessor.AsVector4Array(); + var weights0 = weights0Accessor.AsVector4Array(); + accessors.TryGetValue("JOINTS_1", out var joints1Accessor); + accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor); + var element = new MdlStructs.VertexElement { - if (!accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor)) - throw notifier.Exception("Mesh contained JOINTS_1 attribute but no corresponding WEIGHTS_1 attribute."); + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; - var element = new MdlStructs.VertexElement + var joints1 = joints1Accessor?.AsVector4Array(); + var weights1 = weights1Accessor?.AsVector4Array(); + + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - var joints1 = joints1Accessor.AsVector4Array(); - var weights1 = weights1Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => + var gltfIndices0 = joints0[index]; + var gltfWeights0 = weights0[index]; + var gltfIndices1 = joints1?[index]; + var gltfWeights1 = weights1?[index]; + var v0 = new Vector4( + gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], + gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], + gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], + gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + ); + + Vector4 v1; + if (gltfIndices1 != null && gltfWeights1 != null) { - var gltfIndices0 = joints0[index]; - var gltfWeights0 = weights0[index]; - var gltfIndices1 = joints1[index]; - var gltfWeights1 = weights1[index]; - var v0 = new Vector4( - gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], - gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], - gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], - gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + v1 = new Vector4( + gltfWeights1.Value.X == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.X], + gltfWeights1.Value.Y == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.Y], + gltfWeights1.Value.Z == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.Z], + gltfWeights1.Value.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.W] ); - var v1 = new Vector4( - gltfWeights1.X == 0 ? 0 : boneMap[(ushort)gltfIndices1.X], - gltfWeights1.Y == 0 ? 0 : boneMap[(ushort)gltfIndices1.Y], - gltfWeights1.Z == 0 ? 0 : boneMap[(ushort)gltfIndices1.Z], - gltfWeights1.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.W] - ); - - var byteValues = BuildUshort4(v0, v1); - - return byteValues.Select(x => (byte)x).ToArray(); } - ); - } - else + else + { + v1 = Vector4.Zero; + } + + var byteValues = BuildUshort4(v0, v1); + + return byteValues.Select(x => (byte)x).ToArray(); + } + ); + + /*var element = new MdlStructs.VertexElement() { - var element = new MdlStructs.VertexElement - { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - return new VertexAttribute( - element, - index => - { - var gltfIndices0 = joints0[index]; - var gltfWeights0 = weights0[index]; - var v0 = new Vector4( - gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], - gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], - gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], - gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] - ); - var v1 = Vector4.Zero; - var byteValues = BuildUshort4(v0, v1); + Stream = 0, + Type = (byte)MdlFile.VertexType.UByte4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; - return byteValues.Select(x => (byte)x).ToArray(); - } - ); - /*var element = new MdlStructs.VertexElement() + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UByte4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - return new VertexAttribute( - element, - index => - { - var gltfIndices = joints0[index]; - var gltfWeights = weights0[index]; - return BuildUByte4(new Vector4( - gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], - gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], - gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], - gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] - )); - } - );*/ - } + var gltfIndices = joints0[index]; + var gltfWeights = weights0[index]; + return BuildUByte4(new Vector4( + gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], + gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], + gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], + gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] + )); + } + );*/ } public static VertexAttribute? Normal(Accessors accessors, IEnumerable morphAccessors) @@ -620,18 +575,11 @@ public class VertexAttribute (byte)Math.Round(input.Z * 255f), (byte)Math.Round(input.W * 255f), ]; - - private static float[] BuildUshort4(Vector4 v0, Vector4 v1) - { - var buf = new float[8]; - buf[0] = v0.X; - buf[4] = v1.X; - buf[1] = v0.Y; - buf[5] = v1.Y; - buf[2] = v0.Z; - buf[6] = v1.Z; - buf[3] = v0.W; - buf[7] = v1.W; - return buf; - } + + private static float[] BuildUshort4(Vector4 v0, Vector4 v1) => + new[] + { + v0.X, v0.Y, v0.Z, v0.W, + v1.X, v1.Y, v1.Z, v1.W, + }; } From 9de6b3a9055bcd9e80a8c6d694bfbd22d5a37155 Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Tue, 23 Jul 2024 21:41:58 +1000 Subject: [PATCH 44/73] Vector4 to float array --- Penumbra/Import/Models/Export/MeshExporter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 3707ff79..73160615 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -537,6 +537,7 @@ public class MeshExporter => data switch { byte[] value => value.Select(x => x / 255f).ToArray(), + Vector4 v4 => new[] { v4.X, v4.Y, v4.Z, v4.W }, _ => throw new ArgumentOutOfRangeException($"Invalid float[] input {data}"), }; } From 9c6498e0282aa6626edc243abe4d364e1964e4c2 Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:40:47 +1000 Subject: [PATCH 45/73] Conditionally still check for weights1 even if weights0 is 0 --- Penumbra/Import/Models/Import/MeshImporter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index 813ef422..6a46fb9f 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -205,17 +205,18 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) // Build a set of joints that are referenced by this mesh. for (var i = 0; i < joints0Accessor.Count; i++) { - var joints = joints0Accessor[i]; - var weights = weights0Accessor[i]; + var joints0 = joints0Accessor[i]; + var weights0 = weights0Accessor[i]; var joints1 = joints1Accessor?[i]; var weights1 = weights1Accessor?[i]; for (var index = 0; index < 4; index++) { // If a joint has absolutely no weight, we omit the bone entirely. - if (weights[index] == 0) - continue; + if (weights0[index] != 0) + { + usedJoints.Add((ushort)joints0[index]); + } - usedJoints.Add((ushort)joints[index]); if (joints1 != null && weights1 != null && weights1.Value[index] != 0) { From 3e90524b0613fca177414b011ce00b59ac64039c Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:40:59 +1000 Subject: [PATCH 46/73] Remove old impl comments --- .../Import/Models/Import/VertexAttribute.cs | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index 12ceba23..743ee773 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -154,28 +154,6 @@ public class VertexAttribute return AdjustByteArray(byteValues, originalData); } ); - - /*var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.NByte4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => - { - var weight0 = weights0[index]; - var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - var newByteValues = AdjustByteArray(byteValues, originalData); - if (!newByteValues.SequenceEqual(byteValues)) - notifier.Warning("Adjusted blend weights to maintain precision."); - return newByteValues; - });*/ } private static byte[] AdjustByteArray(byte[] byteValues, float[] originalValues) @@ -268,28 +246,6 @@ public class VertexAttribute return byteValues.Select(x => (byte)x).ToArray(); } ); - - /*var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.UByte4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - return new VertexAttribute( - element, - index => - { - var gltfIndices = joints0[index]; - var gltfWeights = weights0[index]; - return BuildUByte4(new Vector4( - gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], - gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], - gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], - gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] - )); - } - );*/ } public static VertexAttribute? Normal(Accessors accessors, IEnumerable morphAccessors) From 5258c600b7f458aa37d60654378eb58b22372c5c Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:35:11 +1000 Subject: [PATCH 47/73] Rework AdjustByteArray --- .../Import/Models/Import/VertexAttribute.cs | 48 ++++++++----------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index 743ee773..14a830d4 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -146,43 +146,39 @@ public class VertexAttribute return new VertexAttribute( element, - index => { - var weight0 = weights0[index]; - var weight1 = weights1?[index]; - var originalData = BuildUshort4(weight0, weight1 ?? Vector4.Zero); - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - return AdjustByteArray(byteValues, originalData); - } + index => BuildBlendWeights(weights0[index], weights1?[index] ?? Vector4.Zero) ); } - - private static byte[] AdjustByteArray(byte[] byteValues, float[] originalValues) + + private static byte[] BuildBlendWeights(Vector4 v1, Vector4 v2) { + var originalData = BuildUshort4(v1, v2); + var byteValues = new byte[originalData.Length]; + for (var i = 0; i < originalData.Length; i++) + { + byteValues[i] = (byte)Math.Round(originalData[i] * 255f); + } + // Blend weights are _very_ sensitive to float imprecision - a vertex sum being off // by one, such as 256, is enough to cause a visible defect. To avoid this, we tweak // the converted values to have the expected sum, preferencing values with minimal differences. - var adjustment = 255 - byteValues.Select(value => (int)value).Sum(); + var adjustment = 255 - byteValues.Sum(value => value); while (adjustment != 0) { - var convertedValues = byteValues.Select(value => value * (1f / 255f)).ToArray(); var closestIndex = Enumerable.Range(0, byteValues.Length) - .Where(index => + .Where(i => adjustment switch { - var byteValue = byteValues[index]; - if (adjustment < 0) - return byteValue > 0; - if (adjustment > 0) - return byteValue < 255; - - return true; + < 0 when byteValues[i] > 0 => true, + > 0 when byteValues[i] < 255 => true, + _ => true, }) - .Select(index => (index, delta: Math.Abs(originalValues[index] - convertedValues[index]))) + .Select(index => (index, delta: Math.Abs(originalData[index] - (byteValues[index] * (1f / 255f))))) .MinBy(x => x.delta) .index; - byteValues[closestIndex] = (byte)(byteValues[closestIndex] + Math.CopySign(1, adjustment)); - adjustment = 255 - byteValues.Select(value => (int)value).Sum(); + byteValues[closestIndex] += (byte)Math.CopySign(1, adjustment); + adjustment = 255 - byteValues.Sum(value => value); } - + return byteValues; } @@ -226,7 +222,7 @@ public class VertexAttribute gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] ); - Vector4 v1; + var v1 = Vector4.Zero; if (gltfIndices1 != null && gltfWeights1 != null) { v1 = new Vector4( @@ -236,10 +232,6 @@ public class VertexAttribute gltfWeights1.Value.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.W] ); } - else - { - v1 = Vector4.Zero; - } var byteValues = BuildUshort4(v0, v1); From 4719f413b6b7c101239490223f90c5e32a5f5de6 Mon Sep 17 00:00:00 2001 From: Passive <20432486+PassiveModding@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:42:21 +1000 Subject: [PATCH 48/73] Fix adjustment switch --- Penumbra/Import/Models/Import/VertexAttribute.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index 14a830d4..a1c3246b 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -168,9 +168,9 @@ public class VertexAttribute var closestIndex = Enumerable.Range(0, byteValues.Length) .Where(i => adjustment switch { - < 0 when byteValues[i] > 0 => true, - > 0 when byteValues[i] < 255 => true, - _ => true, + < 0 => byteValues[i] > 0, + > 0 => byteValues[i] < 255, + _ => true, }) .Select(index => (index, delta: Math.Abs(originalData[index] - (byteValues[index] * (1f / 255f))))) .MinBy(x => x.delta) From 8fa0875ec6bf37e2d48ddde571c77c724c528ccc Mon Sep 17 00:00:00 2001 From: ackwell Date: Sun, 1 Sep 2024 21:32:31 +1000 Subject: [PATCH 49/73] Fix character*.shpk exports --- .../Import/Models/Export/MaterialExporter.cs | 188 ++++++++++-------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 62892473..0f98e5c4 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -36,12 +36,13 @@ public class MaterialExporter return material.Mtrl.ShaderPackage.Name switch { // NOTE: this isn't particularly precise to game behavior (it has some fade around high opacity), but good enough for now. - "character.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.MASK, 0.5f), - "characterglass.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.BLEND), - "hair.shpk" => BuildHair(material, name), - "iris.shpk" => BuildIris(material, name), - "skin.shpk" => BuildSkin(material, name), - _ => BuildFallback(material, name, notifier), + "character.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.MASK, 0.5f), + "characterlegacy.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.MASK, 0.5f), + "characterglass.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.BLEND), + "hair.shpk" => BuildHair(material, name), + "iris.shpk" => BuildIris(material, name), + "skin.shpk" => BuildSkin(material, name), + _ => BuildFallback(material, name, notifier), }; } @@ -49,70 +50,65 @@ public class MaterialExporter private static MaterialBuilder BuildCharacter(Material material, string name) { // Build the textures from the color table. - var table = new LegacyColorTable(material.Mtrl.Table!); + var table = new ColorTable(material.Mtrl.Table!); + var indexTexture = material.Textures[(TextureUsage)1449103320]; + var indexOperation = new ProcessCharacterIndexOperation(indexTexture, table); + ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, indexTexture.Bounds, in indexOperation); - var normal = material.Textures[TextureUsage.SamplerNormal]; + var normalTexture = material.Textures[TextureUsage.SamplerNormal]; + var normalOperation = new ProcessCharacterNormalOperation(normalTexture); + ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, normalTexture.Bounds, in normalOperation); - var operation = new ProcessCharacterNormalOperation(normal, table); - ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, normal.Bounds, in operation); + // Merge in opacity from the normal. + var baseColor = indexOperation.BaseColor; + MultiplyOperation.Execute(baseColor, normalOperation.BaseColorOpacity); - // Check if full textures are provided, and merge in if available. - var baseColor = operation.BaseColor; + // Check if a full diffuse is provided, and merge in if available. if (material.Textures.TryGetValue(TextureUsage.SamplerDiffuse, out var diffuse)) { - MultiplyOperation.Execute(diffuse, operation.BaseColor); + MultiplyOperation.Execute(diffuse, indexOperation.BaseColor); baseColor = diffuse; } - Image specular = operation.Specular; + var specular = indexOperation.Specular; if (material.Textures.TryGetValue(TextureUsage.SamplerSpecular, out var specularTexture)) { - MultiplyOperation.Execute(specularTexture, operation.Specular); + MultiplyOperation.Execute(specularTexture, indexOperation.Specular); specular = specularTexture; } // Pull further information from the mask. if (material.Textures.TryGetValue(TextureUsage.SamplerMask, out var maskTexture)) { - // Extract the red channel for "ambient occlusion". - maskTexture.Mutate(context => context.Resize(baseColor.Width, baseColor.Height)); - maskTexture.ProcessPixelRows(baseColor, (maskAccessor, baseColorAccessor) => - { - for (var y = 0; y < maskAccessor.Height; y++) - { - var maskSpan = maskAccessor.GetRowSpan(y); - var baseColorSpan = baseColorAccessor.GetRowSpan(y); + var maskOperation = new ProcessCharacterMaskOperation(maskTexture); + ParallelRowIterator.IterateRows(ImageSharpConfiguration.Default, maskTexture.Bounds, in maskOperation); - for (var x = 0; x < maskSpan.Length; x++) - baseColorSpan[x].FromVector4(baseColorSpan[x].ToVector4() * new Vector4(maskSpan[x].R / 255f)); - } - }); - // TODO: handle other textures stored in the mask? + // TODO: consider using the occusion gltf material property. + MultiplyOperation.Execute(baseColor, maskOperation.Occlusion); + + // Similar to base color's alpha, this is a pretty wasteful operation for a single channel. + MultiplyOperation.Execute(specular, maskOperation.SpecularFactor); } // Specular extension puts colour on RGB and factor on A. We're already packing like that, so we can reuse the texture. var specularImage = BuildImage(specular, name, "specular"); return BuildSharedBase(material, name) - .WithBaseColor(BuildImage(baseColor, name, "basecolor")) - .WithNormal(BuildImage(operation.Normal, name, "normal")) - .WithEmissive(BuildImage(operation.Emissive, name, "emissive"), Vector3.One, 1) + .WithBaseColor(BuildImage(baseColor, name, "basecolor")) + .WithNormal(BuildImage(normalOperation.Normal, name, "normal")) + .WithEmissive(BuildImage(indexOperation.Emissive, name, "emissive"), Vector3.One, 1) .WithSpecularFactor(specularImage, 1) .WithSpecularColor(specularImage); } - // TODO: It feels a little silly to request the entire normal here when extracting the normal only needs some of the components. - // As a future refactor, it would be neat to accept a single-channel field here, and then do composition of other stuff later. - // TODO(Dawntrail): Use the dedicated index (_id) map, that is not embedded in the normal map's alpha channel anymore. - private readonly struct ProcessCharacterNormalOperation(Image normal, LegacyColorTable table) : IRowOperation + private readonly struct ProcessCharacterIndexOperation(Image index, ColorTable table) : IRowOperation { - public Image Normal { get; } = normal.Clone(); - public Image BaseColor { get; } = new(normal.Width, normal.Height); - public Image Specular { get; } = new(normal.Width, normal.Height); - public Image Emissive { get; } = new(normal.Width, normal.Height); + public Image BaseColor { get; } = new(index.Width, index.Height); + public Image Specular { get; } = new(index.Width, index.Height); + public Image Emissive { get; } = new(index.Width, index.Height); - private Buffer2D NormalBuffer - => Normal.Frames.RootFrame.PixelBuffer; + private Buffer2D IndexBuffer + => index.Frames.RootFrame.PixelBuffer; private Buffer2D BaseColorBuffer => BaseColor.Frames.RootFrame.PixelBuffer; @@ -125,66 +121,96 @@ public class MaterialExporter public void Invoke(int y) { - var normalSpan = NormalBuffer.DangerousGetRowSpan(y); + var indexSpan = IndexBuffer.DangerousGetRowSpan(y); var baseColorSpan = BaseColorBuffer.DangerousGetRowSpan(y); var specularSpan = SpecularBuffer.DangerousGetRowSpan(y); var emissiveSpan = EmissiveBuffer.DangerousGetRowSpan(y); + for (var x = 0; x < indexSpan.Length; x++) + { + ref var indexPixel = ref indexSpan[x]; + + // Calculate and fetch the color table rows being used for this pixel. + var tablePair = (int) Math.Round(indexPixel.R / 17f); + var rowBlend = 1.0f - indexPixel.G / 255f; + + var prevRow = table[tablePair * 2]; + var nextRow = table[Math.Min(tablePair * 2 + 1, ColorTable.NumRows)]; + + // Lerp between table row values to fetch final pixel values for each subtexture. + var lerpedDiffuse = Vector3.Lerp((Vector3)prevRow.DiffuseColor, (Vector3)nextRow.DiffuseColor, rowBlend); + baseColorSpan[x].FromVector4(new Vector4(lerpedDiffuse, 1)); + + var lerpedSpecularColor = Vector3.Lerp((Vector3)prevRow.SpecularColor, (Vector3)nextRow.SpecularColor, rowBlend); + specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, 1)); + + var lerpedEmissive = Vector3.Lerp((Vector3)prevRow.EmissiveColor, (Vector3)nextRow.EmissiveColor, rowBlend); + emissiveSpan[x].FromVector4(new Vector4(lerpedEmissive, 1)); + } + } + } + + private readonly struct ProcessCharacterNormalOperation(Image normal) : IRowOperation + { + // TODO: Consider omitting the alpha channel here. + public Image Normal { get; } = normal.Clone(); + // TODO: We only really need the alpha here, however using A8 will result in the multiply later zeroing out the RGB channels. + public Image BaseColorOpacity { get; } = new(normal.Width, normal.Height); + + private Buffer2D NormalBuffer + => Normal.Frames.RootFrame.PixelBuffer; + + private Buffer2D BaseColorOpacityBuffer + => BaseColorOpacity.Frames.RootFrame.PixelBuffer; + + public void Invoke(int y) + { + var normalSpan = NormalBuffer.DangerousGetRowSpan(y); + var baseColorOpacitySpan = BaseColorOpacityBuffer.DangerousGetRowSpan(y); + for (var x = 0; x < normalSpan.Length; x++) { ref var normalPixel = ref normalSpan[x]; - // Table row data (.a) - var tableRow = GetTableRowIndices(normalPixel.A / 255f); - var prevRow = table[tableRow.Previous]; - var nextRow = table[tableRow.Next]; + baseColorOpacitySpan[x].FromVector4(Vector4.One); + baseColorOpacitySpan[x].A = normalPixel.B; - // Base colour (table, .b) - var lerpedDiffuse = Vector3.Lerp((Vector3)prevRow.DiffuseColor, (Vector3)nextRow.DiffuseColor, tableRow.Weight); - baseColorSpan[x].FromVector4(new Vector4(lerpedDiffuse, 1)); - baseColorSpan[x].A = normalPixel.B; - - // Specular (table) - var lerpedSpecularColor = Vector3.Lerp((Vector3)prevRow.SpecularColor, (Vector3)nextRow.SpecularColor, tableRow.Weight); - var lerpedSpecularFactor = float.Lerp((float)prevRow.SpecularMask, (float)nextRow.SpecularMask, tableRow.Weight); - specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, lerpedSpecularFactor)); - - // Emissive (table) - var lerpedEmissive = Vector3.Lerp((Vector3)prevRow.EmissiveColor, (Vector3)nextRow.EmissiveColor, tableRow.Weight); - emissiveSpan[x].FromVector4(new Vector4(lerpedEmissive, 1)); - - // Normal (.rg) - // TODO: we don't actually need alpha at all for normal, but _not_ using the existing rgba texture means I'll need a new one, with a new accessor. Think about it. normalPixel.B = byte.MaxValue; normalPixel.A = byte.MaxValue; } } } - private static TableRow GetTableRowIndices(float input) + private readonly struct ProcessCharacterMaskOperation(Image mask) : IRowOperation { - // These calculations are ported from character.shpk. - var smoothed = MathF.Floor(input * 7.5f % 1.0f * 2) - * (-input * 15 + MathF.Floor(input * 15 + 0.5f)) - + input * 15; + public Image Occlusion { get; } = new(mask.Width, mask.Height); + public Image SpecularFactor { get; } = new(mask.Width, mask.Height); - var stepped = MathF.Floor(smoothed + 0.5f); + private Buffer2D MaskBuffer + => mask.Frames.RootFrame.PixelBuffer; - return new TableRow + private Buffer2D OcclusionBuffer + => Occlusion.Frames.RootFrame.PixelBuffer; + + private Buffer2D SpecularFactorBuffer + => SpecularFactor.Frames.RootFrame.PixelBuffer; + + public void Invoke(int y) { - Stepped = (int)stepped, - Previous = (int)MathF.Floor(smoothed), - Next = (int)MathF.Ceiling(smoothed), - Weight = smoothed % 1, - }; - } + var maskSpan = MaskBuffer.DangerousGetRowSpan(y); + var occlusionSpan = OcclusionBuffer.DangerousGetRowSpan(y); + var specularFactorSpan = SpecularFactorBuffer.DangerousGetRowSpan(y); - private ref struct TableRow - { - public int Stepped; - public int Previous; - public int Next; - public float Weight; + for (var x = 0; x < maskSpan.Length; x++) + { + ref var maskPixel = ref maskSpan[x]; + + occlusionSpan[x].FromL8(new L8(maskPixel.B)); + + specularFactorSpan[x].FromVector4(Vector4.One); + specularFactorSpan[x].A = maskPixel.R; + } + } } private readonly struct MultiplyOperation From 8468ed2c07f808c60c00e1ddd09fd7f9a5aadc06 Mon Sep 17 00:00:00 2001 From: ackwell Date: Mon, 2 Sep 2024 00:01:30 +1000 Subject: [PATCH 50/73] Fix skin.shpk --- .../Import/Models/Export/MaterialExporter.cs | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 0f98e5c4..ee8484f0 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -340,21 +340,7 @@ public class MaterialExporter var diffuse = material.Textures[TextureUsage.SamplerDiffuse]; var normal = material.Textures[TextureUsage.SamplerNormal]; - // Create a copy of the normal that's the same size as the diffuse for purposes of copying the opacity across. - var resizedNormal = normal.Clone(context => context.Resize(diffuse.Width, diffuse.Height)); - diffuse.ProcessPixelRows(resizedNormal, (diffuseAccessor, normalAccessor) => - { - for (var y = 0; y < diffuseAccessor.Height; y++) - { - var diffuseSpan = diffuseAccessor.GetRowSpan(y); - var normalSpan = normalAccessor.GetRowSpan(y); - - for (var x = 0; x < diffuseSpan.Length; x++) - diffuseSpan[x].A = normalSpan[x].B; - } - }); - - // Clear the blue channel out of the normal now that we're done with it. + // The normal also stores the skin color influence (.b) and wetness mask (.a) - remove. normal.ProcessPixelRows(normalAccessor => { for (var y = 0; y < normalAccessor.Height; y++) @@ -362,7 +348,10 @@ public class MaterialExporter var normalSpan = normalAccessor.GetRowSpan(y); for (var x = 0; x < normalSpan.Length; x++) + { normalSpan[x].B = byte.MaxValue; + normalSpan[x].A = byte.MaxValue; + } } }); From efd08ae0534cd6fc3489e3e605a4e38c52bb040e Mon Sep 17 00:00:00 2001 From: ackwell Date: Mon, 2 Sep 2024 00:51:51 +1000 Subject: [PATCH 51/73] Add charactertattoo.shpk support --- .../Import/Models/Export/MaterialExporter.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index ee8484f0..31590400 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -39,6 +39,7 @@ public class MaterialExporter "character.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.MASK, 0.5f), "characterlegacy.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.MASK, 0.5f), "characterglass.shpk" => BuildCharacter(material, name).WithAlpha(AlphaMode.BLEND), + "charactertattoo.shpk" => BuildCharacterTattoo(material, name), "hair.shpk" => BuildHair(material, name), "iris.shpk" => BuildIris(material, name), "skin.shpk" => BuildSkin(material, name), @@ -244,6 +245,37 @@ public class MaterialExporter } } + private static readonly Vector4 DefaultTattooColor = new Vector4(38, 112, 102, 255) / new Vector4(255); + + private static MaterialBuilder BuildCharacterTattoo(Material material, string name) + { + var normal = material.Textures[TextureUsage.SamplerNormal]; + var baseColor = new Image(normal.Width, normal.Height); + + normal.ProcessPixelRows(baseColor, (normalAccessor, baseColorAccessor) => + { + for (var y = 0; y < normalAccessor.Height; y++) + { + var normalSpan = normalAccessor.GetRowSpan(y); + var baseColorSpan = baseColorAccessor.GetRowSpan(y); + + for (var x = 0; x < normalSpan.Length; x++) + { + baseColorSpan[x].FromVector4(DefaultTattooColor); + baseColorSpan[x].A = normalSpan[x].A; + + normalSpan[x].B = byte.MaxValue; + normalSpan[x].A = byte.MaxValue; + } + } + }); + + return BuildSharedBase(material, name) + .WithBaseColor(BuildImage(baseColor, name, "basecolor")) + .WithNormal(BuildImage(normal, name, "normal")) + .WithAlpha(AlphaMode.BLEND); + } + // TODO: These are hardcoded colours - I'm not keen on supporting highly customizable exports, but there's possibly some more sensible values to use here. private static readonly Vector4 DefaultHairColor = new Vector4(130, 64, 13, 255) / new Vector4(255); private static readonly Vector4 DefaultHighlightColor = new Vector4(77, 126, 240, 255) / new Vector4(255); From 3b21de35cc0e0fac14d4d13600f4adffa2aa60cc Mon Sep 17 00:00:00 2001 From: ackwell Date: Mon, 2 Sep 2024 01:06:51 +1000 Subject: [PATCH 52/73] Fix iris.shpk --- .../Import/Models/Export/MaterialExporter.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 31590400..bcacf371 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -327,26 +327,23 @@ public class MaterialExporter // NOTE: This is largely the same as the hair material, but is also missing a few features that would cause it to diverge. Keeping separate for now. private static MaterialBuilder BuildIris(Material material, string name) { - var normal = material.Textures[TextureUsage.SamplerNormal]; - var mask = material.Textures[TextureUsage.SamplerMask]; + var normal = material.Textures[TextureUsage.SamplerNormal]; + var mask = material.Textures[TextureUsage.SamplerMask]; + var baseColor = material.Textures[TextureUsage.SamplerDiffuse]; - mask.Mutate(context => context.Resize(normal.Width, normal.Height)); + mask.Mutate(context => context.Resize(baseColor.Width, baseColor.Height)); - var baseColor = new Image(normal.Width, normal.Height); - normal.ProcessPixelRows(mask, baseColor, (normalAccessor, maskAccessor, baseColorAccessor) => + baseColor.ProcessPixelRows(mask, (baseColorAccessor, maskAccessor) => { - for (var y = 0; y < normalAccessor.Height; y++) + for (var y = 0; y < baseColor.Height; y++) { - var normalSpan = normalAccessor.GetRowSpan(y); - var maskSpan = maskAccessor.GetRowSpan(y); var baseColorSpan = baseColorAccessor.GetRowSpan(y); + var maskSpan = maskAccessor.GetRowSpan(y); - for (var x = 0; x < normalSpan.Length; x++) + for (var x = 0; x < baseColorSpan.Length; x++) { - baseColorSpan[x].FromVector4(DefaultEyeColor * new Vector4(maskSpan[x].R / 255f)); - baseColorSpan[x].A = normalSpan[x].A; - - normalSpan[x].A = byte.MaxValue; + var eyeColor = Vector4.Lerp(Vector4.One, DefaultEyeColor, maskSpan[x].B / 255f); + baseColorSpan[x].FromVector4(baseColorSpan[x].ToVector4() * eyeColor); } } }); From a1a880a0f4a85bdbe823e6e972f0bc59e2b8305f Mon Sep 17 00:00:00 2001 From: ackwell Date: Mon, 2 Sep 2024 01:30:21 +1000 Subject: [PATCH 53/73] Fix hair.shpk --- Penumbra/Import/Models/Export/MaterialExporter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index bcacf371..121e6eed 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -306,10 +306,11 @@ public class MaterialExporter for (var x = 0; x < normalSpan.Length; x++) { - var color = Vector4.Lerp(DefaultHairColor, DefaultHighlightColor, maskSpan[x].A / 255f); - baseColorSpan[x].FromVector4(color * new Vector4(maskSpan[x].R / 255f)); + var color = Vector4.Lerp(DefaultHairColor, DefaultHighlightColor, normalSpan[x].B / 255f); + baseColorSpan[x].FromVector4(color * new Vector4(maskSpan[x].A / 255f)); baseColorSpan[x].A = normalSpan[x].A; + normalSpan[x].B = byte.MaxValue; normalSpan[x].A = byte.MaxValue; } } From 76c0264cbee424429b7b6c611378015d227fd4c0 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 14:05:13 +0200 Subject: [PATCH 54/73] Reenable model IO for testing. --- Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index 490fa147..de088736 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -97,9 +97,7 @@ public partial class ModEditWindow private void DrawImportExport(MdlTab tab, bool disabled) { - // TODO: Enable when functional. - using var dawntrailDisabled = ImRaii.Disabled(); - if (!ImGui.CollapsingHeader("Import / Export (currently disabled due to Dawntrail format changes)") || true) + if (!ImGui.CollapsingHeader("Import / Export")) return; var childSize = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); From df0526e6e510c5129aa0deaf5038728318e5552f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 14:23:36 +0200 Subject: [PATCH 55/73] Fix readoing and displaying DemiHuman IMC Identifiers. --- Penumbra/Meta/Manipulations/Imc.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Penumbra/Meta/Manipulations/Imc.cs b/Penumbra/Meta/Manipulations/Imc.cs index 1b2492ee..cba6c379 100644 --- a/Penumbra/Meta/Manipulations/Imc.cs +++ b/Penumbra/Meta/Manipulations/Imc.cs @@ -68,9 +68,13 @@ public readonly record struct ImcIdentifier( => (MetaIndex)(-1); public override string ToString() - => ObjectType is ObjectType.Equipment or ObjectType.Accessory - ? $"Imc - {PrimaryId} - {EquipSlot.ToName()} - {Variant}" - : $"Imc - {PrimaryId} - {ObjectType.ToName()} - {SecondaryId} - {BodySlot} - {Variant}"; + => ObjectType switch + { + ObjectType.Equipment or ObjectType.Accessory => $"Imc - {PrimaryId} - {EquipSlot.ToName()} - {Variant}", + ObjectType.DemiHuman => $"Imc - {PrimaryId} - DemiHuman - {SecondaryId} - {EquipSlot.ToName()} - {Variant}", + _ => $"Imc - {PrimaryId} - {ObjectType.ToName()} - {SecondaryId} - {BodySlot} - {Variant}", + }; + public bool Validate() { @@ -102,6 +106,7 @@ public readonly record struct ImcIdentifier( return false; if (ItemData.AdaptOffhandImc(PrimaryId, out _)) return false; + break; } @@ -163,7 +168,7 @@ public readonly record struct ImcIdentifier( case ObjectType.DemiHuman: { var secondaryId = new SecondaryId(jObj["SecondaryId"]?.ToObject() ?? 0); - var slot = jObj["Slot"]?.ToObject() ?? EquipSlot.Unknown; + var slot = jObj["EquipSlot"]?.ToObject() ?? EquipSlot.Unknown; ret = new ImcIdentifier(primaryId, (Variant)variant, objectType, secondaryId, slot, BodySlot.Unknown); break; } From 740816f3a670d9f53759d674795456a193ca202d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 6 Oct 2024 14:51:52 +0200 Subject: [PATCH 56/73] Fix accessory VFX change not working. --- .../Hooks/Resources/ResolvePathHooksBase.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index b1b23f27..a31dee4c 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -3,6 +3,8 @@ using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; using Penumbra.Collections; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; namespace Penumbra.Interop.Hooks.Resources; @@ -212,25 +214,39 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable return ret; } + [StructLayout(LayoutKind.Explicit)] + private struct ChangedEquipData + { + [FieldOffset(0)] + public PrimaryId Model; + + [FieldOffset(2)] + public Variant Variant; + + [FieldOffset(20)] + public ushort VfxId; + + [FieldOffset(22)] + public GenderRace GenderRace; + } + private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam) { if (slotIndex is <= 4 or >= 10) return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - var changedEquipData = ((Human*)drawObject)->ChangedEquipData; + var changedEquipData = (ChangedEquipData*)((Human*)drawObject)->ChangedEquipData; // Enable vfxs for accessories if (changedEquipData == null) return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - var slot = (ushort*)(changedEquipData + 12 * (nint)slotIndex); - var model = slot[0]; - var variant = slot[1]; - var vfxId = slot[4]; + ref var slot = ref changedEquipData[slotIndex]; - if (model == 0 || variant == 0 || vfxId == 0) + if (slot.Model == 0 || slot.Variant == 0 || slot.VfxId == 0) return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - if (!Utf8.TryWrite(new Span((void*)pathBuffer, (int)pathBufferSize), $"chara/accessory/a{model:D4}/vfx/eff/va{vfxId:D4}.avfx\0", + if (!Utf8.TryWrite(new Span((void*)pathBuffer, (int)pathBufferSize), + $"chara/accessory/a{slot.Model.Id:D4}/vfx/eff/va{slot.VfxId:D4}.avfx\0", out _)) return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); From c4b59295cb4db0ab1782fec14137d85b9e6de153 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 6 Oct 2024 12:54:51 +0000 Subject: [PATCH 57/73] [CI] Updating repo.json for testing_1.2.1.6 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 36b27682..38ea45c0 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.5", + "TestingAssemblyVersion": "1.2.1.6", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.5/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.6/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 2e424a693d67f16c02e154c28a8f63d9f7056fb2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Mon, 7 Oct 2024 16:18:51 +0200 Subject: [PATCH 58/73] Update GameData --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index dd86dafb..34c96a55 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit dd86dafb88ca4c7b662938bbc1310729ba7f788d +Subproject commit 34c96a55efe1ce1296d9edcd8296f6396998cc6a From 4a0c996ff6d492d887a4712606f15e7cf69cb73a Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Wed, 9 Oct 2024 18:47:30 +0200 Subject: [PATCH 59/73] Fix some off-by-one errors with the import progress reports, add test implementation for pbd editing. --- Penumbra.GameData | 2 +- Penumbra/Api/IpcTester/TemporaryIpcTester.cs | 17 +- Penumbra/Import/TexToolsImporter.Gui.cs | 19 +- Penumbra/Import/TexToolsImporter.ModPack.cs | 2 + Penumbra/Mods/Editor/ModFileCollection.cs | 18 +- .../AdvancedWindow/ModEditWindow.Deformers.cs | 324 ++++++++++++++++++ Penumbra/UI/AdvancedWindow/ModEditWindow.cs | 6 + 7 files changed, 368 insertions(+), 20 deletions(-) create mode 100644 Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs diff --git a/Penumbra.GameData b/Penumbra.GameData index 34c96a55..07b01ec9 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 34c96a55efe1ce1296d9edcd8296f6396998cc6a +Subproject commit 07b01ec9b043e4b8f56d084f5d6cde1ed4ed9a58 diff --git a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs index 6d4f17b2..f6d1c9eb 100644 --- a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs +++ b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs @@ -9,7 +9,6 @@ using Penumbra.Api.Api; using Penumbra.Api.Enums; using Penumbra.Api.IpcSubscribers; using Penumbra.Collections.Manager; -using Penumbra.Meta.Manipulations; using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -28,6 +27,8 @@ public class TemporaryIpcTester( { public Guid LastCreatedCollectionId = Guid.Empty; + private readonly bool _debug = Assembly.GetAssembly(typeof(TemporaryIpcTester))?.GetName().Version?.Major >= 9; + private Guid? _tempGuid; private string _tempCollectionName = string.Empty; private string _tempCollectionGuidName = string.Empty; @@ -48,9 +49,9 @@ public class TemporaryIpcTester( ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128); ImGuiUtil.GuidInput("##guid", "Collection GUID...", string.Empty, ref _tempGuid, ref _tempCollectionGuidName); ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0); - ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32); - ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256); - ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256); + ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32); + ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256); + ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256); ImUtf8.InputText("##tempManip"u8, ref _tempManipulation, "Manipulation Base64 String..."u8); ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite); @@ -102,7 +103,7 @@ public class TemporaryIpcTester( !collections.Storage.ByName(_tempModName, out var copyCollection)) && copyCollection is { HasCache: true }) { - var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString()); + var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString()); var manips = MetaApi.CompressMetaManipulations(copyCollection); _lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999); } @@ -124,11 +125,11 @@ public class TemporaryIpcTester( public void DrawCollections() { - using var collTree = ImRaii.TreeNode("Temporary Collections##TempCollections"); + using var collTree = ImUtf8.TreeNode("Temporary Collections##TempCollections"u8); if (!collTree) return; - using var table = ImRaii.Table("##collTree", 6, ImGuiTableFlags.SizingFixedFit); + using var table = ImUtf8.Table("##collTree"u8, 6, ImGuiTableFlags.SizingFixedFit); if (!table) return; @@ -139,7 +140,7 @@ public class TemporaryIpcTester( var character = tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName) .FirstOrDefault() ?? "Unknown"; - if (ImGui.Button("Save##Collection")) + if (_debug && ImUtf8.Button("Save##Collection"u8)) TemporaryMod.SaveTempCollection(config, saveService, modManager, collection, character); using (ImRaii.PushFont(UiBuilder.MonoFont)) diff --git a/Penumbra/Import/TexToolsImporter.Gui.cs b/Penumbra/Import/TexToolsImporter.Gui.cs index a069204c..f145f560 100644 --- a/Penumbra/Import/TexToolsImporter.Gui.cs +++ b/Penumbra/Import/TexToolsImporter.Gui.cs @@ -46,21 +46,28 @@ public partial class TexToolsImporter { ImGui.NewLine(); ImGui.NewLine(); - percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / (float)_currentNumOptions; - ImGui.ProgressBar(percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}"); + if (_currentOptionIdx >= _currentNumOptions) + ImGui.ProgressBar(1f, size, $"Extracted {_currentNumOptions} Options"); + else + ImGui.ProgressBar(_currentOptionIdx / (float)_currentNumOptions, size, + $"Extracting Option {_currentOptionIdx + 1} / {_currentNumOptions}..."); + ImGui.NewLine(); if (State != ImporterState.DeduplicatingFiles) ImGui.TextUnformatted( - $"Extracting option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}..."); + $"Extracting Option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}..."); } ImGui.NewLine(); ImGui.NewLine(); - percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / (float)_currentNumFiles; - ImGui.ProgressBar(percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}"); + if (_currentFileIdx >= _currentNumFiles) + ImGui.ProgressBar(1f, size, $"Extracted {_currentNumFiles} Files"); + else + ImGui.ProgressBar(_currentFileIdx / (float)_currentNumFiles, size, $"Extracting File {_currentFileIdx + 1} / {_currentNumFiles}..."); + ImGui.NewLine(); if (State != ImporterState.DeduplicatingFiles) - ImGui.TextUnformatted($"Extracting file {_currentFileName}..."); + ImGui.TextUnformatted($"Extracting File {_currentFileName}..."); return false; } diff --git a/Penumbra/Import/TexToolsImporter.ModPack.cs b/Penumbra/Import/TexToolsImporter.ModPack.cs index 3ae1eda9..7bbb762e 100644 --- a/Penumbra/Import/TexToolsImporter.ModPack.cs +++ b/Penumbra/Import/TexToolsImporter.ModPack.cs @@ -151,6 +151,7 @@ public partial class TexToolsImporter _currentGroupName = string.Empty; _currentOptionName = "Default"; ExtractSimpleModList(_currentModDirectory, modList.SimpleModsList); + ++_currentOptionIdx; } // Iterate through all pages @@ -208,6 +209,7 @@ public partial class TexToolsImporter options.Insert(idx, MultiSubMod.WithoutGroup(option.Name, option.Description, ModPriority.Default)); if (option.IsChecked) defaultSettings = Setting.Single(idx); + ++_currentOptionIdx; } } diff --git a/Penumbra/Mods/Editor/ModFileCollection.cs b/Penumbra/Mods/Editor/ModFileCollection.cs index 241f5b3b..20423493 100644 --- a/Penumbra/Mods/Editor/ModFileCollection.cs +++ b/Penumbra/Mods/Editor/ModFileCollection.cs @@ -12,6 +12,7 @@ public class ModFileCollection : IDisposable, IService private readonly List _mdl = []; private readonly List _tex = []; private readonly List _shpk = []; + private readonly List _pbd = []; private readonly SortedSet _missing = []; private readonly HashSet _usedPaths = []; @@ -23,19 +24,22 @@ public class ModFileCollection : IDisposable, IService => Ready ? _usedPaths : []; public IReadOnlyList Available - => Ready ? _available : Array.Empty(); + => Ready ? _available : []; public IReadOnlyList Mtrl - => Ready ? _mtrl : Array.Empty(); + => Ready ? _mtrl : []; public IReadOnlyList Mdl - => Ready ? _mdl : Array.Empty(); + => Ready ? _mdl : []; public IReadOnlyList Tex - => Ready ? _tex : Array.Empty(); + => Ready ? _tex : []; public IReadOnlyList Shpk - => Ready ? _shpk : Array.Empty(); + => Ready ? _shpk : []; + + public IReadOnlyList Pbd + => Ready ? _pbd : []; public bool Ready { get; private set; } = true; @@ -128,6 +132,9 @@ public class ModFileCollection : IDisposable, IService case ".shpk": _shpk.Add(registry); break; + case ".pbd": + _pbd.Add(registry); + break; } } } @@ -139,6 +146,7 @@ public class ModFileCollection : IDisposable, IService _mdl.Clear(); _tex.Clear(); _shpk.Clear(); + _pbd.Clear(); } private void ClearPaths(bool clearRegistries, CancellationToken tok) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs new file mode 100644 index 00000000..1b6535a7 --- /dev/null +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs @@ -0,0 +1,324 @@ +using Dalamud.Interface; +using Dalamud.Interface.ImGuiNotification; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using OtterGui; +using OtterGui.Text; +using Penumbra.GameData.Data; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Files; +using Penumbra.UI.Classes; +using Notification = OtterGui.Classes.Notification; + +namespace Penumbra.UI.AdvancedWindow; + +public partial class ModEditWindow +{ + private readonly FileEditor _pbdTab; + private readonly PbdData _pbdData = new(); + + private bool DrawDeformerPanel(PbdTab tab, bool disabled) + { + _pbdData.Update(tab.File); + DrawGenderRaceSelector(tab); + ImGui.SameLine(); + DrawBoneSelector(); + ImGui.SameLine(); + return DrawBoneData(tab, disabled); + } + + private void DrawGenderRaceSelector(PbdTab tab) + { + using var group = ImUtf8.Group(); + var width = ImUtf8.CalcTextSize("Hellsguard - Female (Child)____0000"u8).X + 2 * ImGui.GetStyle().WindowPadding.X; + using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0) + .Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) + { + ImGui.SetNextItemWidth(width); + ImUtf8.InputText("##grFilter"u8, ref _pbdData.RaceCodeFilter, "Filter..."u8); + } + + using var child = ImUtf8.Child("GenderRace"u8, new Vector2(width, ImGui.GetContentRegionMax().Y), true); + if (!child) + return; + + var metaColor = ColorId.ItemId.Value(); + foreach (var (deformer, index) in tab.File.Deformers.WithIndex()) + { + var name = deformer.GenderRace.ToName(); + var raceCode = deformer.GenderRace.ToRaceCode(); + // No clipping necessary since this are not that many objects anyway. + if (!name.Contains(_pbdData.RaceCodeFilter) && !raceCode.Contains(_pbdData.RaceCodeFilter)) + continue; + + using var id = ImUtf8.PushId(index); + using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), deformer.RacialDeformer.IsEmpty); + if (ImUtf8.Selectable(name, deformer.GenderRace == _pbdData.SelectedRaceCode)) + { + _pbdData.SelectedRaceCode = deformer.GenderRace; + _pbdData.SelectedDeformer = deformer.RacialDeformer; + } + + ImGui.SameLine(); + color.Push(ImGuiCol.Text, metaColor); + ImUtf8.TextRightAligned(raceCode); + } + } + + private void DrawBoneSelector() + { + using var group = ImUtf8.Group(); + var width = 200 * ImUtf8.GlobalScale; + using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0) + .Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero)) + { + ImGui.SetNextItemWidth(width); + ImUtf8.InputText("##boneFilter"u8, ref _pbdData.BoneFilter, "Filter..."u8); + } + + using var child = ImUtf8.Child("Bone"u8, new Vector2(width, ImGui.GetContentRegionMax().Y), true); + if (!child) + return; + + if (_pbdData.SelectedDeformer == null) + return; + + if (_pbdData.SelectedDeformer.IsEmpty) + { + ImUtf8.Text(""u8); + } + else + { + var height = ImGui.GetTextLineHeightWithSpacing(); + var skips = ImGuiClip.GetNecessarySkips(height); + var remainder = ImGuiClip.FilteredClippedDraw(_pbdData.SelectedDeformer.DeformMatrices.Keys, skips, + b => b.Contains(_pbdData.BoneFilter), bone + => + { + if (ImUtf8.Selectable(bone, bone == _pbdData.SelectedBone)) + _pbdData.SelectedBone = bone; + }); + ImGuiClip.DrawEndDummy(remainder, height); + } + } + + private bool DrawBoneData(PbdTab tab, bool disabled) + { + using var child = ImUtf8.Child("Data"u8, ImGui.GetContentRegionMax() with { X = ImGui.GetContentRegionAvail().X}, true); + if (!child) + return false; + + if (_pbdData.SelectedBone == null) + return false; + + if (!_pbdData.SelectedDeformer!.DeformMatrices.TryGetValue(_pbdData.SelectedBone, out var matrix)) + return false; + + var width = UiBuilder.MonoFont.GetCharAdvance('0') * 12 + ImGui.GetStyle().FramePadding.X * 2; + var dummyHeight = ImGui.GetTextLineHeight() / 2; + var ret = DrawAddNewBone(tab, disabled, width); + + ImUtf8.Dummy(0, dummyHeight); + ImGui.Separator(); + ImUtf8.Dummy(0, dummyHeight); + ret |= DrawDeformerMatrix(disabled, matrix, width); + ImUtf8.Dummy(0, dummyHeight); + ret |= DrawCopyPasteButtons(disabled, matrix, width); + + + ImUtf8.Dummy(0, dummyHeight); + ImGui.Separator(); + ImUtf8.Dummy(0, dummyHeight); + ret |= DrawDecomposedData(disabled, matrix, width); + + return ret; + } + + private bool DrawAddNewBone(PbdTab tab, bool disabled, float width) + { + var ret = false; + ImUtf8.TextFrameAligned("Copy the values of the bone "u8); + ImGui.SameLine(0, 0); + using (ImRaii.PushColor(ImGuiCol.Text, ColorId.NewMod.Value())) + { + ImUtf8.TextFrameAligned(_pbdData.SelectedBone); + } + ImGui.SameLine(0, 0); + ImUtf8.TextFrameAligned(" to a new bone of name"u8); + + var fullWidth = width * 4 + ImGui.GetStyle().ItemSpacing.X * 3; + ImGui.SetNextItemWidth(fullWidth); + ImUtf8.InputText("##newBone"u8, ref _pbdData.NewBoneName, "New Bone Name..."u8); + ImUtf8.TextFrameAligned("for all races that have a corresponding bone."u8); + ImGui.SameLine(0, fullWidth - width - ImGui.GetItemRectSize().X); + if (!ImUtf8.ButtonEx("Apply"u8, ""u8, new Vector2(width, 0), + disabled || _pbdData.NewBoneName.Length == 0 || _pbdData.SelectedBone == null)) + return ret; + + foreach (var deformer in tab.File.Deformers) + { + if (!deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.SelectedBone!, out var existingMatrix)) + continue; + + if (!deformer.RacialDeformer.DeformMatrices.TryAdd(_pbdData.NewBoneName, existingMatrix) + && deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.NewBoneName, out var newBoneMatrix) + && !newBoneMatrix.Equals(existingMatrix)) + Penumbra.Messager.AddMessage(new Notification( + $"Could not add deformer matrix to {deformer.GenderRace.ToName()}, Bone {_pbdData.NewBoneName} because it already has a deformer that differs from the intended one.", + NotificationType.Warning)); + else + ret = true; + } + + _pbdData.NewBoneName = string.Empty; + return ret; + } + + private bool DrawDeformerMatrix(bool disabled, in TransformMatrix matrix, float width) + { + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + using var _ = ImRaii.Disabled(disabled); + var ret = false; + for (var i = 0; i < 3; ++i) + { + for (var j = 0; j < 4; ++j) + { + using var id = ImUtf8.PushId(i * 4 + j); + ImGui.SetNextItemWidth(width); + var tmp = matrix[i, j]; + if (ImUtf8.InputScalar(""u8, ref tmp, "% 12.8f"u8)) + { + ret = true; + _pbdData.SelectedDeformer!.DeformMatrices[_pbdData.SelectedBone!] = matrix.ChangeValue(i, j, tmp); + } + + ImGui.SameLine(); + } + + ImGui.NewLine(); + } + + return ret; + } + + private bool DrawCopyPasteButtons(bool disabled, in TransformMatrix matrix, float width) + { + var size = new Vector2(width, 0); + if (ImUtf8.Button("Copy Values"u8, size)) + _pbdData.CopiedMatrix = matrix; + + ImGui.SameLine(); + + if (ImUtf8.ButtonEx("Paste Values"u8, ""u8, size, disabled || !_pbdData.CopiedMatrix.HasValue)) + { + _pbdData.SelectedDeformer!.DeformMatrices[_pbdData.SelectedBone!] = _pbdData.CopiedMatrix!.Value; + return true; + } + + return false; + } + + private bool DrawDecomposedData(bool disabled, in TransformMatrix matrix, float width) + { + var ret = false; + + + if (!matrix.TryDecompose(out var scale, out var rotation, out var translation)) + return false; + + using (ImUtf8.Group()) + { + using var font = ImRaii.PushFont(UiBuilder.MonoFont); + using var _ = ImRaii.Disabled(disabled); + + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##ScaleX"u8, ref scale.X, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##ScaleY"u8, ref scale.Y, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##ScaleZ"u8, ref scale.Z, "% 12.8f"u8); + + + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##TranslationX"u8, ref translation.X, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##TranslationY"u8, ref translation.Y, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##TranslationZ"u8, ref translation.Z, "% 12.8f"u8); + + + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##RotationR"u8, ref rotation.W, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##RotationI"u8, ref rotation.X, "% 12.8f"u8); + + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##RotationJ"u8, ref rotation.Y, "% 12.8f"u8); + ImGui.SameLine(); + ImGui.SetNextItemWidth(width); + ret |= ImUtf8.InputScalar("##RotationK"u8, ref rotation.Z, "% 12.8f"u8); + } + + ImGui.SameLine(); + using (ImUtf8.Group()) + { + ImUtf8.TextFrameAligned("Scale"u8); + ImUtf8.TextFrameAligned("Translation"u8); + ImUtf8.TextFrameAligned("Rotation (Quaternion, rijk)"u8); + } + + if (ret) + _pbdData.SelectedDeformer!.DeformMatrices[_pbdData.SelectedBone!] = TransformMatrix.Compose(scale, rotation, translation); + return ret; + } + + public class PbdTab(byte[] data, string filePath) : IWritable + { + public readonly string FilePath = filePath; + + public readonly PbdFile File = new(data); + + public bool Valid + => File.Valid; + + public byte[] Write() + => File.Write(); + } + + private class PbdData + { + public GenderRace SelectedRaceCode = GenderRace.Unknown; + public RacialDeformer? SelectedDeformer; + public string? SelectedBone; + public string NewBoneName = string.Empty; + public string BoneFilter = string.Empty; + public string RaceCodeFilter = string.Empty; + + public TransformMatrix? CopiedMatrix; + + public void Update(PbdFile file) + { + if (SelectedRaceCode is GenderRace.Unknown) + { + SelectedDeformer = null; + } + else + { + SelectedDeformer = file.Deformers.FirstOrDefault(p => p.GenderRace == SelectedRaceCode).RacialDeformer; + if (SelectedDeformer is null) + SelectedRaceCode = GenderRace.Unknown; + } + } + } +} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index f2fe8b9e..1a4065bb 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -236,6 +236,8 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _itemSwapTab.DrawContent(); } + _pbdTab.Draw(); + DrawMissingFilesTab(); DrawMaterialReassignmentTab(); } @@ -665,6 +667,10 @@ public partial class ModEditWindow : Window, IDisposable, IUiService () => PopulateIsOnPlayer(_editor.Files.Shpk, ResourceType.Shpk), DrawShaderPackagePanel, () => Mod?.ModPath.FullName ?? string.Empty, (bytes, path, _) => new ShpkTab(_fileDialog, bytes, path)); + _pbdTab = new FileEditor(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Deformers", ".pbd", + () => _editor.Files.Pbd, DrawDeformerPanel, + () => Mod?.ModPath.FullName ?? string.Empty, + (bytes, path, _) => new PbdTab(bytes, path)); _center = new CombinedTexture(_left, _right); _textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex)); _resourceTreeFactory = resourceTreeFactory; From 40c772a9da9f8b41dad7e023b7a47c48ea8b338e Mon Sep 17 00:00:00 2001 From: Actions User Date: Wed, 9 Oct 2024 16:49:59 +0000 Subject: [PATCH 60/73] [CI] Updating repo.json for testing_1.2.1.7 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 38ea45c0..0d9e071f 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.6", + "TestingAssemblyVersion": "1.2.1.7", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.6/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.7/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 2c5ffc1bc583db158c932ab057a686d6275186a2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 10 Oct 2024 16:50:05 +0200 Subject: [PATCH 61/73] Add delete and single add button and fix child sizes. --- .../AdvancedWindow/ModEditWindow.Deformers.cs | 74 +++++++++++++------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs index 1b6535a7..258e51ff 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs @@ -38,7 +38,8 @@ public partial class ModEditWindow ImUtf8.InputText("##grFilter"u8, ref _pbdData.RaceCodeFilter, "Filter..."u8); } - using var child = ImUtf8.Child("GenderRace"u8, new Vector2(width, ImGui.GetContentRegionMax().Y), true); + using var child = ImUtf8.Child("GenderRace"u8, + new Vector2(width, ImGui.GetContentRegionMax().Y - ImGui.GetFrameHeight() - ImGui.GetStyle().WindowPadding.Y), true); if (!child) return; @@ -76,7 +77,8 @@ public partial class ModEditWindow ImUtf8.InputText("##boneFilter"u8, ref _pbdData.BoneFilter, "Filter..."u8); } - using var child = ImUtf8.Child("Bone"u8, new Vector2(width, ImGui.GetContentRegionMax().Y), true); + using var child = ImUtf8.Child("Bone"u8, + new Vector2(width, ImGui.GetContentRegionMax().Y - ImGui.GetFrameHeight() - ImGui.GetStyle().WindowPadding.Y), true); if (!child) return; @@ -104,7 +106,8 @@ public partial class ModEditWindow private bool DrawBoneData(PbdTab tab, bool disabled) { - using var child = ImUtf8.Child("Data"u8, ImGui.GetContentRegionMax() with { X = ImGui.GetContentRegionAvail().X}, true); + using var child = ImUtf8.Child("Data"u8, + ImGui.GetContentRegionAvail() with { Y = ImGui.GetContentRegionMax().Y - ImGui.GetStyle().WindowPadding.Y }, true); if (!child) return false; @@ -116,7 +119,7 @@ public partial class ModEditWindow var width = UiBuilder.MonoFont.GetCharAdvance('0') * 12 + ImGui.GetStyle().FramePadding.X * 2; var dummyHeight = ImGui.GetTextLineHeight() / 2; - var ret = DrawAddNewBone(tab, disabled, width); + var ret = DrawAddNewBone(tab, disabled, matrix, width); ImUtf8.Dummy(0, dummyHeight); ImGui.Separator(); @@ -134,7 +137,7 @@ public partial class ModEditWindow return ret; } - private bool DrawAddNewBone(PbdTab tab, bool disabled, float width) + private bool DrawAddNewBone(PbdTab tab, bool disabled, in TransformMatrix matrix, float width) { var ret = false; ImUtf8.TextFrameAligned("Copy the values of the bone "u8); @@ -143,6 +146,7 @@ public partial class ModEditWindow { ImUtf8.TextFrameAligned(_pbdData.SelectedBone); } + ImGui.SameLine(0, 0); ImUtf8.TextFrameAligned(" to a new bone of name"u8); @@ -151,26 +155,36 @@ public partial class ModEditWindow ImUtf8.InputText("##newBone"u8, ref _pbdData.NewBoneName, "New Bone Name..."u8); ImUtf8.TextFrameAligned("for all races that have a corresponding bone."u8); ImGui.SameLine(0, fullWidth - width - ImGui.GetItemRectSize().X); - if (!ImUtf8.ButtonEx("Apply"u8, ""u8, new Vector2(width, 0), + if (ImUtf8.ButtonEx("Apply"u8, ""u8, new Vector2(width, 0), disabled || _pbdData.NewBoneName.Length == 0 || _pbdData.SelectedBone == null)) - return ret; - - foreach (var deformer in tab.File.Deformers) { - if (!deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.SelectedBone!, out var existingMatrix)) - continue; + foreach (var deformer in tab.File.Deformers) + { + if (!deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.SelectedBone!, out var existingMatrix)) + continue; - if (!deformer.RacialDeformer.DeformMatrices.TryAdd(_pbdData.NewBoneName, existingMatrix) - && deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.NewBoneName, out var newBoneMatrix) - && !newBoneMatrix.Equals(existingMatrix)) - Penumbra.Messager.AddMessage(new Notification( - $"Could not add deformer matrix to {deformer.GenderRace.ToName()}, Bone {_pbdData.NewBoneName} because it already has a deformer that differs from the intended one.", - NotificationType.Warning)); - else - ret = true; + if (!deformer.RacialDeformer.DeformMatrices.TryAdd(_pbdData.NewBoneName, existingMatrix) + && deformer.RacialDeformer.DeformMatrices.TryGetValue(_pbdData.NewBoneName, out var newBoneMatrix) + && !newBoneMatrix.Equals(existingMatrix)) + Penumbra.Messager.AddMessage(new Notification( + $"Could not add deformer matrix to {deformer.GenderRace.ToName()}, Bone {_pbdData.NewBoneName} because it already has a deformer that differs from the intended one.", + NotificationType.Warning)); + else + ret = true; + } + + _pbdData.NewBoneName = string.Empty; } - _pbdData.NewBoneName = string.Empty; + if (ImUtf8.ButtonEx("Copy Values to Single New Bone Entry"u8, ""u8, new Vector2(fullWidth, 0), + disabled || _pbdData.NewBoneName.Length == 0 || _pbdData.SelectedDeformer!.DeformMatrices.ContainsKey(_pbdData.NewBoneName))) + { + _pbdData.SelectedDeformer!.DeformMatrices[_pbdData.NewBoneName] = matrix; + ret = true; + _pbdData.NewBoneName = string.Empty; + } + + return ret; } @@ -209,13 +223,29 @@ public partial class ModEditWindow ImGui.SameLine(); + var ret = false; if (ImUtf8.ButtonEx("Paste Values"u8, ""u8, size, disabled || !_pbdData.CopiedMatrix.HasValue)) { _pbdData.SelectedDeformer!.DeformMatrices[_pbdData.SelectedBone!] = _pbdData.CopiedMatrix!.Value; - return true; + ret = true; } - return false; + var modifier = _config.DeleteModModifier.IsActive(); + ImGui.SameLine(); + if (modifier) + { + if (ImUtf8.ButtonEx("Delete"u8, "Delete this bone entry."u8, size, disabled)) + { + ret |= _pbdData.SelectedDeformer!.DeformMatrices.Remove(_pbdData.SelectedBone!); + _pbdData.SelectedBone = null; + } + } + else + { + ImUtf8.ButtonEx("Delete"u8, $"Delete this bone entry. Hold {_config.DeleteModModifier} to delete.", size, true); + } + + return ret; } private bool DrawDecomposedData(bool disabled, in TransformMatrix matrix, float width) From 1d5a7a41ab9e056d7070d188d01f414debc1f81f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 11 Oct 2024 16:35:47 +0200 Subject: [PATCH 62/73] Remove BonusItem from use and update ResourceTree a bit. --- Penumbra.GameData | 2 +- .../ResolveContext.PathResolution.cs | 8 +- .../Interop/ResourceTree/ResolveContext.cs | 19 ++-- Penumbra/Interop/ResourceTree/ResourceTree.cs | 11 ++- .../UI/AdvancedWindow/ResourceTreeViewer.cs | 94 ++++++++++--------- 5 files changed, 69 insertions(+), 65 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 07b01ec9..61e06785 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 07b01ec9b043e4b8f56d084f5d6cde1ed4ed9a58 +Subproject commit 61e067857c2cf62bf8426ff6b305e37990f7767a diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index 43324516..c554d97a 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -36,13 +36,13 @@ internal partial record ResolveContext private Utf8GamePath ResolveEquipmentModelPath() { var path = IsEquipmentSlot(SlotIndex) - ? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot) - : GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot); + ? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot.ToSlot()) + : GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot.ToSlot()); return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } private GenderRace ResolveModelRaceCode() - => ResolveEqdpRaceCode(Slot, Equipment.Set); + => ResolveEqdpRaceCode(Slot.ToSlot(), Equipment.Set); private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId) { @@ -161,7 +161,7 @@ internal partial record ResolveContext return variant.Id; } - var entry = ImcFile.GetEntry(imcFileData, Slot, variant, out var exists); + var entry = ImcFile.GetEntry(imcFileData, Slot.ToSlot(), variant, out var exists); if (!exists) return variant.Id; diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index b99ee235..207551e7 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -30,7 +30,7 @@ internal record GlobalResolveContext( public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128); public unsafe ResolveContext CreateContext(CharaBase* characterBase, uint slotIndex = 0xFFFFFFFFu, - EquipSlot slot = EquipSlot.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default) + FullEquipType slot = FullEquipType.Unknown, CharacterArmor equipment = default, SecondaryId secondaryId = default) => new(this, characterBase, slotIndex, slot, equipment, secondaryId); } @@ -38,7 +38,7 @@ internal unsafe partial record ResolveContext( GlobalResolveContext Global, Pointer CharacterBasePointer, uint SlotIndex, - EquipSlot Slot, + FullEquipType Slot, CharacterArmor Equipment, SecondaryId SecondaryId) { @@ -346,13 +346,14 @@ internal unsafe partial record ResolveContext( if (isEquipment) foreach (var item in Global.Identifier.Identify(Equipment.Set, 0, Equipment.Variant, Slot.ToSlot())) { - var name = Slot switch + var name = item.Name; + if (Slot is FullEquipType.Finger) + name = SlotIndex switch { - EquipSlot.RFinger => "R: ", - EquipSlot.LFinger => "L: ", - _ => string.Empty, - } - + item.Name; + 8 => "R: " + name, + 9 => "L: " + name, + _ => name, + }; return new ResourceNode.UiData(name, item.Type.GetCategoryIcon().ToFlag()); } @@ -361,7 +362,7 @@ internal unsafe partial record ResolveContext( return dataFromPath; return isEquipment - ? new ResourceNode.UiData(Slot.ToName(), Slot.ToEquipType().GetCategoryIcon().ToFlag()) + ? new ResourceNode.UiData(Slot.ToName(), Slot.GetCategoryIcon().ToFlag()) : new ResourceNode.UiData(null, ChangedItemIconFlag.Unknown); } diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 38f6fe97..246a4508 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -80,12 +80,13 @@ public class ResourceTree { ModelType.Human => i switch { - < 10 => globalContext.CreateContext(model, i, i.ToEquipSlot(), equipment[(int)i]), - 16 or 17 => globalContext.CreateContext(model, i, EquipSlot.Head, equipment[(int)(i - 6)]), - _ => globalContext.CreateContext(model, i), + < 10 => globalContext.CreateContext(model, i, i.ToEquipSlot().ToEquipType(), equipment[(int)i]), + 16 => globalContext.CreateContext(model, i, FullEquipType.Glasses, equipment[10]), + 17 => globalContext.CreateContext(model, i, FullEquipType.Unknown, equipment[11]), + _ => globalContext.CreateContext(model, i), }, _ => i < equipment.Length - ? globalContext.CreateContext(model, i, i.ToEquipSlot(), equipment[(int)i]) + ? globalContext.CreateContext(model, i, i.ToEquipSlot().ToEquipType(), equipment[(int)i]) : globalContext.CreateContext(model, i), }; @@ -133,7 +134,7 @@ public class ResourceTree var weapon = (Weapon*)subObject; // This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it. - var slot = weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand; + var slot = weaponIndex > 0 ? FullEquipType.UnknownOffhand : FullEquipType.UnknownMainhand; var equipment = new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, new StainIds(weapon->Stain0, weapon->Stain1)); var weaponType = weapon->SecondaryId; diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs index 361094c4..3aff2ac9 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs @@ -190,52 +190,6 @@ public class ResourceTreeViewer var frameHeight = ImGui.GetFrameHeight(); var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f; - bool MatchesFilter(ResourceNode node, ChangedItemIconFlag filterIcon) - { - if (!_typeFilter.HasFlag(filterIcon)) - return false; - - if (_nodeFilter.Length == 0) - return true; - - return node.Name != null && node.Name.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) - || node.FullPath.FullName.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) - || node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) - || Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)); - } - - NodeVisibility CalculateNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemIconFlag parentFilterIcon) - { - if (node.Internal && !debugMode) - return NodeVisibility.Hidden; - - var filterIcon = node.IconFlag != 0 ? node.IconFlag : parentFilterIcon; - if (MatchesFilter(node, filterIcon)) - return NodeVisibility.Visible; - - foreach (var child in node.Children) - { - if (GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden) - return NodeVisibility.DescendentsOnly; - } - - return NodeVisibility.Hidden; - } - - NodeVisibility GetNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemIconFlag parentFilterIcon) - { - if (!_filterCache.TryGetValue(nodePathHash, out var visibility)) - { - visibility = CalculateNodeVisibility(nodePathHash, node, parentFilterIcon); - _filterCache.Add(nodePathHash, visibility); - } - - return visibility; - } - - string GetAdditionalDataSuffix(CiByteString data) - => !debugMode || data.IsEmpty ? string.Empty : $"\n\nAdditional Data: {data}"; - foreach (var (resourceNode, index) in resourceNodes.WithIndex()) { var nodePathHash = unchecked(pathHash + resourceNode.ResourceHandle); @@ -346,6 +300,54 @@ public class ResourceTreeViewer if (unfolded) DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31), filterIcon); } + + return; + + string GetAdditionalDataSuffix(CiByteString data) + => !debugMode || data.IsEmpty ? string.Empty : $"\n\nAdditional Data: {data}"; + + NodeVisibility GetNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemIconFlag parentFilterIcon) + { + if (!_filterCache.TryGetValue(nodePathHash, out var visibility)) + { + visibility = CalculateNodeVisibility(nodePathHash, node, parentFilterIcon); + _filterCache.Add(nodePathHash, visibility); + } + + return visibility; + } + + NodeVisibility CalculateNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemIconFlag parentFilterIcon) + { + if (node.Internal && !debugMode) + return NodeVisibility.Hidden; + + var filterIcon = node.IconFlag != 0 ? node.IconFlag : parentFilterIcon; + if (MatchesFilter(node, filterIcon)) + return NodeVisibility.Visible; + + foreach (var child in node.Children) + { + if (GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden) + return NodeVisibility.DescendentsOnly; + } + + return NodeVisibility.Hidden; + } + + bool MatchesFilter(ResourceNode node, ChangedItemIconFlag filterIcon) + { + if (!_typeFilter.HasFlag(filterIcon)) + return false; + + if (_nodeFilter.Length == 0) + return true; + + return node.Name != null && node.Name.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) + || node.FullPath.FullName.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) + || node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) + || Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)); + } } [Flags] From db2ce1328ff058548ed159b6ac5908f43a6f2045 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 11 Oct 2024 18:19:12 +0200 Subject: [PATCH 63/73] Enable VFX for the glasses slot. --- Penumbra.GameData | 2 +- .../Hooks/Resources/ResolvePathHooksBase.cs | 61 ++++++++++++++----- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 61e06785..2f6acca6 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 61e067857c2cf62bf8426ff6b305e37990f7767a +Subproject commit 2f6acca678b71203763ac4404c3f054747c14f75 diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index a31dee4c..d55caf34 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -6,6 +6,7 @@ using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; +using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler; namespace Penumbra.Interop.Hooks.Resources; @@ -223,6 +224,12 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable [FieldOffset(2)] public Variant Variant; + [FieldOffset(8)] + public PrimaryId BonusModel; + + [FieldOffset(10)] + public Variant BonusVariant; + [FieldOffset(20)] public ushort VfxId; @@ -232,26 +239,50 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam) { - if (slotIndex is <= 4 or >= 10) - return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + switch (slotIndex) + { + case <= 4: return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + case <= 10: + { + // Enable vfxs for accessories + var changedEquipData = (ChangedEquipData*)((Human*)drawObject)->ChangedEquipData; + if (changedEquipData == null) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - var changedEquipData = (ChangedEquipData*)((Human*)drawObject)->ChangedEquipData; - // Enable vfxs for accessories - if (changedEquipData == null) - return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + ref var slot = ref changedEquipData[slotIndex]; - ref var slot = ref changedEquipData[slotIndex]; + if (slot.Model == 0 || slot.Variant == 0 || slot.VfxId == 0) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - if (slot.Model == 0 || slot.Variant == 0 || slot.VfxId == 0) - return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + if (!Utf8.TryWrite(new Span((void*)pathBuffer, (int)pathBufferSize), + $"chara/accessory/a{slot.Model.Id:D4}/vfx/eff/va{slot.VfxId:D4}.avfx\0", + out _)) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - if (!Utf8.TryWrite(new Span((void*)pathBuffer, (int)pathBufferSize), - $"chara/accessory/a{slot.Model.Id:D4}/vfx/eff/va{slot.VfxId:D4}.avfx\0", - out _)) - return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + *(ulong*)unkOutParam = 4; + return ResolvePath(drawObject, pathBuffer); + } + case 16: + { + // Enable vfxs for glasses + var changedEquipData = (ChangedEquipData*)((Human*)drawObject)->ChangedEquipData; + if (changedEquipData == null) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); - *(ulong*)unkOutParam = 4; - return ResolvePath(drawObject, pathBuffer); + ref var slot = ref changedEquipData[slotIndex - 6]; + + if (slot.BonusModel == 0 || slot.BonusVariant == 0 || slot.VfxId == 0) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + if (!Utf8.TryWrite(new Span((void*)pathBuffer, (int)pathBufferSize), + $"chara/equipment/e{slot.BonusModel.Id:D4}/vfx/eff/ve{slot.VfxId:D4}.avfx\0", + out _)) + return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + + *(ulong*)unkOutParam = 4; + return ResolvePath(drawObject, pathBuffer); + } + default: return ResolveVfx(drawObject, pathBuffer, pathBufferSize, slotIndex, unkOutParam); + } } private nint VFunc81(nint drawObject, int estType, nint unk) From 97b310ca3ffa9397e722a429b8fc2665c4728e37 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 12 Oct 2024 15:13:06 +0200 Subject: [PATCH 64/73] Fix issue with meta file not being saved synchronously on creation. --- Penumbra/Mods/Manager/ModDataEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index 933620d9..162f823d 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -37,7 +37,7 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic mod.Description = description ?? mod.Description; mod.Version = version ?? mod.Version; mod.Website = website ?? mod.Website; - saveService.ImmediateSave(new ModMeta(mod)); + saveService.ImmediateSaveSync(new ModMeta(mod)); } public ModDataChangeType LoadLocalData(Mod mod) From e646b48afaa58db4d1ac6c19fc7661cc8bbdbcc8 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 12 Oct 2024 15:13:22 +0200 Subject: [PATCH 65/73] Add swaps to and from Glasses. --- Penumbra.GameData | 2 +- Penumbra/Mods/ItemSwap/EquipmentSwap.cs | 77 ++++++------ Penumbra/UI/AdvancedWindow/ItemSwapTab.cs | 135 ++++++++++++++++------ 3 files changed, 138 insertions(+), 76 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 2f6acca6..63cbf824 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 2f6acca678b71203763ac4404c3f054747c14f75 +Subproject commit 63cbf824178b5b1f91fd9edc22a6c2bbc2e1cd23 diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 1a2f2798..c7e43a26 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -32,26 +32,26 @@ public static class EquipmentSwap EquipSlot slotFrom, EquipItem itemFrom, EquipSlot slotTo, EquipItem itemTo) { LookupItem(itemFrom, out var actualSlotFrom, out var idFrom, out var variantFrom); - LookupItem(itemTo, out var actualSlotTo, out var idTo, out var variantTo); + LookupItem(itemTo, out var actualSlotTo, out var idTo, out var variantTo); if (actualSlotFrom != slotFrom.ToSlot() || actualSlotTo != slotTo.ToSlot()) throw new ItemSwap.InvalidItemTypeException(); var (imcFileFrom, variants, affectedItems) = GetVariants(manager, identifier, slotFrom, idFrom, idTo, variantFrom); var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo); - var imcFileTo = new ImcFile(manager, imcIdentifierTo); + var imcFileTo = new ImcFile(manager, imcIdentifierTo); var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry) ? entry : imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant); var mtrlVariantTo = imcEntry.MaterialId; - var skipFemale = false; - var skipMale = false; + var skipFemale = false; + var skipMale = false; foreach (var gr in Enum.GetValues()) { switch (gr.Split().Item1) { - case Gender.Male when skipMale: continue; - case Gender.Female when skipFemale: continue; - case Gender.MaleNpc when skipMale: continue; + case Gender.Male when skipMale: continue; + case Gender.Female when skipFemale: continue; + case Gender.MaleNpc when skipMale: continue; case Gender.FemaleNpc when skipFemale: continue; } @@ -94,7 +94,7 @@ public static class EquipmentSwap { // Check actual ids, variants and slots. We only support using the same slot. LookupItem(itemFrom, out var slotFrom, out var idFrom, out var variantFrom); - LookupItem(itemTo, out var slotTo, out var idTo, out var variantTo); + LookupItem(itemTo, out var slotTo, out var idTo, out var variantTo); if (slotFrom != slotTo) throw new ItemSwap.InvalidItemTypeException(); @@ -111,7 +111,7 @@ public static class EquipmentSwap { (var imcFileFrom, var variants, affectedItems) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); var imcIdentifierTo = new ImcIdentifier(slotTo, idTo, variantTo); - var imcFileTo = new ImcFile(manager, imcIdentifierTo); + var imcFileTo = new ImcFile(manager, imcIdentifierTo); var imcEntry = manips.TryGetValue(imcIdentifierTo, out var entry) ? entry : imcFileTo.GetEntry(imcIdentifierTo.EquipSlot, imcIdentifierTo.Variant); @@ -122,18 +122,18 @@ public static class EquipmentSwap { EquipSlot.Head => EstType.Head, EquipSlot.Body => EstType.Body, - _ => (EstType)0, + _ => (EstType)0, }; var skipFemale = false; - var skipMale = false; + var skipMale = false; foreach (var gr in Enum.GetValues()) { switch (gr.Split().Item1) { - case Gender.Male when skipMale: continue; - case Gender.Female when skipFemale: continue; - case Gender.MaleNpc when skipMale: continue; + case Gender.Male when skipMale: continue; + case Gender.Female when skipFemale: continue; + case Gender.MaleNpc when skipMale: continue; case Gender.FemaleNpc when skipFemale: continue; } @@ -148,7 +148,7 @@ public static class EquipmentSwap swaps.Add(eqdp); var ownMdl = eqdp?.SwapToModdedEntry.Model ?? false; - var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl); + var est = ItemSwap.CreateEst(manager, redirections, manips, estType, gr, idFrom, idTo, ownMdl); if (est != null) swaps.Add(est); } @@ -176,7 +176,6 @@ public static class EquipmentSwap return affectedItems; } - public static MetaSwap? CreateEqdp(MetaFileManager manager, Func redirections, MetaDictionary manips, EquipSlot slot, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo) => CreateEqdp(manager, redirections, manips, slot, slot, gr, idFrom, idTo, mtrlTo); @@ -186,9 +185,9 @@ public static class EquipmentSwap PrimaryId idTo, byte mtrlTo) { var eqdpFromIdentifier = new EqdpIdentifier(idFrom, slotFrom, gr); - var eqdpToIdentifier = new EqdpIdentifier(idTo, slotTo, gr); - var eqdpFromDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpFromIdentifier), slotFrom); - var eqdpToDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpToIdentifier), slotTo); + var eqdpToIdentifier = new EqdpIdentifier(idTo, slotTo, gr); + var eqdpFromDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpFromIdentifier), slotFrom); + var eqdpToDefault = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(manager, eqdpToIdentifier), slotTo); var meta = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, eqdpFromIdentifier, eqdpFromDefault, eqdpToIdentifier, eqdpToDefault); @@ -217,7 +216,7 @@ public static class EquipmentSwap ? GamePaths.Accessory.Mdl.Path(idFrom, gr, slotFrom) : GamePaths.Equipment.Mdl.Path(idFrom, gr, slotFrom); var mdlPathTo = slotTo.IsAccessory() ? GamePaths.Accessory.Mdl.Path(idTo, gr, slotTo) : GamePaths.Equipment.Mdl.Path(idTo, gr, slotTo); - var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo); + var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo); foreach (ref var fileName in mdl.AsMdl()!.Materials.AsSpan()) { @@ -242,13 +241,13 @@ public static class EquipmentSwap private static (ImcFile, Variant[], EquipItem[]) GetVariants(MetaFileManager manager, ObjectIdentification identifier, EquipSlot slotFrom, PrimaryId idFrom, PrimaryId idTo, Variant variantFrom) { - var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom); - var imc = new ImcFile(manager, ident); + var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom); + var imc = new ImcFile(manager, ident); EquipItem[] items; - Variant[] variants; + Variant[] variants; if (idFrom == idTo) { - items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToArray(); + items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToArray(); variants = [variantFrom]; } else @@ -271,9 +270,9 @@ public static class EquipmentSwap return null; var manipFromIdentifier = new GmpIdentifier(idFrom); - var manipToIdentifier = new GmpIdentifier(idTo); - var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier); - var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier); + var manipToIdentifier = new GmpIdentifier(idTo); + var manipFromDefault = ExpandedGmpFile.GetDefault(manager, manipFromIdentifier); + var manipToDefault = ExpandedGmpFile.GetDefault(manager, manipToIdentifier); return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); } @@ -288,9 +287,9 @@ public static class EquipmentSwap Variant variantFrom, Variant variantTo, ImcFile imcFileFrom, ImcFile imcFileTo) { var manipFromIdentifier = new ImcIdentifier(slotFrom, idFrom, variantFrom); - var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo); - var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); - var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); + var manipToIdentifier = new ImcIdentifier(slotTo, idTo, variantTo); + var manipFromDefault = imcFileFrom.GetEntry(ImcFile.PartIndex(slotFrom), variantFrom); + var manipToDefault = imcFileTo.GetEntry(ImcFile.PartIndex(slotTo), variantTo); var imc = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); @@ -329,7 +328,7 @@ public static class EquipmentSwap var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom, vfxId); vfxPathFrom = ItemSwap.ReplaceType(vfxPathFrom, slotFrom, slotTo, idFrom); var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId); - var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); + var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan()) { @@ -347,9 +346,9 @@ public static class EquipmentSwap return null; var manipFromIdentifier = new EqpIdentifier(idFrom, slot); - var manipToIdentifier = new EqpIdentifier(idTo, slot); - var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot); - var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot); + var manipToIdentifier = new EqpIdentifier(idTo, slot); + var manipFromDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idFrom), slot); + var manipToDefault = new EqpEntryInternal(ExpandedEqpFile.GetDefault(manager, idTo), slot); return new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, manipFromDefault, manipToIdentifier, manipToDefault); } @@ -381,7 +380,7 @@ public static class EquipmentSwap if (newFileName != fileName) { - fileName = newFileName; + fileName = newFileName; dataWasChanged = true; } @@ -406,13 +405,13 @@ public static class EquipmentSwap EquipSlot slotTo, PrimaryId idFrom, PrimaryId idTo, ref MtrlFile.Texture texture, ref bool dataWasChanged) { var addedDashes = GamePaths.Tex.HandleDx11Path(texture, out var path); - var newPath = ItemSwap.ReplaceAnyId(path, prefix, idFrom); + var newPath = ItemSwap.ReplaceAnyId(path, prefix, idFrom); newPath = ItemSwap.ReplaceSlot(newPath, slotTo, slotFrom, slotTo != slotFrom); newPath = ItemSwap.ReplaceType(newPath, slotFrom, slotTo, idFrom); newPath = ItemSwap.AddSuffix(newPath, ".tex", $"_{Path.GetFileName(texture.Path).GetStableHashCode():x8}"); if (newPath != path) { - texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath; + texture.Path = addedDashes ? newPath.Replace("--", string.Empty) : newPath; dataWasChanged = true; } @@ -430,8 +429,8 @@ public static class EquipmentSwap PrimaryId idFrom, ref string filePath, ref bool dataWasChanged) { var oldPath = filePath; - filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}"); - filePath = ItemSwap.ReplaceType(filePath, slotFrom, slotTo, idFrom); + filePath = ItemSwap.AddSuffix(filePath, ".atex", $"_{Path.GetFileName(filePath).GetStableHashCode():x8}"); + filePath = ItemSwap.ReplaceType(filePath, slotFrom, slotTo, idFrom); dataWasChanged = true; return FileSwap.CreateSwap(manager, ResourceType.Atex, redirections, filePath, oldPath, oldPath); diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index 6ed1b55d..3f7f2f6c 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -48,15 +48,16 @@ public class ItemSwapTab : IDisposable, ITab, IUiService _selectors = new Dictionary { // @formatter:off - [SwapType.Hat] = (new ItemSelector(itemService, selector, FullEquipType.Head), new ItemSelector(itemService, null, FullEquipType.Head), "Take this Hat", "and put it on this one" ), - [SwapType.Top] = (new ItemSelector(itemService, selector, FullEquipType.Body), new ItemSelector(itemService, null, FullEquipType.Body), "Take this Top", "and put it on this one" ), - [SwapType.Gloves] = (new ItemSelector(itemService, selector, FullEquipType.Hands), new ItemSelector(itemService, null, FullEquipType.Hands), "Take these Gloves", "and put them on these" ), - [SwapType.Pants] = (new ItemSelector(itemService, selector, FullEquipType.Legs), new ItemSelector(itemService, null, FullEquipType.Legs), "Take these Pants", "and put them on these" ), - [SwapType.Shoes] = (new ItemSelector(itemService, selector, FullEquipType.Feet), new ItemSelector(itemService, null, FullEquipType.Feet), "Take these Shoes", "and put them on these" ), - [SwapType.Earrings] = (new ItemSelector(itemService, selector, FullEquipType.Ears), new ItemSelector(itemService, null, FullEquipType.Ears), "Take these Earrings", "and put them on these" ), - [SwapType.Necklace] = (new ItemSelector(itemService, selector, FullEquipType.Neck), new ItemSelector(itemService, null, FullEquipType.Neck), "Take this Necklace", "and put it on this one" ), - [SwapType.Bracelet] = (new ItemSelector(itemService, selector, FullEquipType.Wrists), new ItemSelector(itemService, null, FullEquipType.Wrists), "Take these Bracelets", "and put them on these" ), - [SwapType.Ring] = (new ItemSelector(itemService, selector, FullEquipType.Finger), new ItemSelector(itemService, null, FullEquipType.Finger), "Take this Ring", "and put it on this one" ), + [SwapType.Hat] = (new ItemSelector(itemService, selector, FullEquipType.Head), new ItemSelector(itemService, null, FullEquipType.Head), "Take this Hat", "and put it on this one" ), + [SwapType.Top] = (new ItemSelector(itemService, selector, FullEquipType.Body), new ItemSelector(itemService, null, FullEquipType.Body), "Take this Top", "and put it on this one" ), + [SwapType.Gloves] = (new ItemSelector(itemService, selector, FullEquipType.Hands), new ItemSelector(itemService, null, FullEquipType.Hands), "Take these Gloves", "and put them on these" ), + [SwapType.Pants] = (new ItemSelector(itemService, selector, FullEquipType.Legs), new ItemSelector(itemService, null, FullEquipType.Legs), "Take these Pants", "and put them on these" ), + [SwapType.Shoes] = (new ItemSelector(itemService, selector, FullEquipType.Feet), new ItemSelector(itemService, null, FullEquipType.Feet), "Take these Shoes", "and put them on these" ), + [SwapType.Earrings] = (new ItemSelector(itemService, selector, FullEquipType.Ears), new ItemSelector(itemService, null, FullEquipType.Ears), "Take these Earrings", "and put them on these" ), + [SwapType.Necklace] = (new ItemSelector(itemService, selector, FullEquipType.Neck), new ItemSelector(itemService, null, FullEquipType.Neck), "Take this Necklace", "and put it on this one" ), + [SwapType.Bracelet] = (new ItemSelector(itemService, selector, FullEquipType.Wrists), new ItemSelector(itemService, null, FullEquipType.Wrists), "Take these Bracelets", "and put them on these" ), + [SwapType.Ring] = (new ItemSelector(itemService, selector, FullEquipType.Finger), new ItemSelector(itemService, null, FullEquipType.Finger), "Take this Ring", "and put it on this one" ), + [SwapType.Glasses] = (new ItemSelector(itemService, selector, FullEquipType.Glasses), new ItemSelector(itemService, null, FullEquipType.Glasses), "Take these Glasses", "and put them on these" ), // @formatter:on }; @@ -129,6 +130,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService Ears, Tail, Weapon, + Glasses, } private class ItemSelector(ItemData data, ModFileSystemSelector? selector, FullEquipType type) @@ -158,14 +160,14 @@ public class ItemSwapTab : IDisposable, ITab, IUiService private ModSettings? _modSettings; private bool _dirty; - private SwapType _lastTab = SwapType.Hair; - private Gender _currentGender = Gender.Male; - private ModelRace _currentRace = ModelRace.Midlander; - private int _targetId; - private int _sourceId; - private Exception? _loadException; - private EquipSlot _slotFrom = EquipSlot.Head; - private EquipSlot _slotTo = EquipSlot.Ears; + private SwapType _lastTab = SwapType.Hair; + private Gender _currentGender = Gender.Male; + private ModelRace _currentRace = ModelRace.Midlander; + private int _targetId; + private int _sourceId; + private Exception? _loadException; + private BetweenSlotTypes _slotFrom = BetweenSlotTypes.Hat; + private BetweenSlotTypes _slotTo = BetweenSlotTypes.Earrings; private string _newModName = string.Empty; private string _newGroupName = "Swaps"; @@ -200,18 +202,19 @@ public class ItemSwapTab : IDisposable, ITab, IUiService case SwapType.Necklace: case SwapType.Bracelet: case SwapType.Ring: + case SwapType.Glasses: var values = _selectors[_lastTab]; if (values.Source.CurrentSelection.Item.Type != FullEquipType.Unknown && values.Target.CurrentSelection.Item.Type != FullEquipType.Unknown) _affectedItems = _swapData.LoadEquipment(values.Target.CurrentSelection.Item, values.Source.CurrentSelection.Item, _useCurrentCollection ? _collectionManager.Active.Current : null, _useRightRing, _useLeftRing); - break; case SwapType.BetweenSlots: var (_, _, selectorFrom) = GetAccessorySelector(_slotFrom, true); var (_, _, selectorTo) = GetAccessorySelector(_slotTo, false); if (selectorFrom.CurrentSelection.Item.Valid && selectorTo.CurrentSelection.Item.Valid) - _affectedItems = _swapData.LoadTypeSwap(_slotTo, selectorTo.CurrentSelection.Item, _slotFrom, selectorFrom.CurrentSelection.Item, + _affectedItems = _swapData.LoadTypeSwap(ToEquipSlot(_slotTo), selectorTo.CurrentSelection.Item, ToEquipSlot(_slotFrom), + selectorFrom.CurrentSelection.Item, _useCurrentCollection ? _collectionManager.Active.Current : null); break; case SwapType.Hair when _targetId > 0 && _sourceId > 0: @@ -264,7 +267,23 @@ public class ItemSwapTab : IDisposable, ITab, IUiService } private string CreateDescription() - => $"Created by swapping {_lastTab} {_sourceId} onto {_lastTab} {_targetId} for {_currentRace.ToName()} {_currentGender.ToName()}s in {_mod!.Name}."; + { + switch (_lastTab) + { + case SwapType.Ears: + case SwapType.Face: + case SwapType.Hair: + case SwapType.Tail: + return + $"Created by swapping {_lastTab} {_sourceId} onto {_lastTab} {_targetId} for {_currentRace.ToName()} {_currentGender.ToName()}s in {_mod!.Name}."; + case SwapType.BetweenSlots: + return + $"Created by swapping {GetAccessorySelector(_slotFrom, true).Item3.CurrentSelection.Item.Name} onto {GetAccessorySelector(_slotTo, false).Item3.CurrentSelection.Item.Name} in {_mod!.Name}."; + default: + return + $"Created by swapping {_selectors[_lastTab].Source.CurrentSelection.Item.Name} onto {_selectors[_lastTab].Target.CurrentSelection.Item.Name} in {_mod!.Name}."; + } + } private void UpdateOption() { @@ -416,6 +435,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService DrawEquipmentSwap(SwapType.Necklace); DrawEquipmentSwap(SwapType.Bracelet); DrawEquipmentSwap(SwapType.Ring); + DrawEquipmentSwap(SwapType.Glasses); DrawAccessorySwap(); DrawHairSwap(); //DrawFaceSwap(); @@ -454,23 +474,24 @@ public class ItemSwapTab : IDisposable, ITab, IUiService ImGui.TableNextColumn(); ImGui.SetNextItemWidth(100 * UiHelpers.Scale); - using (var combo = ImRaii.Combo("##fromType", _slotFrom is EquipSlot.Head ? "Hat" : _slotFrom.ToName())) + using (var combo = ImRaii.Combo("##fromType", ToName(_slotFrom))) { if (combo) - foreach (var slot in EquipSlotExtensions.AccessorySlots.Prepend(EquipSlot.Head)) + foreach (var slot in Enum.GetValues()) { - if (!ImGui.Selectable(slot is EquipSlot.Head ? "Hat" : slot.ToName(), slot == _slotFrom) || slot == _slotFrom) + if (!ImGui.Selectable(ToName(slot), slot == _slotFrom) || slot == _slotFrom) continue; _dirty = true; _slotFrom = slot; if (slot == _slotTo) - _slotTo = EquipSlotExtensions.AccessorySlots.First(s => slot != s); + _slotTo = AvailableToTypes.First(s => slot != s); } } ImGui.TableNextColumn(); - _dirty |= selector.Draw("##itemSource", selector.CurrentSelection.Item.Name ?? string.Empty, string.Empty, InputWidth * 2 * UiHelpers.Scale, + _dirty |= selector.Draw("##itemSource", selector.CurrentSelection.Item.Name ?? string.Empty, string.Empty, + InputWidth * 2 * UiHelpers.Scale, ImGui.GetTextLineHeightWithSpacing()); (article1, _, selector) = GetAccessorySelector(_slotTo, false); @@ -480,12 +501,12 @@ public class ItemSwapTab : IDisposable, ITab, IUiService ImGui.TableNextColumn(); ImGui.SetNextItemWidth(100 * UiHelpers.Scale); - using (var combo = ImRaii.Combo("##toType", _slotTo.ToName())) + using (var combo = ImRaii.Combo("##toType", ToName(_slotTo))) { if (combo) - foreach (var slot in EquipSlotExtensions.AccessorySlots.Where(s => s != _slotFrom)) + foreach (var slot in AvailableToTypes.Where(t => t != _slotFrom)) { - if (!ImGui.Selectable(slot.ToName(), slot == _slotTo) || slot == _slotTo) + if (!ImGui.Selectable(ToName(slot), slot == _slotTo) || slot == _slotTo) continue; _dirty = true; @@ -508,17 +529,18 @@ public class ItemSwapTab : IDisposable, ITab, IUiService .Select(i => i.Name))); } - private (string, string, ItemSelector) GetAccessorySelector(EquipSlot slot, bool source) + private (string, string, ItemSelector) GetAccessorySelector(BetweenSlotTypes slot, bool source) { var (type, article1, article2) = slot switch { - EquipSlot.Head => (SwapType.Hat, "this", "it"), - EquipSlot.Ears => (SwapType.Earrings, "these", "them"), - EquipSlot.Neck => (SwapType.Necklace, "this", "it"), - EquipSlot.Wrists => (SwapType.Bracelet, "these", "them"), - EquipSlot.RFinger => (SwapType.Ring, "this", "it"), - EquipSlot.LFinger => (SwapType.Ring, "this", "it"), - _ => (SwapType.Ring, "this", "it"), + BetweenSlotTypes.Hat => (SwapType.Hat, "this", "it"), + BetweenSlotTypes.Earrings => (SwapType.Earrings, "these", "them"), + BetweenSlotTypes.Necklace => (SwapType.Necklace, "this", "it"), + BetweenSlotTypes.Bracelets => (SwapType.Bracelet, "these", "them"), + BetweenSlotTypes.RightRing => (SwapType.Ring, "this", "it"), + BetweenSlotTypes.LeftRing => (SwapType.Ring, "this", "it"), + BetweenSlotTypes.Glasses => (SwapType.Glasses, "these", "them"), + _ => (SwapType.Ring, "this", "it"), }; var (itemSelector, target, _, _) = _selectors[type]; return (article1, article2, source ? itemSelector : target); @@ -689,6 +711,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService SwapType.Necklace => "One of the selected necklaces does not seem to exist.", SwapType.Bracelet => "One of the selected bracelets does not seem to exist.", SwapType.Ring => "One of the selected rings does not seem to exist.", + SwapType.Glasses => "One of the selected glasses does not seem to exist.", SwapType.Hair => "One of the selected hairstyles does not seem to exist for this gender and race combo.", SwapType.Face => "One of the selected faces does not seem to exist for this gender and race combo.", SwapType.Ears => "One of the selected ear types does not seem to exist for this gender and race combo.", @@ -746,4 +769,44 @@ public class ItemSwapTab : IDisposable, ITab, IUiService UpdateOption(); _dirty = true; } + + private enum BetweenSlotTypes + { + Hat, + Earrings, + Necklace, + Bracelets, + RightRing, + LeftRing, + Glasses, + } + + private static EquipSlot ToEquipSlot(BetweenSlotTypes type) + => type switch + { + BetweenSlotTypes.Hat => EquipSlot.Head, + BetweenSlotTypes.Earrings => EquipSlot.Ears, + BetweenSlotTypes.Necklace => EquipSlot.Neck, + BetweenSlotTypes.Bracelets => EquipSlot.Wrists, + BetweenSlotTypes.RightRing => EquipSlot.RFinger, + BetweenSlotTypes.LeftRing => EquipSlot.LFinger, + BetweenSlotTypes.Glasses => BonusItemFlag.Glasses.ToEquipSlot(), + _ => EquipSlot.Unknown, + }; + + private static string ToName(BetweenSlotTypes type) + => type switch + { + BetweenSlotTypes.Hat => "Hat", + BetweenSlotTypes.Earrings => "Earrings", + BetweenSlotTypes.Necklace => "Necklace", + BetweenSlotTypes.Bracelets => "Bracelets", + BetweenSlotTypes.RightRing => "Right Ring", + BetweenSlotTypes.LeftRing => "Left Ring", + BetweenSlotTypes.Glasses => "Glasses", + _ => "Unknown", + }; + + private static readonly IReadOnlyList AvailableToTypes = + Enum.GetValues().Where(s => s is not BetweenSlotTypes.Hat).ToArray(); } From a54e45f9c3b59e0532d422230a23c0a2748172b8 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sat, 12 Oct 2024 13:15:18 +0000 Subject: [PATCH 66/73] [CI] Updating repo.json for testing_1.2.1.8 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 0d9e071f..4e8b1ed8 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.7", + "TestingAssemblyVersion": "1.2.1.8", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.7/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.8/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 50db83146adab19207e9b867ca8768ae1d7cc06f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 13 Oct 2024 13:55:01 +0200 Subject: [PATCH 67/73] Maybe fix left finger resource nodes. --- .../ResolveContext.PathResolution.cs | 2 +- .../Interop/ResourceTree/ResolveContext.cs | 4 +-- Penumbra/Interop/ResourceTree/ResourceNode.cs | 32 +++++++++---------- Penumbra/Interop/ResourceTree/ResourceTree.cs | 10 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index c554d97a..79f97881 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -37,7 +37,7 @@ internal partial record ResolveContext { var path = IsEquipmentSlot(SlotIndex) ? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot.ToSlot()) - : GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot.ToSlot()); + : GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot()); return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index 207551e7..54612070 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -340,9 +340,9 @@ internal unsafe partial record ResolveContext( internal ResourceNode.UiData GuessModelUiData(Utf8GamePath gamePath) { - var path = gamePath.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries); + var path = gamePath.Path.Split((byte)'/'); // Weapons intentionally left out. - var isEquipment = SafeGet(path, 0) == "chara" && SafeGet(path, 1) is "accessory" or "equipment"; + var isEquipment = path.Count >= 2 && path[0].Span.SequenceEqual("chara"u8) && (path[1].Span.SequenceEqual("accessory"u8) || path[1].Span.SequenceEqual("equipment"u8)); if (isEquipment) foreach (var item in Global.Identifier.Identify(Equipment.Set, 0, Equipment.Variant, Slot.ToSlot())) { diff --git a/Penumbra/Interop/ResourceTree/ResourceNode.cs b/Penumbra/Interop/ResourceTree/ResourceNode.cs index 85d12ce7..6c3e1ebe 100644 --- a/Penumbra/Interop/ResourceTree/ResourceNode.cs +++ b/Penumbra/Interop/ResourceTree/ResourceNode.cs @@ -7,20 +7,20 @@ namespace Penumbra.Interop.ResourceTree; public class ResourceNode : ICloneable { - public string? Name; - public string? FallbackName; - public ChangedItemIconFlag IconFlag; - public readonly ResourceType Type; - public readonly nint ObjectAddress; - public readonly nint ResourceHandle; - public Utf8GamePath[] PossibleGamePaths; - public FullPath FullPath; - public string? ModName; - public string? ModRelativePath; - public CiByteString AdditionalData; - public readonly ulong Length; - public readonly List Children; - internal ResolveContext? ResolveContext; + public string? Name; + public string? FallbackName; + public ChangedItemIconFlag IconFlag; + public readonly ResourceType Type; + public readonly nint ObjectAddress; + public readonly nint ResourceHandle; + public Utf8GamePath[] PossibleGamePaths; + public FullPath FullPath; + public string? ModName; + public string? ModRelativePath; + public CiByteString AdditionalData; + public readonly ulong Length; + public readonly List Children; + internal ResolveContext? ResolveContext; public Utf8GamePath GamePath { @@ -53,7 +53,7 @@ public class ResourceNode : ICloneable { Name = other.Name; FallbackName = other.FallbackName; - IconFlag = other.IconFlag; + IconFlag = other.IconFlag; Type = other.Type; ObjectAddress = other.ObjectAddress; ResourceHandle = other.ResourceHandle; @@ -82,7 +82,7 @@ public class ResourceNode : ICloneable public void SetUiData(UiData uiData) { - Name = uiData.Name; + Name = uiData.Name; IconFlag = uiData.IconFlag; } diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 246a4508..89e0c62b 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -81,8 +81,8 @@ public class ResourceTree ModelType.Human => i switch { < 10 => globalContext.CreateContext(model, i, i.ToEquipSlot().ToEquipType(), equipment[(int)i]), - 16 => globalContext.CreateContext(model, i, FullEquipType.Glasses, equipment[10]), - 17 => globalContext.CreateContext(model, i, FullEquipType.Unknown, equipment[11]), + 16 => globalContext.CreateContext(model, i, FullEquipType.Glasses, equipment[10]), + 17 => globalContext.CreateContext(model, i, FullEquipType.Unknown, equipment[11]), _ => globalContext.CreateContext(model, i), }, _ => i < equipment.Length @@ -185,7 +185,7 @@ public class ResourceTree { pbdNode = pbdNode.Clone(); pbdNode.FallbackName = "Racial Deformer"; - pbdNode.IconFlag = ChangedItemIconFlag.Customization; + pbdNode.IconFlag = ChangedItemIconFlag.Customization; } Nodes.Add(pbdNode); @@ -203,7 +203,7 @@ public class ResourceTree { decalNode = decalNode.Clone(); decalNode.FallbackName = "Face Decal"; - decalNode.IconFlag = ChangedItemIconFlag.Customization; + decalNode.IconFlag = ChangedItemIconFlag.Customization; } Nodes.Add(decalNode); @@ -220,7 +220,7 @@ public class ResourceTree { legacyDecalNode = legacyDecalNode.Clone(); legacyDecalNode.FallbackName = "Legacy Body Decal"; - legacyDecalNode.IconFlag = ChangedItemIconFlag.Customization; + legacyDecalNode.IconFlag = ChangedItemIconFlag.Customization; } Nodes.Add(legacyDecalNode); From 9bd1f86a1d31efe180cc5755852bc6f129e4a187 Mon Sep 17 00:00:00 2001 From: Actions User Date: Sun, 13 Oct 2024 11:57:07 +0000 Subject: [PATCH 68/73] [CI] Updating repo.json for testing_1.2.1.9 --- repo.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repo.json b/repo.json index 4e8b1ed8..2ead369a 100644 --- a/repo.json +++ b/repo.json @@ -6,7 +6,7 @@ "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.8", + "TestingAssemblyVersion": "1.2.1.9", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -19,7 +19,7 @@ "LoadRequiredState": 2, "LoadSync": true, "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.8/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.9/Penumbra.zip", "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } From 339d1f8caf00e6afe3fde06ea20685523020887d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sun, 13 Oct 2024 14:41:21 +0200 Subject: [PATCH 69/73] Update GameData --- Penumbra.GameData | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 63cbf824..554e28a3 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 63cbf824178b5b1f91fd9edc22a6c2bbc2e1cd23 +Subproject commit 554e28a3d1fca9394a20fd9856f6387e2a5e4a57 From 9ddb011545c3b94b7c4958eb47f674627f93bc29 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 18 Oct 2024 16:03:08 +0200 Subject: [PATCH 70/73] Fix issue with long mod titles in the merge mods tab. --- Penumbra/UI/AdvancedWindow/ModMergeTab.cs | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs index b5f0255c..bd62089f 100644 --- a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs @@ -3,6 +3,7 @@ using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; +using OtterGui.Text; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager; using Penumbra.Mods.SubMods; @@ -45,9 +46,30 @@ public class ModMergeTab(ModMerger modMerger) : IUiService private void DrawMergeInto(float size) { - using var bigGroup = ImRaii.Group(); + using var bigGroup = ImRaii.Group(); + var minComboSize = 300 * ImGuiHelpers.GlobalScale; + var textSize = ImUtf8.CalcTextSize($"Merge {modMerger.MergeFromMod!.Name} into ").X; + ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted($"Merge {modMerger.MergeFromMod!.Name} into "); + + using (ImRaii.Group()) + { + ImUtf8.Text("Merge "u8); + ImGui.SameLine(0, 0); + if (size - textSize < minComboSize) + { + ImUtf8.Text("selected mod"u8, ColorId.FolderLine.Value()); + ImUtf8.HoverTooltip(modMerger.MergeFromMod!.Name.Text); + } + else + { + ImUtf8.Text(modMerger.MergeFromMod!.Name.Text, ColorId.FolderLine.Value()); + } + + ImGui.SameLine(0, 0); + ImUtf8.Text(" into"u8); + } + ImGui.SameLine(); DrawCombo(size - ImGui.GetItemRectSize().X - ImGui.GetStyle().ItemSpacing.X); From 472d803141dab3baa9cf59bef4461986ad1a1f84 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 18 Oct 2024 16:24:30 +0200 Subject: [PATCH 71/73] 1.3.0.0 --- Penumbra/UI/Changelog.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Penumbra/UI/Changelog.cs b/Penumbra/UI/Changelog.cs index 41920d1c..48ac90d8 100644 --- a/Penumbra/UI/Changelog.cs +++ b/Penumbra/UI/Changelog.cs @@ -53,10 +53,42 @@ public class PenumbraChangelog : IUiService Add1_1_0_0(Changelog); Add1_1_1_0(Changelog); Add1_2_1_0(Changelog); + Add1_3_0_0(Changelog); } #region Changelogs + private static void Add1_3_0_0(Changelog log) + => log.NextVersion("Version 1.3.0.0") + + .RegisterHighlight("The textures tab in the advanced editing window can now import and export .tga files.") + .RegisterEntry("BC4 and BC6 textures can now also be imported.", 1) + .RegisterHighlight("Added item swapping from and to the Glasses slot.") + .RegisterEntry("Reworked quite a bit of things around face wear / bonus items. Please let me know if anything broke.", 1) + .RegisterEntry("The import date of a mod is now shown in the Edit Mod tab, and can be reset via button.") + .RegisterEntry("A button to open the file containing local mod data for a mod was also added.", 1) + .RegisterHighlight("IMC groups can now be configured to only apply the attribute flags for their entry, and take the other values from the default value.") + .RegisterEntry("This allows keeping the material index of every IMC entry of a group, while setting the attributes.", 1) + .RegisterHighlight("Model Import/Export was fixed and re-enabled (thanks ackwell and ramen).") + .RegisterHighlight("Added a hack to allow bonus items (face wear, glasses) to have VFX.") + .RegisterEntry("Also fixed the hack that allowed accessories to have VFX not working anymore.", 1) + .RegisterHighlight("Added rudimentary options to edit PBD files in the advanced editing window.") + .RegisterEntry("Preparing the advanced editing window for a mod now does not freeze the game until it is ready.") + .RegisterEntry("Meta Manipulations in the advanced editing window are now ordered and do not eat into performance as much when drawn.") + .RegisterEntry("Added a button to the advanced editing window to remove all default-valued meta manipulations from a mod") + .RegisterEntry("Default-valued manipulations will now also be removed on import from archives and .pmps, not just .ttmps, if not configured otherwise.", 1) + .RegisterEntry("Checkbox-based mod filters are now tri-state checkboxes instead of two disjoint checkboxes.") + .RegisterEntry("Paths from the resource logger can now be copied.") + .RegisterEntry("Silenced some redundant error logs when updating mods via Heliosphere.") + .RegisterEntry("Added 'Page' to imported mod data for TexTools interop. The value is not used in Penumbra, just persisted.") + .RegisterEntry("Updated all external dependencies.") + .RegisterEntry("Fixed issue with Demihuman IMC entries.") + .RegisterEntry("Fixed some off-by-one errors on the mod import window.") + .RegisterEntry("Fixed a race-condition concerning the first-time creation of mod-meta files.") + .RegisterEntry("Fixed an issue with long mod titles in the merge mods tab.") + .RegisterEntry("A bunch of other miscellaneous fixes."); + + private static void Add1_2_1_0(Changelog log) => log.NextVersion("Version 1.2.1.0") .RegisterHighlight("Penumbra is now released for Dawntrail!") From 71101ef553dd19f50678fae379a8f617bb5cd9fb Mon Sep 17 00:00:00 2001 From: Actions User Date: Fri, 18 Oct 2024 14:26:25 +0000 Subject: [PATCH 72/73] [CI] Updating repo.json for 1.3.0.0 --- repo.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/repo.json b/repo.json index 2ead369a..cba274c8 100644 --- a/repo.json +++ b/repo.json @@ -5,8 +5,8 @@ "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", - "AssemblyVersion": "1.2.1.1", - "TestingAssemblyVersion": "1.2.1.9", + "AssemblyVersion": "1.3.0.0", + "TestingAssemblyVersion": "1.3.0.0", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 10, @@ -18,9 +18,9 @@ "LoadPriority": 69420, "LoadRequiredState": 2, "LoadSync": true, - "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.2.1.9/Penumbra.zip", - "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.2.1.1/Penumbra.zip", + "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip", + "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } ] From 69971c12afd084192dbf40b1f45068d1b6d93916 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Sat, 19 Oct 2024 19:23:51 +0200 Subject: [PATCH 73/73] Fix EQP entries for earring hiding. --- Penumbra.GameData | 2 +- Penumbra/Collections/Cache/GlobalEqpCache.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 554e28a3..e9fc5930 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 554e28a3d1fca9394a20fd9856f6387e2a5e4a57 +Subproject commit e9fc5930a9c035c1e1e3c87ee9bcc4f05eb3015b diff --git a/Penumbra/Collections/Cache/GlobalEqpCache.cs b/Penumbra/Collections/Cache/GlobalEqpCache.cs index efcab109..60e782b5 100644 --- a/Penumbra/Collections/Cache/GlobalEqpCache.cs +++ b/Penumbra/Collections/Cache/GlobalEqpCache.cs @@ -40,7 +40,7 @@ public class GlobalEqpCache : ReadWriteDictionary, original |= EqpEntry.HeadShowHrothgarHat; if (_doNotHideEarrings.Contains(armor[5].Set)) - original |= EqpEntry.HeadShowEarrings | EqpEntry.HeadShowEarringsAura | EqpEntry.HeadShowEarringsHuman; + original |= EqpEntry.HeadShowEarringsHyurRoe | EqpEntry.HeadShowEarringsLalaElezen | EqpEntry.HeadShowEarringsMiqoHrothViera | EqpEntry.HeadShowEarringsAura; if (_doNotHideNecklace.Contains(armor[6].Set)) original |= EqpEntry.BodyShowNecklace | EqpEntry.HeadShowNecklace;