diff --git a/.editorconfig b/.editorconfig index f0328fd7..c645b573 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3576,18 +3576,6 @@ resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_ resharper_xaml_x_key_attribute_disallowed_highlighting=error resharper_xml_doc_comment_syntax_problem_highlighting=warning resharper_xunit_xunit_test_with_console_output_highlighting=warning -csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion -csharp_style_expression_bodied_methods = true:silent -csharp_style_prefer_tuple_swap = true:suggestion -csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion -csharp_style_prefer_utf8_string_literals = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_expression_bodied_constructors = true:silent -csharp_style_expression_bodied_operators = true:silent -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:silent -csharp_style_expression_bodied_properties = true:silent [*.{cshtml,htm,html,proto,razor}] indent_style=tab diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7901a653..1783c9a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '9.x.x' + dotnet-version: '8.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 377919b2..4799cbed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,12 +15,12 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '9.x.x' + dotnet-version: '8.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud run: | - Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip + Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev" - name: Build run: | diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml index 2bece720..549c967a 100644 --- a/.github/workflows/test_release.yml +++ b/.github/workflows/test_release.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: '9.x.x' + dotnet-version: '8.x.x' - name: Restore dependencies run: dotnet restore - name: Download Dalamud diff --git a/OtterGui b/OtterGui index a63f6735..215e0172 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 +Subproject commit 215e01722a319c70b271dd23a40d99edc3fc197e diff --git a/Penumbra.Api b/Penumbra.Api index 3d6cee1a..97e9f427 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 3d6cee1a11922ccd426f36060fd026bc1a698adf +Subproject commit 97e9f427406f82a59ddef764b44ecea654a51623 diff --git a/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs b/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs index 292be2ff..11dc52db 100644 --- a/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs +++ b/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Penumbra.CrashHandler.Buffers; diff --git a/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs b/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs index 89fea29d..a48fe846 100644 --- a/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs +++ b/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Penumbra.CrashHandler.Buffers; diff --git a/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs b/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs index e2ffcebe..a1b3de52 100644 --- a/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs +++ b/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs @@ -1,8 +1,5 @@ -using System; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.IO.MemoryMappedFiles; -using System.Linq; using System.Numerics; using System.Text; diff --git a/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs b/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs index e4ee66d0..ac507e7f 100644 --- a/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs +++ b/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; namespace Penumbra.CrashHandler.Buffers; diff --git a/Penumbra.CrashHandler/CrashData.cs b/Penumbra.CrashHandler/CrashData.cs index 55460548..dd75f46e 100644 --- a/Penumbra.CrashHandler/CrashData.cs +++ b/Penumbra.CrashHandler/CrashData.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Penumbra.CrashHandler.Buffers; namespace Penumbra.CrashHandler; diff --git a/Penumbra.CrashHandler/GameEventLogReader.cs b/Penumbra.CrashHandler/GameEventLogReader.cs index 8a7f53f8..1813a671 100644 --- a/Penumbra.CrashHandler/GameEventLogReader.cs +++ b/Penumbra.CrashHandler/GameEventLogReader.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; using Penumbra.CrashHandler.Buffers; namespace Penumbra.CrashHandler; diff --git a/Penumbra.CrashHandler/GameEventLogWriter.cs b/Penumbra.CrashHandler/GameEventLogWriter.cs index 915c59a2..e2c461f4 100644 --- a/Penumbra.CrashHandler/GameEventLogWriter.cs +++ b/Penumbra.CrashHandler/GameEventLogWriter.cs @@ -1,5 +1,4 @@ -using System; -using Penumbra.CrashHandler.Buffers; +using Penumbra.CrashHandler.Buffers; namespace Penumbra.CrashHandler; diff --git a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj b/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj index 1b1f0a28..c9f97fde 100644 --- a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj +++ b/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj @@ -1,6 +1,20 @@ - + + Exe + net8.0-windows + preview + enable + x64 + enable + true + false + + + + $(appdata)\XIVLauncher\addon\Hooks\dev\ + $(HOME)/.xlcore/dalamud/Hooks/dev/ + $(DALAMUD_HOME)/ @@ -11,8 +25,4 @@ embedded - - false - - diff --git a/Penumbra.CrashHandler/Program.cs b/Penumbra.CrashHandler/Program.cs index 38c176a6..3bc461f7 100644 --- a/Penumbra.CrashHandler/Program.cs +++ b/Penumbra.CrashHandler/Program.cs @@ -1,6 +1,4 @@ -using System; -using System.Diagnostics; -using System.IO; +using System.Diagnostics; using System.Text.Json; namespace Penumbra.CrashHandler; diff --git a/Penumbra.CrashHandler/packages.lock.json b/Penumbra.CrashHandler/packages.lock.json deleted file mode 100644 index 1d395083..00000000 --- a/Penumbra.CrashHandler/packages.lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": 1, - "dependencies": { - "net9.0-windows7.0": { - "DotNet.ReproducibleBuilds": { - "type": "Direct", - "requested": "[1.2.25, )", - "resolved": "1.2.25", - "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" - } - } - } -} \ No newline at end of file diff --git a/Penumbra.GameData b/Penumbra.GameData index d889f9ef..2b0c7f3b 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d889f9ef918514a46049725052d378b441915b00 +Subproject commit 2b0c7f3bee0bc2eb466540d2fac265804354493d diff --git a/Penumbra.String b/Penumbra.String index c8611a0c..dd83f972 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793 +Subproject commit dd83f97299ac33cfacb1064bde4f4d1f6a260936 diff --git a/Penumbra.sln b/Penumbra.sln index fbcd6080..94a04ef3 100644 --- a/Penumbra.sln +++ b/Penumbra.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\build.yml = .github\workflows\build.yml - Penumbra\Penumbra.json = Penumbra\Penumbra.json .github\workflows\release.yml = .github\workflows\release.yml repo.json = repo.json .github\workflows\test_release.yml = .github\workflows\test_release.yml @@ -25,74 +24,40 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{BFEA7504-1210-4F79-A7FE-BF03B6567E33}" - ProjectSection(SolutionItems) = preProject - schemas\default_mod.json = schemas\default_mod.json - schemas\group.json = schemas\group.json - schemas\local_mod_data-v3.json = schemas\local_mod_data-v3.json - schemas\mod_meta-v3.json = schemas\mod_meta-v3.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "structs", "structs", "{B03F276A-0572-4F62-AF86-EF62F6B80463}" - ProjectSection(SolutionItems) = preProject - schemas\structs\container.json = schemas\structs\container.json - schemas\structs\group_combining.json = schemas\structs\group_combining.json - schemas\structs\group_imc.json = schemas\structs\group_imc.json - schemas\structs\group_multi.json = schemas\structs\group_multi.json - schemas\structs\group_single.json = schemas\structs\group_single.json - schemas\structs\manipulation.json = schemas\structs\manipulation.json - schemas\structs\meta_atch.json = schemas\structs\meta_atch.json - schemas\structs\meta_atr.json = schemas\structs\meta_atr.json - schemas\structs\meta_enums.json = schemas\structs\meta_enums.json - schemas\structs\meta_eqdp.json = schemas\structs\meta_eqdp.json - schemas\structs\meta_eqp.json = schemas\structs\meta_eqp.json - schemas\structs\meta_est.json = schemas\structs\meta_est.json - schemas\structs\meta_geqp.json = schemas\structs\meta_geqp.json - schemas\structs\meta_gmp.json = schemas\structs\meta_gmp.json - schemas\structs\meta_imc.json = schemas\structs\meta_imc.json - schemas\structs\meta_rsp.json = schemas\structs\meta_rsp.json - schemas\structs\meta_shp.json = schemas\structs\meta_shp.json - schemas\structs\option.json = schemas\structs\option.json - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64 - {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64 - {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64 - {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64 - {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.ActiveCfg = Debug|x64 - {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.Build.0 = Debug|x64 - {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.ActiveCfg = Release|x64 - {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.Build.0 = Release|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.ActiveCfg = Debug|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.Build.0 = Debug|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.ActiveCfg = Release|x64 - {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.Build.0 = Release|x64 - {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.ActiveCfg = Debug|x64 - {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.Build.0 = Debug|x64 - {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.ActiveCfg = Release|x64 - {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.Build.0 = Release|x64 - {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.ActiveCfg = Debug|x64 - {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.Build.0 = Debug|x64 - {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.ActiveCfg = Release|x64 - {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.Build.0 = Release|x64 - {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.ActiveCfg = Debug|x64 - {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.Build.0 = Debug|x64 - {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.ActiveCfg = Release|x64 - {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.Build.0 = Release|x64 + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|Any CPU + {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.Build.0 = Release|Any CPU + {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|Any CPU.Build.0 = Release|Any CPU + {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.Build.0 = Release|Any CPU + {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU + {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {BFEA7504-1210-4F79-A7FE-BF03B6567E33} = {F89C9EAE-25C8-43BE-8108-5921E5A93502} - {B03F276A-0572-4F62-AF86-EF62F6B80463} = {BFEA7504-1210-4F79-A7FE-BF03B6567E33} - EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF} EndGlobalSection diff --git a/Penumbra/Api/Api/CollectionApi.cs b/Penumbra/Api/Api/CollectionApi.cs index c40feb12..04299187 100644 --- a/Penumbra/Api/Api/CollectionApi.cs +++ b/Penumbra/Api/Api/CollectionApi.cs @@ -2,14 +2,13 @@ using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; -using Penumbra.Mods; namespace Penumbra.Api.Api; public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : IPenumbraApiCollection, IApiService { public Dictionary GetCollections() - => collections.Storage.ToDictionary(c => c.Identity.Id, c => c.Identity.Name); + => collections.Storage.ToDictionary(c => c.Id, c => c.Name); public List<(Guid Id, string Name)> GetCollectionsByIdentifier(string identifier) { @@ -18,33 +17,17 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : var list = new List<(Guid Id, string Name)>(4); if (Guid.TryParse(identifier, out var guid) && collections.Storage.ById(guid, out var collection) && collection != ModCollection.Empty) - list.Add((collection.Identity.Id, collection.Identity.Name)); + list.Add((collection.Id, collection.Name)); else if (identifier.Length >= 8) - list.AddRange(collections.Storage.Where(c => c.Identity.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase)) - .Select(c => (c.Identity.Id, c.Identity.Name))); + list.AddRange(collections.Storage.Where(c => c.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase)) + .Select(c => (c.Id, c.Name))); list.AddRange(collections.Storage - .Where(c => string.Equals(c.Identity.Name, identifier, StringComparison.OrdinalIgnoreCase) - && !list.Contains((c.Identity.Id, c.Identity.Name))) - .Select(c => (c.Identity.Id, c.Identity.Name))); + .Where(c => string.Equals(c.Name, identifier, StringComparison.OrdinalIgnoreCase) && !list.Contains((c.Id, c.Name))) + .Select(c => (c.Id, c.Name))); return list; } - public Func CheckCurrentChangedItemFunc() - { - var weakRef = new WeakReference(collections); - return s => - { - if (!weakRef.TryGetTarget(out var c)) - throw new ObjectDisposedException("The underlying collection storage of this IPC container was disposed."); - - if (!c.Active.Current.ChangedItems.TryGetValue(s, out var d)) - return []; - - return d.Item1.Select(m => (m is Mod mod ? mod.Identifier : string.Empty, m.Name.Text)).ToArray(); - }; - } - public Dictionary GetChangedItemsForCollection(Guid collectionId) { try @@ -71,7 +54,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : return null; var collection = collections.Active.ByType((CollectionType)type); - return collection == null ? null : (collection.Identity.Id, collection.Identity.Name); + return collection == null ? null : (collection.Id, collection.Name); } internal (Guid Id, string Name)? GetCollection(byte type) @@ -81,18 +64,17 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : { var id = helpers.AssociatedIdentifier(gameObjectIdx); if (!id.IsValid) - return (false, false, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name)); + return (false, false, (collections.Active.Default.Id, collections.Active.Default.Name)); if (collections.Active.Individuals.TryGetValue(id, out var collection)) - return (true, true, (collection.Identity.Id, collection.Identity.Name)); + return (true, true, (collection.Id, collection.Name)); helpers.AssociatedCollection(gameObjectIdx, out collection); - return (true, false, (collection.Identity.Id, collection.Identity.Name)); + return (true, false, (collection.Id, collection.Name)); } public Guid[] GetCollectionByName(string name) - => collections.Storage.Where(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Identity.Id) - .ToArray(); + => collections.Storage.Where(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Id).ToArray(); public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollection(ApiCollectionType type, Guid? collectionId, bool allowCreateNew, bool allowDelete) @@ -101,7 +83,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : return (PenumbraApiEc.InvalidArgument, null); var oldCollection = collections.Active.ByType((CollectionType)type); - var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple?(); + var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple?(); if (collectionId == null) { if (old == null) @@ -124,7 +106,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : collections.Active.CreateSpecialCollection((CollectionType)type); } - else if (old.Value.Item1 == collection.Identity.Id) + else if (old.Value.Item1 == collection.Id) { return (PenumbraApiEc.NothingChanged, old); } @@ -138,10 +120,10 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : { var id = helpers.AssociatedIdentifier(gameObjectIdx); if (!id.IsValid) - return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name)); + return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Id, collections.Active.Default.Name)); var oldCollection = collections.Active.Individuals.TryGetValue(id, out var c) ? c : null; - var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple?(); + var old = oldCollection != null ? (oldCollection.Id, oldCollection.Name) : new ValueTuple?(); if (collectionId == null) { if (old == null) @@ -166,7 +148,7 @@ public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : var ids = collections.Active.Individuals.GetGroup(id); collections.Active.CreateIndividualCollection(ids); } - else if (old.Value.Item1 == collection.Identity.Id) + else if (old.Value.Item1 == collection.Id) { return (PenumbraApiEc.NothingChanged, old); } diff --git a/Penumbra/Api/Api/GameStateApi.cs b/Penumbra/Api/Api/GameStateApi.cs index 74cde3a0..c2cae32b 100644 --- a/Penumbra/Api/Api/GameStateApi.cs +++ b/Penumbra/Api/Api/GameStateApi.cs @@ -14,18 +14,16 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable { private readonly CommunicatorService _communicator; private readonly CollectionResolver _collectionResolver; - private readonly DrawObjectState _drawObjectState; private readonly CutsceneService _cutsceneService; private readonly ResourceLoader _resourceLoader; public unsafe GameStateApi(CommunicatorService communicator, CollectionResolver collectionResolver, CutsceneService cutsceneService, - ResourceLoader resourceLoader, DrawObjectState drawObjectState) + ResourceLoader resourceLoader) { _communicator = communicator; _collectionResolver = collectionResolver; _cutsceneService = cutsceneService; _resourceLoader = resourceLoader; - _drawObjectState = drawObjectState; _resourceLoader.ResourceLoaded += OnResourceLoaded; _resourceLoader.PapRequested += OnPapRequested; _communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api); @@ -63,36 +61,12 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable public unsafe (nint GameObject, (Guid Id, string Name) Collection) GetDrawObjectInfo(nint drawObject) { var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true); - return (data.AssociatedGameObject, (Id: data.ModCollection.Identity.Id, Name: data.ModCollection.Identity.Name)); + return (data.AssociatedGameObject, (data.ModCollection.Id, data.ModCollection.Name)); } public int GetCutsceneParentIndex(int actorIdx) => _cutsceneService.GetParentIndex(actorIdx); - public Func GetCutsceneParentIndexFunc() - { - var weakRef = new WeakReference(_cutsceneService); - return idx => - { - if (!weakRef.TryGetTarget(out var c)) - throw new ObjectDisposedException("The underlying cutscene state storage of this IPC container was disposed."); - - return c.GetParentIndex(idx); - }; - } - - public Func GetGameObjectFromDrawObjectFunc() - { - var weakRef = new WeakReference(_drawObjectState); - return model => - { - if (!weakRef.TryGetTarget(out var c)) - throw new ObjectDisposedException("The underlying draw object state storage of this IPC container was disposed."); - - return c.TryGetValue(model, out var data) ? data.Item1.Address : nint.Zero; - }; - } - public PenumbraApiEc SetCutsceneParentIndex(int copyIdx, int newParentIdx) => _cutsceneService.SetParentIndex(copyIdx, newParentIdx) ? PenumbraApiEc.Success @@ -119,5 +93,5 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable } private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject) - => CreatedCharacterBase?.Invoke(gameObject, collection.Identity.Id, drawObject); + => CreatedCharacterBase?.Invoke(gameObject, collection.Id, drawObject); } diff --git a/Penumbra/Api/Api/IdentityChecker.cs b/Penumbra/Api/Api/IdentityChecker.cs deleted file mode 100644 index e090053e..00000000 --- a/Penumbra/Api/Api/IdentityChecker.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Penumbra.Api.Api; - -public static class IdentityChecker -{ - public static bool Check(string identity) - => true; -} diff --git a/Penumbra/Api/Api/MetaApi.cs b/Penumbra/Api/Api/MetaApi.cs index 5cffc811..217cb1e3 100644 --- a/Penumbra/Api/Api/MetaApi.cs +++ b/Penumbra/Api/Api/MetaApi.cs @@ -16,6 +16,8 @@ namespace Penumbra.Api.Api; public class MetaApi(IFramework framework, CollectionResolver collectionResolver, ApiHelpers helpers) : IPenumbraApiMeta, IApiService { + public const int CurrentVersion = 1; + public string GetPlayerMetaManipulations() { var collection = collectionResolver.PlayerCollection(); @@ -51,7 +53,7 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver } internal static string CompressMetaManipulations(ModCollection collection) - => CompressMetaManipulationsV1(collection); + => CompressMetaManipulationsV0(collection); private static string CompressMetaManipulationsV0(ModCollection collection) { @@ -66,8 +68,6 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver MetaDictionary.SerializeTo(array, cache.Rsp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); MetaDictionary.SerializeTo(array, cache.Atch.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); - MetaDictionary.SerializeTo(array, cache.Shp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); - MetaDictionary.SerializeTo(array, cache.Atr.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry))); } return Functions.ToCompressedBase64(array, 0); @@ -99,6 +99,7 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver WriteCache(zipStream, cache.Est); WriteCache(zipStream, cache.Rsp); WriteCache(zipStream, cache.Gmp); + WriteCache(zipStream, cache.Atch); cache.GlobalEqp.EnterReadLock(); try @@ -111,10 +112,6 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver { cache.GlobalEqp.ExitReadLock(); } - - WriteCache(zipStream, cache.Atch); - WriteCache(zipStream, cache.Shp); - WriteCache(zipStream, cache.Atr); } } @@ -144,97 +141,16 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver } } - public const uint ImcKey = ((uint)'I' << 24) | ((uint)'M' << 16) | ((uint)'C' << 8); - public const uint EqpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'P' << 8); - public const uint EqdpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'D' << 8) | 'P'; - public const uint EstKey = ((uint)'E' << 24) | ((uint)'S' << 16) | ((uint)'T' << 8); - public const uint RspKey = ((uint)'R' << 24) | ((uint)'S' << 16) | ((uint)'P' << 8); - public const uint GmpKey = ((uint)'G' << 24) | ((uint)'M' << 16) | ((uint)'P' << 8); - public const uint GeqpKey = ((uint)'G' << 24) | ((uint)'E' << 16) | ((uint)'Q' << 8) | 'P'; - public const uint AtchKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'C' << 8) | 'H'; - public const uint ShpKey = ((uint)'S' << 24) | ((uint)'H' << 16) | ((uint)'P' << 8); - public const uint AtrKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'R' << 8); - - private static unsafe string CompressMetaManipulationsV2(ModCollection? collection) - { - using var ms = new MemoryStream(); - ms.Capacity = 1024; - using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true)) - { - zipStream.Write((byte)2); - zipStream.Write("META0002"u8); - if (collection?.MetaCache is { } cache) - { - WriteCache(zipStream, cache.Imc, ImcKey); - WriteCache(zipStream, cache.Eqp, EqpKey); - WriteCache(zipStream, cache.Eqdp, EqdpKey); - WriteCache(zipStream, cache.Est, EstKey); - WriteCache(zipStream, cache.Rsp, RspKey); - WriteCache(zipStream, cache.Gmp, GmpKey); - cache.GlobalEqp.EnterReadLock(); - - try - { - if (cache.GlobalEqp.Count > 0) - { - zipStream.Write(GeqpKey); - zipStream.Write(cache.GlobalEqp.Count); - foreach (var (globalEqp, _) in cache.GlobalEqp) - zipStream.Write(new ReadOnlySpan(&globalEqp, sizeof(GlobalEqpManipulation))); - } - } - finally - { - cache.GlobalEqp.ExitReadLock(); - } - - WriteCache(zipStream, cache.Atch, AtchKey); - WriteCache(zipStream, cache.Shp, ShpKey); - WriteCache(zipStream, cache.Atr, AtrKey); - } - } - - ms.Flush(); - ms.Position = 0; - var data = ms.GetBuffer().AsSpan(0, (int)ms.Length); - return Convert.ToBase64String(data); - - void WriteCache(Stream stream, MetaCacheBase metaCache, uint label) - where TKey : unmanaged, IMetaIdentifier - where TValue : unmanaged - { - metaCache.EnterReadLock(); - try - { - if (metaCache.Count <= 0) - return; - - stream.Write(label); - 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, out byte version) + internal static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips) { if (manipString.Length == 0) { - manips = new MetaDictionary(); - version = byte.MaxValue; + manips = new MetaDictionary(); return true; } @@ -247,14 +163,13 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver zipStream.CopyTo(resultStream); resultStream.Flush(); resultStream.Position = 0; - var data = resultStream.GetBuffer().AsSpan(0, (int)resultStream.Length); - version = data[0]; - data = data[1..]; + 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); - case 2: return ConvertManipsV2(data, out manips); default: Penumbra.Log.Debug($"Invalid version for manipulations: {version}."); manips = null; @@ -264,135 +179,9 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver catch (Exception ex) { Penumbra.Log.Debug($"Error decompressing manipulations:\n{ex}"); - manips = null; - version = byte.MaxValue; - return false; - } - } - - private static bool ConvertManipsV2(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips) - { - if (!data.StartsWith("META0002"u8)) - { - Penumbra.Log.Debug("Invalid manipulations of version 2, does not start with valid prefix."); manips = null; return false; } - - manips = new MetaDictionary(); - var r = new SpanBinaryReader(data[8..]); - while (r.Remaining > 4) - { - var prefix = r.ReadUInt32(); - var count = r.Remaining > 4 ? r.ReadInt32() : 0; - if (count is 0) - continue; - - switch (prefix) - { - case ImcKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case EqpKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case EqdpKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case EstKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case RspKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case GmpKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case GeqpKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier)) - return false; - } - - break; - case AtchKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case ShpKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - case AtrKey: - for (var i = 0; i < count; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - break; - } - } - - return true; } private static bool ConvertManipsV1(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips) @@ -460,6 +249,15 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver return false; } + var atchCount = r.ReadInt32(); + for (var i = 0; i < atchCount; ++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) { @@ -468,41 +266,6 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver return false; } - // Atch was added after there were already some V1 around, so check for size here. - if (r.Position < r.Count) - { - var atchCount = r.ReadInt32(); - for (var i = 0; i < atchCount; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - // Shp and Atr was added later - if (r.Position < r.Count) - { - var shpCount = r.ReadInt32(); - for (var i = 0; i < shpCount; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - - var atrCount = r.ReadInt32(); - for (var i = 0; i < atrCount; ++i) - { - var identifier = r.Read(); - var value = r.Read(); - if (!identifier.Validate() || !manips.TryAdd(identifier, value)) - return false; - } - } - } - return true; } @@ -511,7 +274,7 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver var json = Encoding.UTF8.GetString(data); manips = JsonConvert.DeserializeObject(json); return manips != null; - } + } internal void TestMetaManipulations() { @@ -528,11 +291,11 @@ public class MetaApi(IFramework framework, CollectionResolver collectionResolver var v1Time = watch.ElapsedMilliseconds; watch.Restart(); - var v1Success = ConvertManips(v1, out var v1Roundtrip, out _); + var v1Success = ConvertManips(v1, out var v1Roundtrip); var v1RoundtripTime = watch.ElapsedMilliseconds; watch.Restart(); - var v0Success = ConvertManips(v0, out var v0Roundtrip, out _); + var v0Success = ConvertManips(v0, out var v0Roundtrip); var v0RoundtripTime = watch.ElapsedMilliseconds; Penumbra.Log.Information($"Version | Count | Time | Length | Success | ReCount | ReTime | Equal"); diff --git a/Penumbra/Api/Api/ModSettingsApi.cs b/Penumbra/Api/Api/ModSettingsApi.cs index 3ba17cf4..e046ce30 100644 --- a/Penumbra/Api/Api/ModSettingsApi.cs +++ b/Penumbra/Api/Api/ModSettingsApi.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; @@ -63,18 +63,13 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable return new AvailableModSettings(dict); } + public Dictionary? GetAvailableModSettingsBase(string modDirectory, string modName) + => _modManager.TryGetMod(modDirectory, modName, out var mod) + ? mod.Groups.ToDictionary(g => g.Name, g => (g.Options.Select(o => o.Name).ToArray(), (int)g.Type)) + : null; + public (PenumbraApiEc, (bool, int, Dictionary>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory, string modName, bool ignoreInheritance) - { - var ret = GetCurrentModSettingsWithTemp(collectionId, modDirectory, modName, ignoreInheritance, true, 0); - if (ret.Item2 is null) - return (ret.Item1, null); - - return (ret.Item1, (ret.Item2.Value.Item1, ret.Item2.Value.Item2, ret.Item2.Value.Item3, ret.Item2.Value.Item4)); - } - - public (PenumbraApiEc, (bool, int, Dictionary>, bool, bool)?) GetCurrentModSettingsWithTemp(Guid collectionId, - string modDirectory, string modName, bool ignoreInheritance, bool ignoreTemporary, int key) { if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) return (PenumbraApiEc.ModMissing, null); @@ -82,32 +77,17 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable if (!_collectionManager.Storage.ById(collectionId, out var collection)) return (PenumbraApiEc.CollectionMissing, null); - if (collection.Identity.Id == Guid.Empty) + var settings = collection.Id == Guid.Empty + ? null + : ignoreInheritance + ? collection.Settings[mod.Index] + : collection[mod.Index].Settings; + if (settings == null) return (PenumbraApiEc.Success, null); - if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings) - return (PenumbraApiEc.Success, settings); - - return (PenumbraApiEc.Success, null); - } - - public (PenumbraApiEc, Dictionary>, bool, bool)>?) GetAllModSettings(Guid collectionId, - bool ignoreInheritance, bool ignoreTemporary, int key) - { - if (!_collectionManager.Storage.ById(collectionId, out var collection)) - return (PenumbraApiEc.CollectionMissing, null); - - if (collection.Identity.Id == Guid.Empty) - return (PenumbraApiEc.Success, []); - - var ret = new Dictionary>, bool, bool)>(_modManager.Count); - foreach (var mod in _modManager) - { - if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings) - ret[mod.Identifier] = settings; - } - - return (PenumbraApiEc.Success, ret); + var (enabled, priority, dict) = settings.ConvertToShareable(mod); + return (PenumbraApiEc.Success, + (enabled, priority.Value, dict, collection.Settings[mod.Index] == null)); } public PenumbraApiEc TryInheritMod(Guid collectionId, string modDirectory, string modName, bool inherit) @@ -204,9 +184,36 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) return ApiHelpers.Return(PenumbraApiEc.ModMissing, args); - var settingSuccess = ConvertModSetting(mod, optionGroupName, optionNames, out var groupIdx, out var setting); - if (settingSuccess is not PenumbraApiEc.Success) - return ApiHelpers.Return(settingSuccess, args); + var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName); + if (groupIdx < 0) + return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args); + + var setting = Setting.Zero; + switch (mod.Groups[groupIdx]) + { + case { Behaviour: GroupDrawBehaviour.SingleSelection } single: + { + var optionIdx = optionNames.Count == 0 ? -1 : single.Options.IndexOf(o => o.Name == optionNames[^1]); + if (optionIdx < 0) + return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args); + + setting = Setting.Single(optionIdx); + break; + } + case { Behaviour: GroupDrawBehaviour.MultiSelection } multi: + { + foreach (var name in optionNames) + { + var optionIdx = multi.Options.IndexOf(o => o.Name == name); + if (optionIdx < 0) + return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args); + + setting |= Setting.Multi(optionIdx); + } + + break; + } + } var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success @@ -231,38 +238,13 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable return ApiHelpers.Return(PenumbraApiEc.Success, args); } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private (bool, int, Dictionary>, bool, bool)? GetCurrentSettings(ModCollection collection, Mod mod, - bool ignoreInheritance, bool ignoreTemporary, int key) - { - var settings = collection.Settings.Settings[mod.Index]; - if (!ignoreTemporary && settings.TempSettings is { } tempSettings && (tempSettings.Lock <= 0 || tempSettings.Lock == key)) - { - if (!tempSettings.ForceInherit) - return (tempSettings.Enabled, tempSettings.Priority.Value, tempSettings.ConvertToShareable(mod).Settings, - false, true); - if (!ignoreInheritance && collection.GetActualSettings(mod.Index).Settings is { } actualSettingsTemp) - return (actualSettingsTemp.Enabled, actualSettingsTemp.Priority.Value, - actualSettingsTemp.ConvertToShareable(mod).Settings, true, true); - } - - if (settings.Settings is { } ownSettings) - return (ownSettings.Enabled, ownSettings.Priority.Value, ownSettings.ConvertToShareable(mod).Settings, false, - false); - if (!ignoreInheritance && collection.GetInheritedSettings(mod.Index).Settings is { } actualSettings) - return (actualSettings.Enabled, actualSettings.Priority.Value, - actualSettings.ConvertToShareable(mod).Settings, true, false); - - return null; - } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void TriggerSettingEdited(Mod mod) { var collection = _collectionResolver.PlayerCollection(); - var (settings, parent) = collection.GetActualSettings(mod.Index); + var (settings, parent) = collection[mod.Index]; if (settings is { Enabled: true }) - ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Identity.Id, mod.Identifier, parent != collection); + ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Id, mod.Identifier, parent != collection); } private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2) @@ -272,7 +254,7 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable } private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited) - => ModSettingChanged?.Invoke(type, collection.Identity.Id, mod?.ModPath.Name ?? string.Empty, inherited); + => ModSettingChanged?.Invoke(type, collection.Id, mod?.ModPath.Name ?? string.Empty, inherited); private void OnModOptionEdited(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container, int moveIndex) @@ -301,41 +283,4 @@ public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable TriggerSettingEdited(mod); } - - public static PenumbraApiEc ConvertModSetting(Mod mod, string groupName, IReadOnlyList optionNames, out int groupIndex, - out Setting setting) - { - groupIndex = mod.Groups.IndexOf(g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase)); - setting = Setting.Zero; - if (groupIndex < 0) - return PenumbraApiEc.OptionGroupMissing; - - switch (mod.Groups[groupIndex]) - { - case { Behaviour: GroupDrawBehaviour.SingleSelection } single: - { - var optionIdx = optionNames.Count == 0 ? -1 : single.Options.IndexOf(o => o.Name == optionNames[^1]); - if (optionIdx < 0) - return PenumbraApiEc.OptionMissing; - - setting = Setting.Single(optionIdx); - break; - } - case { Behaviour: GroupDrawBehaviour.MultiSelection } multi: - { - foreach (var name in optionNames) - { - var optionIdx = multi.Options.IndexOf(o => o.Name == name); - if (optionIdx < 0) - return PenumbraApiEc.OptionMissing; - - setting |= Setting.Multi(optionIdx); - } - - break; - } - } - - return PenumbraApiEc.Success; - } } diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs index 1f4f1cf4..64e201be 100644 --- a/Penumbra/Api/Api/ModsApi.cs +++ b/Penumbra/Api/Api/ModsApi.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json.Linq; using OtterGui.Compression; using OtterGui.Services; using Penumbra.Api.Enums; @@ -34,8 +33,12 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable { switch (type) { - case ModPathChangeType.Deleted when oldDirectory != null: ModDeleted?.Invoke(oldDirectory.Name); break; - case ModPathChangeType.Added when newDirectory != null: ModAdded?.Invoke(newDirectory.Name); break; + case ModPathChangeType.Deleted when oldDirectory != null: + ModDeleted?.Invoke(oldDirectory.Name); + break; + case ModPathChangeType.Added when newDirectory != null: + ModAdded?.Invoke(newDirectory.Name); + break; case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null: ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name); break; @@ -43,9 +46,7 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable } public void Dispose() - { - _communicator.ModPathChanged.Unsubscribe(OnModPathChanged); - } + => _communicator.ModPathChanged.Unsubscribe(OnModPathChanged); public Dictionary GetModList() => _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text); @@ -108,22 +109,10 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable public event Action? ModAdded; public event Action? ModMoved; - public event Action? CreatingPcp - { - add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi); - remove => _communicator.PcpCreation.Unsubscribe(value!); - } - - public event Action? ParsingPcp - { - add => _communicator.PcpParsing.Subscribe(value!, PcpParsing.Priority.ModsApi); - remove => _communicator.PcpParsing.Unsubscribe(value!); - } - public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName) { if (!_modManager.TryGetMod(modDirectory, modName, out var mod) - || !_modFileSystem.TryGetValue(mod, out var leaf)) + || !_modFileSystem.FindLeaf(mod, out var leaf)) return (PenumbraApiEc.ModMissing, string.Empty, false, false); var fullPath = leaf.FullName(); @@ -138,7 +127,7 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable return PenumbraApiEc.InvalidArgument; if (!_modManager.TryGetMod(modDirectory, modName, out var mod) - || !_modFileSystem.TryGetValue(mod, out var leaf)) + || !_modFileSystem.FindLeaf(mod, out var leaf)) return PenumbraApiEc.ModMissing; try @@ -156,10 +145,4 @@ public class ModsApi : IPenumbraApiMods, IApiService, IDisposable => _modManager.TryGetMod(modDirectory, modName, out var mod) ? mod.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToInternalObject()) : []; - - public IReadOnlyDictionary> GetChangedItemAdapterDictionary() - => new ModChangedItemAdapter(new WeakReference(_modManager)); - - public IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)> GetChangedItemAdapterList() - => new ModChangedItemAdapter(new WeakReference(_modManager)); } diff --git a/Penumbra/Api/Api/PenumbraApi.cs b/Penumbra/Api/Api/PenumbraApi.cs index c4026c72..eaaf9f38 100644 --- a/Penumbra/Api/Api/PenumbraApi.cs +++ b/Penumbra/Api/Api/PenumbraApi.cs @@ -16,16 +16,13 @@ public class PenumbraApi( TemporaryApi temporary, UiApi ui) : IDisposable, IApiService, IPenumbraApi { - public const int BreakingVersion = 5; - public const int FeatureVersion = 13; - public void Dispose() { Valid = false; } public (int Breaking, int Feature) ApiVersion - => (BreakingVersion, FeatureVersion); + => (5, 3); public bool Valid { get; private set; } = true; public IPenumbraApiCollection Collection { get; } = collection; diff --git a/Penumbra/Api/Api/PluginStateApi.cs b/Penumbra/Api/Api/PluginStateApi.cs index f74553d1..d69df448 100644 --- a/Penumbra/Api/Api/PluginStateApi.cs +++ b/Penumbra/Api/Api/PluginStateApi.cs @@ -1,38 +1,39 @@ -using System.Collections.Frozen; using Newtonsoft.Json; using OtterGui.Services; using Penumbra.Communication; -using Penumbra.Mods; using Penumbra.Services; namespace Penumbra.Api.Api; -public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService +public class PluginStateApi : IPenumbraApiPluginState, IApiService { + private readonly Configuration _config; + private readonly CommunicatorService _communicator; + + public PluginStateApi(Configuration config, CommunicatorService communicator) + { + _config = config; + _communicator = communicator; + } + public string GetModDirectory() - => config.ModDirectory; + => _config.ModDirectory; public string GetConfiguration() - => JsonConvert.SerializeObject(config, Formatting.Indented); + => JsonConvert.SerializeObject(_config, Formatting.Indented); public event Action? ModDirectoryChanged { - add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api); - remove => communicator.ModDirectoryChanged.Unsubscribe(value!); + add => _communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api); + remove => _communicator.ModDirectoryChanged.Unsubscribe(value!); } public bool GetEnabledState() - => config.EnableMods; + => _config.EnableMods; public event Action? EnabledChange { - add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api); - remove => communicator.EnabledChanged.Unsubscribe(value!); + add => _communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api); + remove => _communicator.EnabledChanged.Unsubscribe(value!); } - - public FrozenSet SupportedFeatures - => FeatureChecker.SupportedFeatures.ToFrozenSet(); - - public string[] CheckSupportedFeatures(IEnumerable requiredFeatures) - => requiredFeatures.Where(f => !FeatureChecker.Supported(f)).ToArray(); } diff --git a/Penumbra/Api/Api/RedrawApi.cs b/Penumbra/Api/Api/RedrawApi.cs index 08f1f9df..82d14f7b 100644 --- a/Penumbra/Api/Api/RedrawApi.cs +++ b/Penumbra/Api/Api/RedrawApi.cs @@ -1,57 +1,27 @@ using Dalamud.Game.ClientState.Objects.Types; -using Dalamud.Plugin.Services; using OtterGui.Services; using Penumbra.Api.Enums; -using Penumbra.Collections; -using Penumbra.Collections.Manager; -using Penumbra.GameData.Interop; using Penumbra.Interop.Services; -namespace Penumbra.Api.Api; - -public class RedrawApi(RedrawService redrawService, IFramework framework, CollectionManager collections, ObjectManager objects, ApiHelpers helpers) : IPenumbraApiRedraw, IApiService +namespace Penumbra.Api.Api; + +public class RedrawApi(RedrawService redrawService) : IPenumbraApiRedraw, IApiService { public void RedrawObject(int gameObjectIndex, RedrawType setting) - { - framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObjectIndex, setting)); - } + => redrawService.RedrawObject(gameObjectIndex, setting); public void RedrawObject(string name, RedrawType setting) - { - framework.RunOnFrameworkThread(() => redrawService.RedrawObject(name, setting)); - } + => redrawService.RedrawObject(name, setting); public void RedrawObject(IGameObject? gameObject, RedrawType setting) - { - framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObject, setting)); - } + => redrawService.RedrawObject(gameObject, setting); public void RedrawAll(RedrawType setting) - { - framework.RunOnFrameworkThread(() => redrawService.RedrawAll(setting)); - } - - public void RedrawCollectionMembers(Guid collectionId, RedrawType setting) - { - - if (!collections.Storage.ById(collectionId, out var collection)) - collection = ModCollection.Empty; - framework.RunOnFrameworkThread(() => - { - foreach (var actor in objects.Objects) - { - helpers.AssociatedCollection(actor.ObjectIndex, out var modCollection); - if (collection == modCollection) - { - redrawService.RedrawObject(actor.ObjectIndex, setting); - } - } - }); - } + => redrawService.RedrawAll(setting); public event GameObjectRedrawnDelegate? GameObjectRedrawn { add => redrawService.GameObjectRedrawn += value; remove => redrawService.GameObjectRedrawn -= value; } -} +} diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs index 7567acd3..516b4347 100644 --- a/Penumbra/Api/Api/TemporaryApi.cs +++ b/Penumbra/Api/Api/TemporaryApi.cs @@ -1,11 +1,8 @@ -using OtterGui.Log; using OtterGui.Services; using Penumbra.Api.Enums; -using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; using Penumbra.GameData.Interop; -using Penumbra.Mods.Manager; using Penumbra.Mods.Settings; using Penumbra.String.Classes; @@ -16,20 +13,10 @@ public class TemporaryApi( ObjectManager objects, ActorManager actors, CollectionManager collectionManager, - TempModManager tempMods, - ApiHelpers apiHelpers, - ModManager modManager) : IPenumbraApiTemporary, IApiService + TempModManager tempMods) : IPenumbraApiTemporary, IApiService { - public (PenumbraApiEc, Guid) CreateTemporaryCollection(string identity, string name) - { - if (!IdentityChecker.Check(identity)) - return (PenumbraApiEc.InvalidCredentials, Guid.Empty); - - var collection = tempCollections.CreateTemporaryCollection(name); - if (collection == Guid.Empty) - return (PenumbraApiEc.UnknownError, collection); - return (PenumbraApiEc.Success, collection); - } + public Guid CreateTemporaryCollection(string name) + => tempCollections.CreateTemporaryCollection(name); public PenumbraApiEc DeleteTemporaryCollection(Guid collectionId) => tempCollections.RemoveTemporaryCollection(collectionId) @@ -73,7 +60,7 @@ public class TemporaryApi( if (!ConvertPaths(paths, out var p)) return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args); - if (!MetaApi.ConvertManips(manipString, out var m, out _)) + 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 @@ -99,7 +86,7 @@ public class TemporaryApi( if (!ConvertPaths(paths, out var p)) return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args); - if (!MetaApi.ConvertManips(manipString, out var m, out _)) + 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 @@ -138,177 +125,6 @@ public class TemporaryApi( return ApiHelpers.Return(ret, args); } - public (PenumbraApiEc, (bool, bool, int, Dictionary>)?, string) QueryTemporaryModSettings(Guid collectionId, - string modDirectory, string modName, int key) - { - var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName); - if (!collectionManager.Storage.ById(collectionId, out var collection)) - return (ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args), null, string.Empty); - - return QueryTemporaryModSettings(args, collection, modDirectory, modName, key); - } - - public (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary>)? Settings, string Source) - QueryTemporaryModSettingsPlayer(int objectIndex, - string modDirectory, string modName, int key) - { - var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName); - if (!apiHelpers.AssociatedCollection(objectIndex, out var collection)) - return (ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args), null, string.Empty); - - return QueryTemporaryModSettings(args, collection, modDirectory, modName, key); - } - - private (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary>)? Settings, string Source) QueryTemporaryModSettings( - in LazyString args, ModCollection collection, string modDirectory, string modName, int key) - { - if (!modManager.TryGetMod(modDirectory, modName, out var mod)) - return (ApiHelpers.Return(PenumbraApiEc.ModMissing, args), null, string.Empty); - - if (collection.Identity.Index <= 0) - return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty); - - var settings = collection.GetTempSettings(mod.Index); - if (settings == null) - return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty); - - if (settings.Lock > 0 && settings.Lock != key) - return (ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args), null, settings.Source); - - return (ApiHelpers.Return(PenumbraApiEc.Success, args), - (settings.ForceInherit, settings.Enabled, settings.Priority.Value, settings.ConvertToShareable(mod).Settings), settings.Source); - } - - - public PenumbraApiEc SetTemporaryModSettings(Guid collectionId, string modDirectory, string modName, bool inherit, bool enabled, - int priority, - IReadOnlyDictionary> options, string source, int key) - { - var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, - "Enabled", enabled, - "Priority", priority, "Options", options, "Source", source, "Key", key); - if (!collectionManager.Storage.ById(collectionId, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args); - - return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key); - } - - public PenumbraApiEc SetTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, bool inherit, bool enabled, - int priority, - IReadOnlyDictionary> options, string source, int key) - { - var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, "Enabled", - enabled, - "Priority", priority, "Options", options, "Source", source, "Key", key); - if (!apiHelpers.AssociatedCollection(objectIndex, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args); - - return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key); - } - - private PenumbraApiEc SetTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName, - bool inherit, bool enabled, int priority, IReadOnlyDictionary> options, string source, int key) - { - if (collection.Identity.Index <= 0) - return ApiHelpers.Return(PenumbraApiEc.TemporarySettingImpossible, args); - - if (!modManager.TryGetMod(modDirectory, modName, out var mod)) - return ApiHelpers.Return(PenumbraApiEc.ModMissing, args); - - if (!collectionManager.Editor.CanSetTemporarySettings(collection, mod, key)) - if (collection.GetTempSettings(mod.Index) is { Lock: > 0 } oldSettings && oldSettings.Lock != key) - return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args); - - var newSettings = new TemporaryModSettings() - { - ForceInherit = inherit, - Enabled = enabled, - Priority = new ModPriority(priority), - Lock = key, - Source = source, - Settings = SettingList.Default(mod), - }; - - - foreach (var (groupName, optionNames) in options) - { - var ec = ModSettingsApi.ConvertModSetting(mod, groupName, optionNames, out var groupIdx, out var setting); - if (ec != PenumbraApiEc.Success) - return ApiHelpers.Return(ec, args); - - newSettings.Settings[groupIdx] = setting; - } - - if (collectionManager.Editor.SetTemporarySettings(collection, mod, newSettings, key)) - return ApiHelpers.Return(PenumbraApiEc.Success, args); - - // This should not happen since all error cases had been checked before. - return ApiHelpers.Return(PenumbraApiEc.UnknownError, args); - } - - public PenumbraApiEc RemoveTemporaryModSettings(Guid collectionId, string modDirectory, string modName, int key) - { - var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Key", key); - if (!collectionManager.Storage.ById(collectionId, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args); - - return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key); - } - - public PenumbraApiEc RemoveTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, int key) - { - var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Key", key); - if (!apiHelpers.AssociatedCollection(objectIndex, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args); - - return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key); - } - - private PenumbraApiEc RemoveTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName, int key) - { - if (collection.Identity.Index <= 0) - return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args); - - if (!modManager.TryGetMod(modDirectory, modName, out var mod)) - return ApiHelpers.Return(PenumbraApiEc.ModMissing, args); - - if (collection.GetTempSettings(mod.Index) is null) - return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args); - - if (!collectionManager.Editor.SetTemporarySettings(collection, mod, null, key)) - return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args); - - return ApiHelpers.Return(PenumbraApiEc.Success, args); - } - - public PenumbraApiEc RemoveAllTemporaryModSettings(Guid collectionId, int key) - { - var args = ApiHelpers.Args("CollectionId", collectionId, "Key", key); - if (!collectionManager.Storage.ById(collectionId, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args); - - return RemoveAllTemporaryModSettings(args, collection, key); - } - - public PenumbraApiEc RemoveAllTemporaryModSettingsPlayer(int objectIndex, int key) - { - var args = ApiHelpers.Args("ObjectIndex", objectIndex, "Key", key); - if (!apiHelpers.AssociatedCollection(objectIndex, out var collection)) - return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args); - - return RemoveAllTemporaryModSettings(args, collection, key); - } - - private PenumbraApiEc RemoveAllTemporaryModSettings(in LazyString args, ModCollection collection, int key) - { - if (collection.Identity.Index <= 0) - return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args); - - var numRemoved = collectionManager.Editor.ClearTemporarySettings(collection, key); - return ApiHelpers.Return(numRemoved > 0 ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, args); - } - - /// /// Convert a dictionary of strings to a dictionary of game paths to full paths. /// Only returns true if all paths can successfully be converted and added. diff --git a/Penumbra/Api/Api/UiApi.cs b/Penumbra/Api/Api/UiApi.cs index b14f67ae..515874c0 100644 --- a/Penumbra/Api/Api/UiApi.cs +++ b/Penumbra/Api/Api/UiApi.cs @@ -81,21 +81,21 @@ public class UiApi : IPenumbraApiUi, IApiService, IDisposable public void CloseMainWindow() => _configWindow.IsOpen = false; - private void OnChangedItemClick(MouseButton button, IIdentifiedObjectData data) + private void OnChangedItemClick(MouseButton button, IIdentifiedObjectData? data) { if (ChangedItemClicked == null) return; - var (type, id) = data.ToApiObject(); + var (type, id) = data?.ToApiObject() ?? (ChangedItemType.None, 0); ChangedItemClicked.Invoke(button, type, id); } - private void OnChangedItemHover(IIdentifiedObjectData data) + private void OnChangedItemHover(IIdentifiedObjectData? data) { if (ChangedItemTooltip == null) return; - var (type, id) = data.ToApiObject(); + var (type, id) = data?.ToApiObject() ?? (ChangedItemType.None, 0); ChangedItemTooltip.Invoke(type, id); } } diff --git a/Penumbra/Api/HttpApi.cs b/Penumbra/Api/HttpApi.cs index 79348a88..859c46b4 100644 --- a/Penumbra/Api/HttpApi.cs +++ b/Penumbra/Api/HttpApi.cs @@ -1,11 +1,9 @@ -using Dalamud.Plugin.Services; using EmbedIO; using EmbedIO.Routing; using EmbedIO.WebApi; using OtterGui.Services; using Penumbra.Api.Api; using Penumbra.Api.Enums; -using Penumbra.Mods.Settings; namespace Penumbra.Api; @@ -14,28 +12,23 @@ public class HttpApi : IDisposable, IApiService private partial class Controller : WebApiController { // @formatter:off - [Route( HttpVerbs.Get, "/moddirectory" )] public partial string GetModDirectory(); - [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); - [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); - [Route( HttpVerbs.Post, "/redrawAll" )] public partial Task RedrawAll(); - [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); - [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod(); - [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow(); - [Route( HttpVerbs.Post, "/focusmod" )] public partial Task FocusMod(); - [Route( HttpVerbs.Post, "/setmodsettings")] public partial Task SetModSettings(); + [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); + [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); + [Route( HttpVerbs.Post, "/redrawAll" )] public partial void RedrawAll(); + [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); + [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod(); + [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow(); // @formatter:on } public const string Prefix = "http://localhost:42069/"; private readonly IPenumbraApi _api; - private readonly IFramework _framework; private WebServer? _server; - public HttpApi(Configuration config, IPenumbraApi api, IFramework framework) + public HttpApi(Configuration config, IPenumbraApi api) { - _api = api; - _framework = framework; + _api = api; if (config.EnableHttpApi) CreateWebServer(); } @@ -51,7 +44,7 @@ public class HttpApi : IDisposable, IApiService .WithUrlPrefix(Prefix) .WithMode(HttpListenerMode.EmbedIO)) .WithCors(Prefix) - .WithWebApi("/api", m => m.WithController(() => new Controller(_api, _framework))); + .WithWebApi("/api", m => m.WithController(() => new Controller(_api))); _server.StateChanged += (_, e) => Penumbra.Log.Information($"WebServer New State - {e.NewState}"); _server.RunAsync(); @@ -66,96 +59,60 @@ public class HttpApi : IDisposable, IApiService public void Dispose() => ShutdownWebServer(); - private partial class Controller(IPenumbraApi api, IFramework framework) + private partial class Controller { - public partial string GetModDirectory() - { - Penumbra.Log.Debug($"[HTTP] {nameof(GetModDirectory)} triggered."); - return api.PluginState.GetModDirectory(); - } + private readonly IPenumbraApi _api; + + public Controller(IPenumbraApi api) + => _api = api; public partial object? GetMods() { Penumbra.Log.Debug($"[HTTP] {nameof(GetMods)} triggered."); - return api.Mods.GetModList(); + return _api.Mods.GetModList(); } public async partial Task Redraw() { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); - Penumbra.Log.Debug($"[HTTP] [{Environment.CurrentManagedThreadId}] {nameof(Redraw)} triggered with {data}."); - await framework.RunOnFrameworkThread(() => - { - if (data.ObjectTableIndex >= 0) - api.Redraw.RedrawObject(data.ObjectTableIndex, data.Type); - else - api.Redraw.RedrawAll(data.Type); - }).ConfigureAwait(false); + var data = await HttpContext.GetRequestDataAsync(); + Penumbra.Log.Debug($"[HTTP] {nameof(Redraw)} triggered with {data}."); + if (data.ObjectTableIndex >= 0) + _api.Redraw.RedrawObject(data.ObjectTableIndex, data.Type); + else + _api.Redraw.RedrawAll(data.Type); } - public async partial Task RedrawAll() + public partial void RedrawAll() { Penumbra.Log.Debug($"[HTTP] {nameof(RedrawAll)} triggered."); - await framework.RunOnFrameworkThread(() => { api.Redraw.RedrawAll(RedrawType.Redraw); }).ConfigureAwait(false); + _api.Redraw.RedrawAll(RedrawType.Redraw); } public async partial Task ReloadMod() { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); + var data = await HttpContext.GetRequestDataAsync(); Penumbra.Log.Debug($"[HTTP] {nameof(ReloadMod)} triggered with {data}."); // Add the mod if it is not already loaded and if the directory name is given. // AddMod returns Success if the mod is already loaded. if (data.Path.Length != 0) - api.Mods.AddMod(data.Path); + _api.Mods.AddMod(data.Path); // Reload the mod by path or name, which will also remove no-longer existing mods. - api.Mods.ReloadMod(data.Path, data.Name); + _api.Mods.ReloadMod(data.Path, data.Name); } public async partial Task InstallMod() { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); + var data = await HttpContext.GetRequestDataAsync(); Penumbra.Log.Debug($"[HTTP] {nameof(InstallMod)} triggered with {data}."); if (data.Path.Length != 0) - api.Mods.InstallMod(data.Path); + _api.Mods.InstallMod(data.Path); } public partial void OpenWindow() { Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered."); - api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty); - } - - public async partial Task FocusMod() - { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); - Penumbra.Log.Debug($"[HTTP] {nameof(FocusMod)} triggered."); - if (data.Path.Length != 0) - api.Ui.OpenMainWindow(TabType.Mods, data.Path, data.Name); - } - - public async partial Task SetModSettings() - { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); - Penumbra.Log.Debug($"[HTTP] {nameof(SetModSettings)} triggered."); - await framework.RunOnFrameworkThread(() => - { - var collection = data.CollectionId ?? api.Collection.GetCollection(ApiCollectionType.Current)!.Value.Id; - if (data.Inherit.HasValue) - { - api.ModSettings.TryInheritMod(collection, data.ModPath, data.ModName, data.Inherit.Value); - if (data.Inherit.Value) - return; - } - - if (data.State.HasValue) - api.ModSettings.TrySetMod(collection, data.ModPath, data.ModName, data.State.Value); - if (data.Priority.HasValue) - api.ModSettings.TrySetModPriority(collection, data.ModPath, data.ModName, data.Priority.Value); - foreach (var (group, settings) in data.Settings ?? []) - api.ModSettings.TrySetModSettings(collection, data.ModPath, data.ModName, group, settings); - } - ).ConfigureAwait(false); + _api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty); } private record ModReloadData(string Path, string Name) @@ -165,13 +122,6 @@ public class HttpApi : IDisposable, IApiService { } } - private record ModFocusData(string Path, string Name) - { - public ModFocusData() - : this(string.Empty, string.Empty) - { } - } - private record ModInstallData(string Path) { public ModInstallData() @@ -185,19 +135,5 @@ public class HttpApi : IDisposable, IApiService : this(string.Empty, RedrawType.Redraw, -1) { } } - - private record SetModSettingsData( - Guid? CollectionId, - string ModPath, - string ModName, - bool? Inherit, - bool? State, - int? Priority, - Dictionary>? Settings) - { - public SetModSettingsData() - : this(null, string.Empty, string.Empty, null, null, null, null) - {} - } } } diff --git a/Penumbra/Api/IpcLaunchingProvider.cs b/Penumbra/Api/IpcLaunchingProvider.cs deleted file mode 100644 index ff851003..00000000 --- a/Penumbra/Api/IpcLaunchingProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Dalamud.Plugin; -using OtterGui.Log; -using OtterGui.Services; -using Penumbra.Api.Api; -using Serilog.Events; - -namespace Penumbra.Api; - -public sealed class IpcLaunchingProvider : IApiService -{ - public IpcLaunchingProvider(IDalamudPluginInterface pi, Logger log) - { - try - { - using var subscriber = log.MainLogger.IsEnabled(LogEventLevel.Debug) - ? IpcSubscribers.Launching.Subscriber(pi, - (major, minor) => log.Debug($"[IPC] Invoked Penumbra.Launching IPC with API Version {major}.{minor}.")) - : null; - - using var provider = IpcSubscribers.Launching.Provider(pi); - provider.Invoke(PenumbraApi.BreakingVersion, PenumbraApi.FeatureVersion); - } - catch (Exception ex) - { - log.Error($"[IPC] Could not invoke Penumbra.Launching IPC:\n{ex}"); - } - } -} diff --git a/Penumbra/Api/IpcProviders.cs b/Penumbra/Api/IpcProviders.cs index 5f04540f..861225fa 100644 --- a/Penumbra/Api/IpcProviders.cs +++ b/Penumbra/Api/IpcProviders.cs @@ -2,8 +2,6 @@ using Dalamud.Plugin; using OtterGui.Services; using Penumbra.Api.Api; using Penumbra.Api.Helpers; -using Penumbra.Communication; -using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; namespace Penumbra.Api; @@ -11,13 +9,11 @@ public sealed class IpcProviders : IDisposable, IApiService { private readonly List _providers; - private readonly EventProvider _disposedProvider; - private readonly EventProvider _initializedProvider; - private readonly CharacterUtility _characterUtility; + private readonly EventProvider _disposedProvider; + private readonly EventProvider _initializedProvider; - public IpcProviders(IDalamudPluginInterface pi, IPenumbraApi api, CharacterUtility characterUtility) + public IpcProviders(IDalamudPluginInterface pi, IPenumbraApi api) { - _characterUtility = characterUtility; _disposedProvider = IpcSubscribers.Disposed.Provider(pi); _initializedProvider = IpcSubscribers.Initialized.Provider(pi); _providers = @@ -29,7 +25,6 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.GetCollectionForObject.Provider(pi, api.Collection), IpcSubscribers.SetCollection.Provider(pi, api.Collection), IpcSubscribers.SetCollectionForObject.Provider(pi, api.Collection), - IpcSubscribers.CheckCurrentChangedItemFunc.Provider(pi, api.Collection), IpcSubscribers.ConvertTextureFile.Provider(pi, api.Editing), IpcSubscribers.ConvertTextureData.Provider(pi, api.Editing), @@ -40,8 +35,6 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.CreatingCharacterBase.Provider(pi, api.GameState), IpcSubscribers.CreatedCharacterBase.Provider(pi, api.GameState), IpcSubscribers.GameObjectResourcePathResolved.Provider(pi, api.GameState), - IpcSubscribers.GetCutsceneParentIndexFunc.Provider(pi, api.GameState), - IpcSubscribers.GetGameObjectFromDrawObjectFunc.Provider(pi, api.GameState), IpcSubscribers.GetPlayerMetaManipulations.Provider(pi, api.Meta), IpcSubscribers.GetMetaManipulations.Provider(pi, api.Meta), @@ -54,18 +47,12 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ModDeleted.Provider(pi, api.Mods), IpcSubscribers.ModAdded.Provider(pi, api.Mods), IpcSubscribers.ModMoved.Provider(pi, api.Mods), - IpcSubscribers.CreatingPcp.Provider(pi, api.Mods), - IpcSubscribers.ParsingPcp.Provider(pi, api.Mods), IpcSubscribers.GetModPath.Provider(pi, api.Mods), IpcSubscribers.SetModPath.Provider(pi, api.Mods), IpcSubscribers.GetChangedItems.Provider(pi, api.Mods), - IpcSubscribers.GetChangedItemAdapterDictionary.Provider(pi, api.Mods), - IpcSubscribers.GetChangedItemAdapterList.Provider(pi, api.Mods), IpcSubscribers.GetAvailableModSettings.Provider(pi, api.ModSettings), IpcSubscribers.GetCurrentModSettings.Provider(pi, api.ModSettings), - IpcSubscribers.GetCurrentModSettingsWithTemp.Provider(pi, api.ModSettings), - IpcSubscribers.GetAllModSettings.Provider(pi, api.ModSettings), IpcSubscribers.TryInheritMod.Provider(pi, api.ModSettings), IpcSubscribers.TrySetMod.Provider(pi, api.ModSettings), IpcSubscribers.TrySetModPriority.Provider(pi, api.ModSettings), @@ -76,19 +63,16 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.ApiVersion.Provider(pi, api), new FuncProvider<(int Major, int Minor)>(pi, "Penumbra.ApiVersions", () => api.ApiVersion), // backward compatibility - new FuncProvider(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility + new FuncProvider(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility IpcSubscribers.GetModDirectory.Provider(pi, api.PluginState), IpcSubscribers.GetConfiguration.Provider(pi, api.PluginState), IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState), IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState), IpcSubscribers.EnabledChange.Provider(pi, api.PluginState), - IpcSubscribers.SupportedFeatures.Provider(pi, api.PluginState), - IpcSubscribers.CheckSupportedFeatures.Provider(pi, api.PluginState), IpcSubscribers.RedrawObject.Provider(pi, api.Redraw), IpcSubscribers.RedrawAll.Provider(pi, api.Redraw), IpcSubscribers.GameObjectRedrawn.Provider(pi, api.Redraw), - IpcSubscribers.RedrawCollectionMembers.Provider(pi, api.Redraw), IpcSubscribers.ResolveDefaultPath.Provider(pi, api.Resolve), IpcSubscribers.ResolveInterfacePath.Provider(pi, api.Resolve), @@ -113,14 +97,6 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.AddTemporaryMod.Provider(pi, api.Temporary), IpcSubscribers.RemoveTemporaryModAll.Provider(pi, api.Temporary), IpcSubscribers.RemoveTemporaryMod.Provider(pi, api.Temporary), - IpcSubscribers.SetTemporaryModSettings.Provider(pi, api.Temporary), - IpcSubscribers.SetTemporaryModSettingsPlayer.Provider(pi, api.Temporary), - IpcSubscribers.RemoveTemporaryModSettings.Provider(pi, api.Temporary), - IpcSubscribers.RemoveTemporaryModSettingsPlayer.Provider(pi, api.Temporary), - IpcSubscribers.RemoveAllTemporaryModSettings.Provider(pi, api.Temporary), - IpcSubscribers.RemoveAllTemporaryModSettingsPlayer.Provider(pi, api.Temporary), - IpcSubscribers.QueryTemporaryModSettings.Provider(pi, api.Temporary), - IpcSubscribers.QueryTemporaryModSettingsPlayer.Provider(pi, api.Temporary), IpcSubscribers.ChangedItemTooltip.Provider(pi, api.Ui), IpcSubscribers.ChangedItemClicked.Provider(pi, api.Ui), @@ -131,21 +107,11 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.OpenMainWindow.Provider(pi, api.Ui), IpcSubscribers.CloseMainWindow.Provider(pi, api.Ui), ]; - if (_characterUtility.Ready) - _initializedProvider.Invoke(); - else - _characterUtility.LoadingFinished.Subscribe(OnCharacterUtilityReady, CharacterUtilityFinished.Priority.IpcProvider); - } - - private void OnCharacterUtilityReady() - { _initializedProvider.Invoke(); - _characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady); } public void Dispose() { - _characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady); foreach (var provider in _providers) provider.Dispose(); _providers.Clear(); diff --git a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs b/Penumbra/Api/IpcTester/CollectionsIpcTester.cs index f033b7c3..1d516eba 100644 --- a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs +++ b/Penumbra/Api/IpcTester/CollectionsIpcTester.cs @@ -1,7 +1,7 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Plugin; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; @@ -121,10 +121,6 @@ public class CollectionsIpcTester(IDalamudPluginInterface pi) : IUiService }).ToArray(); ImGui.OpenPopup("Changed Item List"); } - IpcTester.DrawIntro(RedrawCollectionMembers.Label, "Redraw Collection Members"); - if (ImGui.Button("Redraw##ObjectCollection")) - new RedrawCollectionMembers(pi).Invoke(collectionList[0].Id, RedrawType.Redraw); - } private void DrawChangedItemPopup() diff --git a/Penumbra/Api/IpcTester/EditingIpcTester.cs b/Penumbra/Api/IpcTester/EditingIpcTester.cs index d754cf90..a1001630 100644 --- a/Penumbra/Api/IpcTester/EditingIpcTester.cs +++ b/Penumbra/Api/IpcTester/EditingIpcTester.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Plugin; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/Api/IpcTester/GameStateIpcTester.cs b/Penumbra/Api/IpcTester/GameStateIpcTester.cs index 38a09714..04541a57 100644 --- a/Penumbra/Api/IpcTester/GameStateIpcTester.cs +++ b/Penumbra/Api/IpcTester/GameStateIpcTester.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Api.Enums; diff --git a/Penumbra/Api/IpcTester/IpcTester.cs b/Penumbra/Api/IpcTester/IpcTester.cs index b03d7e03..201e7068 100644 --- a/Penumbra/Api/IpcTester/IpcTester.cs +++ b/Penumbra/Api/IpcTester/IpcTester.cs @@ -1,6 +1,6 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.System.Framework; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using Penumbra.Api.Api; diff --git a/Penumbra/Api/IpcTester/MetaIpcTester.cs b/Penumbra/Api/IpcTester/MetaIpcTester.cs index bee1981c..8b393ade 100644 --- a/Penumbra/Api/IpcTester/MetaIpcTester.cs +++ b/Penumbra/Api/IpcTester/MetaIpcTester.cs @@ -1,20 +1,14 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Api.Api; using Penumbra.Api.IpcSubscribers; -using Penumbra.Meta.Manipulations; namespace Penumbra.Api.IpcTester; public class MetaIpcTester(IDalamudPluginInterface pi) : IUiService { - private int _gameObjectIndex; - private string _metaBase64 = string.Empty; - private MetaDictionary _metaDict = new(); - private byte _parsedVersion = byte.MaxValue; + private int _gameObjectIndex; public void Draw() { @@ -23,11 +17,6 @@ public class MetaIpcTester(IDalamudPluginInterface pi) : IUiService return; ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0); - if (ImUtf8.InputText("##metaText"u8, ref _metaBase64, "Base64 Metadata..."u8)) - if (!MetaApi.ConvertManips(_metaBase64, out _metaDict!, out _parsedVersion)) - _metaDict ??= new MetaDictionary(); - - using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); if (!table) return; @@ -45,8 +34,5 @@ public class MetaIpcTester(IDalamudPluginInterface pi) : IUiService var base64 = new GetMetaManipulations(pi).Invoke(_gameObjectIndex); ImGui.SetClipboardText(base64); } - - IpcTester.DrawIntro(string.Empty, "Parsed Data"); - ImUtf8.Text($"Version: {_parsedVersion}, Count: {_metaDict.Count}"); } } diff --git a/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs b/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs index 152efa45..23078576 100644 --- a/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs +++ b/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs @@ -1,9 +1,8 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Plugin; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; @@ -23,20 +22,16 @@ public class ModSettingsIpcTester : IUiService, IDisposable private bool _lastSettingChangeInherited; private DateTimeOffset _lastSettingChange; - private string _settingsModDirectory = string.Empty; - private string _settingsModName = string.Empty; - private Guid? _settingsCollection; - private string _settingsCollectionName = string.Empty; - private bool _settingsIgnoreInheritance; - private bool _settingsIgnoreTemporary; - private int _settingsKey; - private bool _settingsInherit; - private bool _settingsTemporary; - private bool _settingsEnabled; - private int _settingsPriority; - private IReadOnlyDictionary? _availableSettings; - private Dictionary>? _currentSettings; - private Dictionary>, bool, bool)>? _allSettings; + private string _settingsModDirectory = string.Empty; + private string _settingsModName = string.Empty; + private Guid? _settingsCollection; + private string _settingsCollectionName = string.Empty; + private bool _settingsIgnoreInheritance; + private bool _settingsInherit; + private bool _settingsEnabled; + private int _settingsPriority; + private IReadOnlyDictionary? _availableSettings; + private Dictionary>? _currentSettings; public ModSettingsIpcTester(IDalamudPluginInterface pi) { @@ -59,9 +54,7 @@ public class ModSettingsIpcTester : IUiService, IDisposable ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100); ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100); ImGuiUtil.GuidInput("##settingsCollection", "Collection...", string.Empty, ref _settingsCollection, ref _settingsCollectionName); - ImUtf8.Checkbox("Ignore Inheritance"u8, ref _settingsIgnoreInheritance); - ImUtf8.Checkbox("Ignore Temporary"u8, ref _settingsIgnoreTemporary); - ImUtf8.InputScalar("Key"u8, ref _settingsKey); + ImGui.Checkbox("Ignore Inheritance", ref _settingsIgnoreInheritance); var collection = _settingsCollection.GetValueOrDefault(Guid.Empty); using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); @@ -90,11 +83,10 @@ public class ModSettingsIpcTester : IUiService, IDisposable _lastSettingsError = ret.Item1; if (ret.Item1 == PenumbraApiEc.Success) { - _settingsEnabled = ret.Item2?.Item1 ?? false; - _settingsInherit = ret.Item2?.Item4 ?? true; - _settingsTemporary = false; - _settingsPriority = ret.Item2?.Item2 ?? 0; - _currentSettings = ret.Item2?.Item3; + _settingsEnabled = ret.Item2?.Item1 ?? false; + _settingsInherit = ret.Item2?.Item4 ?? true; + _settingsPriority = ret.Item2?.Item2 ?? 0; + _currentSettings = ret.Item2?.Item3; } else { @@ -102,40 +94,6 @@ public class ModSettingsIpcTester : IUiService, IDisposable } } - IpcTester.DrawIntro(GetCurrentModSettingsWithTemp.Label, "Get Current Settings With Temp"); - if (ImGui.Button("Get##CurrentTemp")) - { - var ret = new GetCurrentModSettingsWithTemp(_pi) - .Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey); - _lastSettingsError = ret.Item1; - if (ret.Item1 == PenumbraApiEc.Success) - { - _settingsEnabled = ret.Item2?.Item1 ?? false; - _settingsInherit = ret.Item2?.Item4 ?? true; - _settingsTemporary = ret.Item2?.Item5 ?? false; - _settingsPriority = ret.Item2?.Item2 ?? 0; - _currentSettings = ret.Item2?.Item3; - } - else - { - _currentSettings = null; - } - } - - IpcTester.DrawIntro(GetAllModSettings.Label, "Get All Mod Settings"); - if (ImGui.Button("Get##All")) - { - var ret = new GetAllModSettings(_pi).Invoke(collection, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey); - _lastSettingsError = ret.Item1; - _allSettings = ret.Item2; - } - - if (_allSettings != null) - { - ImGui.SameLine(); - ImUtf8.Text($"{_allSettings.Count} Mods"); - } - IpcTester.DrawIntro(TryInheritMod.Label, "Inherit Mod"); ImGui.Checkbox("##inherit", ref _settingsInherit); ImGui.SameLine(); diff --git a/Penumbra/Api/IpcTester/ModsIpcTester.cs b/Penumbra/Api/IpcTester/ModsIpcTester.cs index 9ea53366..a24861a3 100644 --- a/Penumbra/Api/IpcTester/ModsIpcTester.cs +++ b/Penumbra/Api/IpcTester/ModsIpcTester.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/Api/IpcTester/PluginStateIpcTester.cs b/Penumbra/Api/IpcTester/PluginStateIpcTester.cs index 073305d0..df82033d 100644 --- a/Penumbra/Api/IpcTester/PluginStateIpcTester.cs +++ b/Penumbra/Api/IpcTester/PluginStateIpcTester.cs @@ -1,11 +1,10 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using Penumbra.Api.Helpers; using Penumbra.Api.IpcSubscribers; @@ -13,7 +12,7 @@ namespace Penumbra.Api.IpcTester; public class PluginStateIpcTester : IUiService, IDisposable { - private readonly IDalamudPluginInterface _pi; + private readonly IDalamudPluginInterface _pi; public readonly EventSubscriber ModDirectoryChanged; public readonly EventSubscriber Initialized; public readonly EventSubscriber Disposed; @@ -27,9 +26,6 @@ public class PluginStateIpcTester : IUiService, IDisposable private readonly List _initializedList = []; private readonly List _disposedList = []; - private string _requiredFeatureString = string.Empty; - private string[] _requiredFeatures = []; - private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch; private bool? _lastEnabledValue; @@ -52,15 +48,12 @@ public class PluginStateIpcTester : IUiService, IDisposable EnabledChange.Dispose(); } - public void Draw() { using var _ = ImRaii.TreeNode("Plugin State"); if (!_) return; - if (ImUtf8.InputText("Required Features"u8, ref _requiredFeatureString)) - _requiredFeatures = _requiredFeatureString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit); if (!table) return; @@ -78,12 +71,6 @@ public class PluginStateIpcTester : IUiService, IDisposable IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change"); ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never"); - IpcTester.DrawIntro(SupportedFeatures.Label, "Supported Features"); - ImUtf8.Text(string.Join(", ", new SupportedFeatures(_pi).Invoke())); - - IpcTester.DrawIntro(CheckSupportedFeatures.Label, "Missing Features"); - ImUtf8.Text(string.Join(", ", new CheckSupportedFeatures(_pi).Invoke(_requiredFeatures))); - DrawConfigPopup(); IpcTester.DrawIntro(GetConfiguration.Label, "Configuration"); if (ImGui.Button("Get")) diff --git a/Penumbra/Api/IpcTester/RedrawingIpcTester.cs b/Penumbra/Api/IpcTester/RedrawingIpcTester.cs index 6b853ed2..b862dde5 100644 --- a/Penumbra/Api/IpcTester/RedrawingIpcTester.cs +++ b/Penumbra/Api/IpcTester/RedrawingIpcTester.cs @@ -1,6 +1,6 @@ using Dalamud.Plugin; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Api.Enums; diff --git a/Penumbra/Api/IpcTester/ResolveIpcTester.cs b/Penumbra/Api/IpcTester/ResolveIpcTester.cs index 9fc5bfc7..a79b099d 100644 --- a/Penumbra/Api/IpcTester/ResolveIpcTester.cs +++ b/Penumbra/Api/IpcTester/ResolveIpcTester.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Api.IpcSubscribers; diff --git a/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs b/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs index e6c8d52e..088a77bd 100644 --- a/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs +++ b/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs @@ -1,10 +1,9 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Plugin; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Api.Enums; diff --git a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs index d46c5728..f6d1c9eb 100644 --- a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs +++ b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs @@ -1,8 +1,7 @@ using Dalamud.Interface; using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -34,11 +33,9 @@ public class TemporaryIpcTester( private string _tempCollectionName = string.Empty; private string _tempCollectionGuidName = string.Empty; private string _tempModName = string.Empty; - private string _modDirectory = string.Empty; private string _tempGamePath = "test/game/path.mtrl"; private string _tempFilePath = "test/success.mtrl"; private string _tempManipulation = string.Empty; - private string _identity = string.Empty; private PenumbraApiEc _lastTempError; private int _tempActorIndex; private bool _forceOverwrite; @@ -49,12 +46,10 @@ public class TemporaryIpcTester( if (!_) return; - ImGui.InputTextWithHint("##identity", "Identity...", ref _identity, 128); 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("##mod", "Existing Mod Name...", ref _modDirectory, 256); 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); @@ -75,7 +70,7 @@ public class TemporaryIpcTester( IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Create Temporary Collection"); if (ImGui.Button("Create##Collection")) { - _lastTempError = new CreateTemporaryCollection(pi).Invoke(_identity, _tempCollectionName, out LastCreatedCollectionId); + LastCreatedCollectionId = new CreateTemporaryCollection(pi).Invoke(_tempCollectionName); if (_tempGuid == null) { _tempGuid = LastCreatedCollectionId; @@ -126,115 +121,6 @@ public class TemporaryIpcTester( IpcTester.DrawIntro(RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections"); if (ImGui.Button("Remove##ModAll")) _lastTempError = new RemoveTemporaryModAll(pi).Invoke(_tempModName, int.MaxValue); - - IpcTester.DrawIntro(SetTemporaryModSettings.Label, "Set Temporary Mod Settings (to default) in specific Collection"); - if (ImUtf8.Button("Set##SetTemporary"u8)) - _lastTempError = new SetTemporaryModSettings(pi).Invoke(guid, _modDirectory, false, true, 1337, - new Dictionary>(), - "IPC Tester", 1337); - - IpcTester.DrawIntro(SetTemporaryModSettingsPlayer.Label, "Set Temporary Mod Settings (to default) in game object collection"); - if (ImUtf8.Button("Set##SetTemporaryPlayer"u8)) - _lastTempError = new SetTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, false, true, 1337, - new Dictionary>(), - "IPC Tester", 1337); - - IpcTester.DrawIntro(RemoveTemporaryModSettings.Label, "Remove Temporary Mod Settings from specific Collection"); - if (ImUtf8.Button("Remove##RemoveTemporary"u8)) - _lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1337); - ImGui.SameLine(); - if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporary"u8)) - _lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1338); - - IpcTester.DrawIntro(RemoveTemporaryModSettingsPlayer.Label, "Remove Temporary Mod Settings from game object Collection"); - if (ImUtf8.Button("Remove##RemoveTemporaryPlayer"u8)) - _lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1337); - ImGui.SameLine(); - if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporaryPlayer"u8)) - _lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1338); - - IpcTester.DrawIntro(RemoveAllTemporaryModSettings.Label, "Remove All Temporary Mod Settings from specific Collection"); - if (ImUtf8.Button("Remove##RemoveAllTemporary"u8)) - _lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1337); - ImGui.SameLine(); - if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporary"u8)) - _lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1338); - - IpcTester.DrawIntro(RemoveAllTemporaryModSettingsPlayer.Label, "Remove All Temporary Mod Settings from game object Collection"); - if (ImUtf8.Button("Remove##RemoveAllTemporaryPlayer"u8)) - _lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1337); - ImGui.SameLine(); - if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporaryPlayer"u8)) - _lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1338); - - IpcTester.DrawIntro(QueryTemporaryModSettings.Label, "Query Temporary Mod Settings from specific Collection"); - ImUtf8.Button("Query##QueryTemporaryModSettings"u8); - if (ImGui.IsItemHovered()) - { - _lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1337); - DrawTooltip(settings, source); - } - - ImGui.SameLine(); - ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporary"u8); - if (ImGui.IsItemHovered()) - { - _lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1338); - DrawTooltip(settings, source); - } - - IpcTester.DrawIntro(QueryTemporaryModSettingsPlayer.Label, "Query Temporary Mod Settings from game object Collection"); - ImUtf8.Button("Query##QueryTemporaryModSettingsPlayer"u8); - if (ImGui.IsItemHovered()) - { - _lastTempError = - new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1337); - DrawTooltip(settings, source); - } - - ImGui.SameLine(); - ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporaryPlayer"u8); - if (ImGui.IsItemHovered()) - { - _lastTempError = - new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1338); - DrawTooltip(settings, source); - } - - void DrawTooltip((bool ForceInherit, bool Enabled, int Priority, Dictionary> Settings)? settings, string source) - { - using var tt = ImUtf8.Tooltip(); - ImUtf8.Text($"Query returned {_lastTempError}"); - if (settings != null) - ImUtf8.Text($"Settings created by {(source.Length == 0 ? "Unknown Source" : source)}:"); - else - ImUtf8.Text(source.Length > 0 ? $"Locked by {source}." : "No settings exist."); - ImGui.Separator(); - if (settings == null) - { - - return; - } - - using (ImUtf8.Group()) - { - ImUtf8.Text("Force Inherit"u8); - ImUtf8.Text("Enabled"u8); - ImUtf8.Text("Priority"u8); - foreach (var group in settings.Value.Settings.Keys) - ImUtf8.Text(group); - } - - ImGui.SameLine(); - using (ImUtf8.Group()) - { - ImUtf8.Text($"{settings.Value.ForceInherit}"); - ImUtf8.Text($"{settings.Value.Enabled}"); - ImUtf8.Text($"{settings.Value.Priority}"); - foreach (var group in settings.Value.Settings.Values) - ImUtf8.Text(string.Join("; ", group)); - } - } } public void DrawCollections() @@ -260,10 +146,10 @@ public class TemporaryIpcTester( using (ImRaii.PushFont(UiBuilder.MonoFont)) { ImGui.TableNextColumn(); - ImGuiUtil.CopyOnClickSelectable(collection.Identity.Identifier); + ImGuiUtil.CopyOnClickSelectable(collection.Identifier); } - ImGuiUtil.DrawTableColumn(collection.Identity.Name); + ImGuiUtil.DrawTableColumn(collection.Name); ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString()); ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0"); ImGuiUtil.DrawTableColumn(string.Join(", ", @@ -284,7 +170,7 @@ public class TemporaryIpcTester( foreach (var mod in list) { ImGui.TableNextColumn(); - ImGui.TextUnformatted(mod.Name.Text); + ImGui.TextUnformatted(mod.Name); ImGui.TableNextColumn(); ImGui.TextUnformatted(mod.Priority.ToString()); ImGui.TableNextColumn(); @@ -313,7 +199,7 @@ public class TemporaryIpcTester( { PrintList("All", tempMods.ModsForAllCollections); foreach (var (collection, list) in tempMods.Mods) - PrintList(collection.Identity.Name, list); + PrintList(collection.Name, list); } } } diff --git a/Penumbra/Api/IpcTester/UiIpcTester.cs b/Penumbra/Api/IpcTester/UiIpcTester.cs index 852339c9..647a4dda 100644 --- a/Penumbra/Api/IpcTester/UiIpcTester.cs +++ b/Penumbra/Api/IpcTester/UiIpcTester.cs @@ -1,5 +1,5 @@ using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Api.Enums; diff --git a/Penumbra/Api/ModChangedItemAdapter.cs b/Penumbra/Api/ModChangedItemAdapter.cs deleted file mode 100644 index 8d2d473c..00000000 --- a/Penumbra/Api/ModChangedItemAdapter.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Penumbra.GameData.Data; -using Penumbra.Mods.Manager; - -namespace Penumbra.Api; - -public sealed class ModChangedItemAdapter(WeakReference storage) - : IReadOnlyDictionary>, - IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)> -{ - IEnumerator<(string ModDirectory, IReadOnlyDictionary ChangedItems)> - IEnumerable<(string ModDirectory, IReadOnlyDictionary ChangedItems)>.GetEnumerator() - => Storage.Select(m => (m.Identifier, (IReadOnlyDictionary)new ChangedItemDictionaryAdapter(m.ChangedItems))) - .GetEnumerator(); - - public IEnumerator>> GetEnumerator() - => Storage.Select(m => new KeyValuePair>(m.Identifier, - new ChangedItemDictionaryAdapter(m.ChangedItems))) - .GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => Storage.Count; - - public bool ContainsKey(string key) - => Storage.TryGetMod(key, string.Empty, out _); - - public bool TryGetValue(string key, [NotNullWhen(true)] out IReadOnlyDictionary? value) - { - if (Storage.TryGetMod(key, string.Empty, out var mod)) - { - value = new ChangedItemDictionaryAdapter(mod.ChangedItems); - return true; - } - - value = null; - return false; - } - - public IReadOnlyDictionary this[string key] - => TryGetValue(key, out var v) ? v : throw new KeyNotFoundException(); - - (string ModDirectory, IReadOnlyDictionary ChangedItems) - IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>.this[int index] - { - get - { - var m = Storage[index]; - return (m.Identifier, new ChangedItemDictionaryAdapter(m.ChangedItems)); - } - } - - public IEnumerable Keys - => Storage.Select(m => m.Identifier); - - public IEnumerable> Values - => Storage.Select(m => new ChangedItemDictionaryAdapter(m.ChangedItems)); - - private ModStorage Storage - { - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - get => storage.TryGetTarget(out var t) - ? t - : throw new ObjectDisposedException("The underlying mod storage of this IPC container was disposed."); - } - - private sealed class ChangedItemDictionaryAdapter(SortedList data) : IReadOnlyDictionary - { - public IEnumerator> GetEnumerator() - => data.Select(d => new KeyValuePair(d.Key, d.Value?.ToInternalObject())).GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() - => GetEnumerator(); - - public int Count - => data.Count; - - public bool ContainsKey(string key) - => data.ContainsKey(key); - - public bool TryGetValue(string key, out object? value) - { - if (data.TryGetValue(key, out var v)) - { - value = v?.ToInternalObject(); - return true; - } - - value = null; - return false; - } - - public object? this[string key] - => data[key]?.ToInternalObject(); - - public IEnumerable Keys - => data.Keys; - - public IEnumerable Values - => data.Values.Select(v => v?.ToInternalObject()); - } -} diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs index b3c6066a..0b52e64a 100644 --- a/Penumbra/Api/TempModManager.cs +++ b/Penumbra/Api/TempModManager.cs @@ -85,13 +85,13 @@ public class TempModManager : IDisposable, IService { if (removed) { - Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.Identity.AnonymizedName}."); + Penumbra.Log.Verbose($"Removing temporary Mod {mod.Name} from {collection.AnonymizedName}."); collection.Remove(mod); _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.False, 0, false); } else { - Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.Identity.AnonymizedName}."); + Penumbra.Log.Verbose($"Adding {(created ? "new " : string.Empty)}temporary Mod {mod.Name} to {collection.AnonymizedName}."); collection.Apply(mod, created); _communicator.ModSettingChanged.Invoke(collection, ModSettingChange.TemporaryMod, null, Setting.True, 0, false); } diff --git a/Penumbra/ChangedItemMode.cs b/Penumbra/ChangedItemMode.cs deleted file mode 100644 index ddb79ee0..00000000 --- a/Penumbra/ChangedItemMode.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Dalamud.Bindings.ImGui; -using OtterGui.Text; - -namespace Penumbra; - -public enum ChangedItemMode -{ - GroupedCollapsed, - GroupedExpanded, - Alphabetical, -} - -public static class ChangedItemModeExtensions -{ - public static ReadOnlySpan ToName(this ChangedItemMode mode) - => mode switch - { - ChangedItemMode.GroupedCollapsed => "Grouped (Collapsed)"u8, - ChangedItemMode.GroupedExpanded => "Grouped (Expanded)"u8, - ChangedItemMode.Alphabetical => "Alphabetical"u8, - _ => "Error"u8, - }; - - public static ReadOnlySpan ToTooltip(this ChangedItemMode mode) - => mode switch - { - ChangedItemMode.GroupedCollapsed => - "Display items as groups by their model and slot. Collapse those groups to a single item by default. Prefers items with more changes affecting them or configured items as the main item."u8, - ChangedItemMode.GroupedExpanded => - "Display items as groups by their model and slot. Expand those groups showing all items by default. Prefers items with more changes affecting them or configured items as the main item."u8, - ChangedItemMode.Alphabetical => "Display all changed items in a single list sorted alphabetically."u8, - _ => ""u8, - }; - - public static bool DrawCombo(ReadOnlySpan label, ChangedItemMode value, float width, Action setter) - { - ImGui.SetNextItemWidth(width); - using var combo = ImUtf8.Combo(label, value.ToName()); - if (!combo) - return false; - - var ret = false; - foreach (var newValue in Enum.GetValues()) - { - var selected = ImUtf8.Selectable(newValue.ToName(), newValue == value); - if (selected) - { - ret = true; - setter(newValue); - } - - ImUtf8.HoverTooltip(newValue.ToTooltip()); - } - - return ret; - } -} diff --git a/Penumbra/Collections/Cache/AtchCache.cs b/Penumbra/Collections/Cache/AtchCache.cs index 10990553..9e0f6caf 100644 --- a/Penumbra/Collections/Cache/AtchCache.cs +++ b/Penumbra/Collections/Cache/AtchCache.cs @@ -2,6 +2,7 @@ using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Files.AtchStructs; using Penumbra.Meta; +using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Collections.Cache; @@ -36,7 +37,7 @@ public sealed class AtchCache(MetaFileManager manager, ModCollection collection) protected override void ApplyModInternal(AtchIdentifier identifier, AtchEntry entry) { - Collection.Counters.IncrementAtch(); + ++Collection.AtchChangeCounter; ApplyFile(identifier, entry); } @@ -67,7 +68,7 @@ public sealed class AtchCache(MetaFileManager manager, ModCollection collection) protected override void RevertModInternal(AtchIdentifier identifier) { - Collection.Counters.IncrementAtch(); + ++Collection.AtchChangeCounter; if (!_atchFiles.TryGetValue(identifier.GenderRace, out var pair)) return; diff --git a/Penumbra/Collections/Cache/AtrCache.cs b/Penumbra/Collections/Cache/AtrCache.cs deleted file mode 100644 index b017da32..00000000 --- a/Penumbra/Collections/Cache/AtrCache.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.Collections.Cache; - -public sealed class AtrCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) -{ - public bool ShouldBeDisabled(in ShapeAttributeString attribute, HumanSlot slot, PrimaryId id, GenderRace genderRace) - => DisabledCount > 0 && _atrData.TryGetValue(attribute, out var value) && value.CheckEntry(slot, id, genderRace) is false; - - public int EnabledCount { get; private set; } - public int DisabledCount { get; private set; } - - - internal IReadOnlyDictionary Data - => _atrData; - - private readonly Dictionary _atrData = []; - - public void Reset() - { - Clear(); - _atrData.Clear(); - DisabledCount = 0; - EnabledCount = 0; - } - - protected override void Dispose(bool _) - => Reset(); - - protected override void ApplyModInternal(AtrIdentifier identifier, AtrEntry entry) - { - if (!_atrData.TryGetValue(identifier.Attribute, out var value)) - { - value = []; - _atrData.Add(identifier.Attribute, value); - } - - if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value, out _)) - { - if (entry.Value) - ++EnabledCount; - else - ++DisabledCount; - } - } - - protected override void RevertModInternal(AtrIdentifier identifier) - { - if (!_atrData.TryGetValue(identifier.Attribute, out var value)) - return; - - if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, null, out var which)) - { - if (which) - --EnabledCount; - else - --DisabledCount; - if (value.IsEmpty) - _atrData.Remove(identifier.Attribute); - } - } -} diff --git a/Penumbra/Collections/Cache/CollectionCache.cs b/Penumbra/Collections/Cache/CollectionCache.cs index 8294624b..64cf54ea 100644 --- a/Penumbra/Collections/Cache/CollectionCache.cs +++ b/Penumbra/Collections/Cache/CollectionCache.cs @@ -1,4 +1,4 @@ -using Dalamud.Interface.ImGuiNotification; +using OtterGui; using OtterGui.Classes; using Penumbra.Meta.Manipulations; using Penumbra.Mods; @@ -7,7 +7,6 @@ using Penumbra.Mods.Editor; using Penumbra.String.Classes; using Penumbra.Util; using Penumbra.GameData.Data; -using OtterGui.Extensions; namespace Penumbra.Collections.Cache; @@ -23,7 +22,7 @@ public sealed class CollectionCache : IDisposable private readonly CollectionCacheManager _manager; private readonly ModCollection _collection; public readonly CollectionModData ModData = new(); - private readonly SortedList, IIdentifiedObjectData)> _changedItems = []; + private readonly SortedList, IIdentifiedObjectData?)> _changedItems = []; public readonly ConcurrentDictionary ResolvedFiles = new(); public readonly CustomResourceCache CustomResources; public readonly MetaCache Meta; @@ -32,7 +31,7 @@ public sealed class CollectionCache : IDisposable public int Calculating = -1; public string AnonymizedName - => _collection.Identity.AnonymizedName; + => _collection.AnonymizedName; public IEnumerable> AllConflicts => ConflictDict.Values; @@ -43,7 +42,7 @@ public sealed class CollectionCache : IDisposable private int _changedItemsSaveCounter = -1; // Obtain currently changed items. Computes them if they haven't been computed before. - public IReadOnlyDictionary, IIdentifiedObjectData)> ChangedItems + public IReadOnlyDictionary, IIdentifiedObjectData?)> ChangedItems { get { @@ -178,7 +177,7 @@ public sealed class CollectionCache : IDisposable var (paths, manipulations) = ModData.RemoveMod(mod); if (addMetaChanges) - _collection.Counters.IncrementChange(); + _collection.IncrementCounter(); foreach (var path in paths) { @@ -245,17 +244,13 @@ public sealed class CollectionCache : IDisposable AddManipulation(mod, identifier, entry); foreach (var (identifier, entry) in files.Manipulations.Atch) AddManipulation(mod, identifier, entry); - foreach (var (identifier, entry) in files.Manipulations.Shp) - AddManipulation(mod, identifier, entry); - foreach (var (identifier, entry) in files.Manipulations.Atr) - AddManipulation(mod, identifier, entry); foreach (var identifier in files.Manipulations.GlobalEqp) AddManipulation(mod, identifier, null!); } if (addMetaChanges) { - _collection.Counters.IncrementChange(); + _collection.IncrementCounter(); _manager.MetaFileManager.ApplyDefaultFiles(_collection); } } @@ -265,7 +260,7 @@ public sealed class CollectionCache : IDisposable if (mod.Index < 0) return mod.GetData(); - var settings = _collection.GetActualSettings(mod.Index).Settings; + var settings = _collection[mod.Index].Settings; return settings is not { Enabled: true } ? AppliedModData.Empty : mod.GetData(settings); @@ -279,24 +274,6 @@ public sealed class CollectionCache : IDisposable _manager.ResolvedFileChanged.Invoke(collection, type, key, value, old, mod); } - private static bool IsRedirectionSupported(Utf8GamePath path, IMod mod) - { - var ext = path.Extension().AsciiToLower().ToString(); - switch (ext) - { - case ".atch" or ".eqp" or ".eqdp" or ".est" or ".gmp" or ".cmp" or ".imc": - Penumbra.Messager.NotificationMessage( - $"Redirection of {ext} files for {mod.Name} is unsupported. This probably means that the mod is outdated and may not work correctly.\n\nPlease tell the mod creator to use the corresponding meta manipulations instead.", - NotificationType.Warning); - return false; - case ".lvb" or ".lgb" or ".sgb": - Penumbra.Messager.NotificationMessage($"Redirection of {ext} files for {mod.Name} is unsupported as this breaks the game.\n\nThis mod will probably not work correctly.", - NotificationType.Warning); - return false; - default: return true; - } - } - // Add a specific file redirection, handling potential conflicts. // For different mods, higher mod priority takes precedence before option group priority, // which takes precedence before option priority, which takes precedence before ordering. @@ -306,9 +283,6 @@ public sealed class CollectionCache : IDisposable if (!CheckFullPath(path, file)) return; - if (!IsRedirectionSupported(path, mod)) - return; - try { if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) @@ -368,9 +342,8 @@ public sealed class CollectionCache : IDisposable // Returns if the added mod takes priority before the existing mod. private bool AddConflict(object data, IMod addedMod, IMod existingMod) { - var addedPriority = addedMod.Index >= 0 ? _collection.GetActualSettings(addedMod.Index).Settings!.Priority : addedMod.Priority; - var existingPriority = - existingMod.Index >= 0 ? _collection.GetActualSettings(existingMod.Index).Settings!.Priority : existingMod.Priority; + var addedPriority = addedMod.Index >= 0 ? _collection[addedMod.Index].Settings!.Priority : addedMod.Priority; + var existingPriority = existingMod.Index >= 0 ? _collection[existingMod.Index].Settings!.Priority : existingMod.Priority; if (existingPriority < addedPriority) { @@ -435,17 +408,17 @@ public sealed class CollectionCache : IDisposable // Identify and record all manipulated objects for this entire collection. private void SetChangedItems() { - if (_changedItemsSaveCounter == _collection.Counters.Change) + if (_changedItemsSaveCounter == _collection.ChangeCounter) return; try { - _changedItemsSaveCounter = _collection.Counters.Change; + _changedItemsSaveCounter = _collection.ChangeCounter; _changedItems.Clear(); // Skip IMCs because they would result in far too many false-positive items, // since they are per set instead of per item-slot/item/variant. var identifier = _manager.MetaFileManager.Identifier; - var items = new SortedList(512); + var items = new SortedList(512); void AddItems(IMod mod) { @@ -454,8 +427,7 @@ public sealed class CollectionCache : IDisposable if (!_changedItems.TryGetValue(name, out var data)) _changedItems.Add(name, (new SingleArray(mod), obj)); else if (!data.Item1.Contains(mod)) - _changedItems[name] = (data.Item1.Append(mod), - obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y ? x + y : obj); + _changedItems[name] = (data.Item1.Append(mod), obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y ? x + y : obj); else if (obj is IdentifiedCounter x && data.Item2 is IdentifiedCounter y) _changedItems[name] = (data.Item1, x + y); } diff --git a/Penumbra/Collections/Cache/CollectionCacheManager.cs b/Penumbra/Collections/Cache/CollectionCacheManager.cs index ec48e608..a3b6bb83 100644 --- a/Penumbra/Collections/Cache/CollectionCacheManager.cs +++ b/Penumbra/Collections/Cache/CollectionCacheManager.cs @@ -71,7 +71,7 @@ public class CollectionCacheManager : IDisposable, IService _communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionCacheManager); if (!MetaFileManager.CharacterUtility.Ready) - MetaFileManager.CharacterUtility.LoadingFinished.Subscribe(IncrementCounters, CharacterUtilityFinished.Priority.CollectionCacheManager); + MetaFileManager.CharacterUtility.LoadingFinished += IncrementCounters; } public void Dispose() @@ -83,7 +83,7 @@ public class CollectionCacheManager : IDisposable, IService _communicator.ModOptionChanged.Unsubscribe(OnModOptionChange); _communicator.ModSettingChanged.Unsubscribe(OnModSettingChange); _communicator.CollectionInheritanceChanged.Unsubscribe(OnCollectionInheritanceChange); - MetaFileManager.CharacterUtility.LoadingFinished.Unsubscribe(IncrementCounters); + MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters; foreach (var collection in _storage) { @@ -114,16 +114,16 @@ public class CollectionCacheManager : IDisposable, IService /// Only creates a new cache, does not update an existing one. public bool CreateCache(ModCollection collection) { - if (collection.Identity.Index == ModCollection.Empty.Identity.Index) + if (collection.Index == ModCollection.Empty.Index) return false; if (collection._cache != null) return false; collection._cache = new CollectionCache(this, collection); - if (collection.Identity.Index > 0) + if (collection.Index > 0) Interlocked.Increment(ref _count); - Penumbra.Log.Verbose($"Created new cache for collection {collection.Identity.AnonymizedName}."); + Penumbra.Log.Verbose($"Created new cache for collection {collection.AnonymizedName}."); return true; } @@ -132,32 +132,32 @@ public class CollectionCacheManager : IDisposable, IService /// Does not create caches. /// public void CalculateEffectiveFileList(ModCollection collection) - => _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identity.Identifier, + => _framework.RegisterImportant(nameof(CalculateEffectiveFileList) + collection.Identifier, () => CalculateEffectiveFileListInternal(collection)); private void CalculateEffectiveFileListInternal(ModCollection collection) { // Skip the empty collection. - if (collection.Identity.Index == 0) + if (collection.Index == 0) return; - Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName}"); + Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}"); if (!collection.HasCache) { Penumbra.Log.Error( - $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, no cache exists."); + $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists."); } else if (collection._cache!.Calculating != -1) { Penumbra.Log.Error( - $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.Identity.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}]."); + $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}]."); } else { FullRecalculation(collection); Penumbra.Log.Debug( - $"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.Identity.AnonymizedName} finished."); + $"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished."); } } @@ -171,7 +171,8 @@ public class CollectionCacheManager : IDisposable, IService try { ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeStart, Utf8GamePath.Empty, FullPath.Empty, - FullPath.Empty, null); + FullPath.Empty, + null); cache.ResolvedFiles.Clear(); cache.Meta.Reset(); cache.ConflictDict.Clear(); @@ -186,7 +187,7 @@ public class CollectionCacheManager : IDisposable, IService foreach (var mod in _modStorage) cache.AddModSync(mod, false); - collection.Counters.IncrementChange(); + collection.IncrementCounter(); MetaFileManager.ApplyDefaultFiles(collection); ResolvedFileChanged.Invoke(collection, ResolvedFileChanged.Type.FullRecomputeFinished, Utf8GamePath.Empty, FullPath.Empty, @@ -212,7 +213,7 @@ public class CollectionCacheManager : IDisposable, IService else { RemoveCache(old); - if (type is not CollectionType.Inactive && newCollection != null && newCollection.Identity.Index != 0 && CreateCache(newCollection)) + if (type is not CollectionType.Inactive && newCollection != null && newCollection.Index != 0 && CreateCache(newCollection)) CalculateEffectiveFileList(newCollection); if (type is CollectionType.Default) @@ -230,11 +231,11 @@ public class CollectionCacheManager : IDisposable, IService { case ModPathChangeType.Deleted: case ModPathChangeType.StartingReload: - foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true)) + foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true)) collection._cache!.RemoveMod(mod, true); break; case ModPathChangeType.Moved: - foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true)) + foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true)) collection._cache!.ReloadMod(mod, true); break; } @@ -245,7 +246,7 @@ public class CollectionCacheManager : IDisposable, IService if (type is not (ModPathChangeType.Added or ModPathChangeType.Reloaded)) return; - foreach (var collection in _storage.Where(c => c.HasCache && c.GetActualSettings(mod.Index).Settings?.Enabled == true)) + foreach (var collection in _storage.Where(c => c.HasCache && c[mod.Index].Settings?.Enabled == true)) collection._cache!.AddMod(mod, true); } @@ -257,12 +258,12 @@ public class CollectionCacheManager : IDisposable, IService private void RemoveCache(ModCollection? collection) { if (collection != null - && collection.Identity.Index > ModCollection.Empty.Identity.Index - && collection.Identity.Index != _active.Default.Identity.Index - && collection.Identity.Index != _active.Interface.Identity.Index - && collection.Identity.Index != _active.Current.Identity.Index - && _active.SpecialAssignments.All(c => c.Value.Identity.Index != collection.Identity.Index) - && _active.Individuals.All(c => c.Collection.Identity.Index != collection.Identity.Index)) + && collection.Index > ModCollection.Empty.Index + && collection.Index != _active.Default.Index + && collection.Index != _active.Interface.Index + && collection.Index != _active.Current.Index + && _active.SpecialAssignments.All(c => c.Value.Index != collection.Index) + && _active.Individuals.All(c => c.Collection.Index != collection.Index)) ClearCache(collection); } @@ -272,7 +273,7 @@ public class CollectionCacheManager : IDisposable, IService { if (type is ModOptionChangeType.PrepareChange) { - foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true })) + foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true })) collection._cache!.RemoveMod(mod, false); return; @@ -283,7 +284,7 @@ public class CollectionCacheManager : IDisposable, IService if (!recomputeList) return; - foreach (var collection in _storage.Where(collection => collection.HasCache && collection.GetActualSettings(mod.Index).Settings is { Enabled: true })) + foreach (var collection in _storage.Where(collection => collection.HasCache && collection[mod.Index].Settings is { Enabled: true })) { if (justAdd) collection._cache!.AddMod(mod, true); @@ -296,8 +297,8 @@ public class CollectionCacheManager : IDisposable, IService private void IncrementCounters() { foreach (var collection in _storage.Where(c => c.HasCache)) - collection.Counters.IncrementChange(); - MetaFileManager.CharacterUtility.LoadingFinished.Unsubscribe(IncrementCounters); + collection.IncrementCounter(); + MetaFileManager.CharacterUtility.LoadingFinished -= IncrementCounters; } private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool _) @@ -316,7 +317,7 @@ public class CollectionCacheManager : IDisposable, IService cache.AddMod(mod!, true); else if (oldValue == Setting.True) cache.RemoveMod(mod!, true); - else if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true) + else if (collection[mod!.Index].Settings?.Enabled == true) cache.ReloadMod(mod!, true); else cache.RemoveMod(mod!, true); @@ -328,13 +329,10 @@ public class CollectionCacheManager : IDisposable, IService break; case ModSettingChange.Setting: - if (collection.GetActualSettings(mod!.Index).Settings?.Enabled == true) - cache.ReloadMod(mod, true); + if (collection[mod!.Index].Settings?.Enabled == true) + cache.ReloadMod(mod!, true); break; - case ModSettingChange.TemporarySetting: - cache.ReloadMod(mod!, true); - break; case ModSettingChange.MultiInheritance: case ModSettingChange.MultiEnableState: FullRecalculation(collection); @@ -361,9 +359,9 @@ public class CollectionCacheManager : IDisposable, IService collection._cache!.Dispose(); collection._cache = null; - if (collection.Identity.Index > 0) + if (collection.Index > 0) Interlocked.Decrement(ref _count); - Penumbra.Log.Verbose($"Cleared cache of collection {collection.Identity.AnonymizedName}."); + Penumbra.Log.Verbose($"Cleared cache of collection {collection.AnonymizedName}."); } /// diff --git a/Penumbra/Collections/Cache/EqpCache.cs b/Penumbra/Collections/Cache/EqpCache.cs index da1a1d44..c681b230 100644 --- a/Penumbra/Collections/Cache/EqpCache.cs +++ b/Penumbra/Collections/Cache/EqpCache.cs @@ -48,8 +48,8 @@ public sealed class EqpCache(MetaFileManager manager, ModCollection collection) ? entry.HasFlag(EqpEntry.BodyHideGlovesL) : entry.HasFlag(EqpEntry.BodyHideGlovesM); return testFlag - ? (entry | EqpEntry.BodyHideGloveCuffs) & ~EqpEntry.BodyHideGlovesS - : entry & ~(EqpEntry.BodyHideGloveCuffs | EqpEntry.BodyHideGlovesS); + ? (entry | EqpEntry._4) & ~EqpEntry.BodyHideGlovesS + : entry & ~(EqpEntry._4 | EqpEntry.BodyHideGlovesS); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] diff --git a/Penumbra/Collections/Cache/GlobalEqpCache.cs b/Penumbra/Collections/Cache/GlobalEqpCache.cs index 7d2fbf64..60e782b5 100644 --- a/Penumbra/Collections/Cache/GlobalEqpCache.cs +++ b/Penumbra/Collections/Cache/GlobalEqpCache.cs @@ -15,9 +15,6 @@ public class GlobalEqpCache : ReadWriteDictionary, private readonly HashSet _doNotHideRingR = []; private bool _doNotHideVieraHats; private bool _doNotHideHrothgarHats; - private bool _hideAuRaHorns; - private bool _hideVieraEars; - private bool _hideMiqoteEars; public new void Clear() { @@ -29,9 +26,6 @@ public class GlobalEqpCache : ReadWriteDictionary, _doNotHideRingR.Clear(); _doNotHideHrothgarHats = false; _doNotHideVieraHats = false; - _hideAuRaHorns = false; - _hideVieraEars = false; - _hideMiqoteEars = false; } public unsafe EqpEntry Apply(EqpEntry original, CharacterArmor* armor) @@ -45,20 +39,8 @@ public class GlobalEqpCache : ReadWriteDictionary, if (_doNotHideHrothgarHats) original |= EqpEntry.HeadShowHrothgarHat; - if (_hideAuRaHorns) - original &= ~EqpEntry.HeadShowEarAuRa; - - if (_hideVieraEars) - original &= ~EqpEntry.HeadShowEarViera; - - if (_hideMiqoteEars) - original &= ~EqpEntry.HeadShowEarMiqote; - if (_doNotHideEarrings.Contains(armor[5].Set)) - original |= EqpEntry.HeadShowEarringsHyurRoe - | EqpEntry.HeadShowEarringsLalaElezen - | EqpEntry.HeadShowEarringsMiqoHrothViera - | EqpEntry.HeadShowEarringsAura; + original |= EqpEntry.HeadShowEarringsHyurRoe | EqpEntry.HeadShowEarringsLalaElezen | EqpEntry.HeadShowEarringsMiqoHrothViera | EqpEntry.HeadShowEarringsAura; if (_doNotHideNecklace.Contains(armor[6].Set)) original |= EqpEntry.BodyShowNecklace | EqpEntry.HeadShowNecklace; @@ -71,7 +53,6 @@ public class GlobalEqpCache : ReadWriteDictionary, if (_doNotHideRingL.Contains(armor[9].Set)) original |= EqpEntry.HandShowRingL; - return original; } @@ -90,9 +71,6 @@ public class GlobalEqpCache : ReadWriteDictionary, GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Add(manipulation.Condition), GlobalEqpType.DoNotHideHrothgarHats => !_doNotHideHrothgarHats && (_doNotHideHrothgarHats = true), GlobalEqpType.DoNotHideVieraHats => !_doNotHideVieraHats && (_doNotHideVieraHats = true), - GlobalEqpType.HideHorns => !_hideAuRaHorns && (_hideAuRaHorns = true), - GlobalEqpType.HideMiqoteEars => !_hideMiqoteEars && (_hideMiqoteEars = true), - GlobalEqpType.HideVieraEars => !_hideVieraEars && (_hideVieraEars = true), _ => false, }; return true; @@ -112,9 +90,6 @@ public class GlobalEqpCache : ReadWriteDictionary, GlobalEqpType.DoNotHideRingL => _doNotHideRingL.Remove(manipulation.Condition), GlobalEqpType.DoNotHideHrothgarHats => _doNotHideHrothgarHats && !(_doNotHideHrothgarHats = false), GlobalEqpType.DoNotHideVieraHats => _doNotHideVieraHats && !(_doNotHideVieraHats = false), - GlobalEqpType.HideHorns => _hideAuRaHorns && (_hideAuRaHorns = false), - GlobalEqpType.HideMiqoteEars => _hideMiqoteEars && (_hideMiqoteEars = false), - GlobalEqpType.HideVieraEars => _hideVieraEars && (_hideVieraEars = false), _ => false, }; return true; diff --git a/Penumbra/Collections/Cache/ImcCache.cs b/Penumbra/Collections/Cache/ImcCache.cs index 461ffccc..cac52f99 100644 --- a/Penumbra/Collections/Cache/ImcCache.cs +++ b/Penumbra/Collections/Cache/ImcCache.cs @@ -39,7 +39,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) protected override void ApplyModInternal(ImcIdentifier identifier, ImcEntry entry) { - Collection.Counters.IncrementImc(); + ++Collection.ImcChangeCounter; ApplyFile(identifier, entry); } @@ -51,6 +51,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) if (!_imcFiles.TryGetValue(path, out var pair)) pair = (new ImcFile(Manager, identifier), []); + if (!Apply(pair.Item1, identifier, entry)) return; @@ -70,7 +71,7 @@ public sealed class ImcCache(MetaFileManager manager, ModCollection collection) protected override void RevertModInternal(ImcIdentifier identifier) { - Collection.Counters.IncrementImc(); + ++Collection.ImcChangeCounter; var path = identifier.GamePath().Path; if (!_imcFiles.TryGetValue(path, out var pair)) return; diff --git a/Penumbra/Collections/Cache/MetaCache.cs b/Penumbra/Collections/Cache/MetaCache.cs index 011cdd23..7d8586c3 100644 --- a/Penumbra/Collections/Cache/MetaCache.cs +++ b/Penumbra/Collections/Cache/MetaCache.cs @@ -16,13 +16,11 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) public readonly RspCache Rsp = new(manager, collection); public readonly ImcCache Imc = new(manager, collection); public readonly AtchCache Atch = new(manager, collection); - public readonly ShpCache Shp = new(manager, collection); - public readonly AtrCache Atr = 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 + Atch.Count + Shp.Count + Atr.Count + GlobalEqp.Count; + => Eqp.Count + Eqdp.Count + Est.Count + Gmp.Count + Rsp.Count + Imc.Count + Atch.Count + GlobalEqp.Count; public IEnumerable<(IMetaIdentifier, IMod)> IdentifierSources => Eqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source)) @@ -32,8 +30,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) .Concat(Rsp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) .Concat(Imc.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) .Concat(Atch.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) - .Concat(Shp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) - .Concat(Atr.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value.Source))) .Concat(GlobalEqp.Select(kvp => ((IMetaIdentifier)kvp.Key, kvp.Value))); public void Reset() @@ -45,8 +41,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) Rsp.Reset(); Imc.Reset(); Atch.Reset(); - Shp.Reset(); - Atr.Reset(); GlobalEqp.Clear(); } @@ -63,8 +57,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) Rsp.Dispose(); Imc.Dispose(); Atch.Dispose(); - Shp.Dispose(); - Atr.Dispose(); } public bool TryGetMod(IMetaIdentifier identifier, [NotNullWhen(true)] out IMod? mod) @@ -79,8 +71,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) ImcIdentifier i => Imc.TryGetValue(i, out var p) && Convert(p, out mod), RspIdentifier i => Rsp.TryGetValue(i, out var p) && Convert(p, out mod), AtchIdentifier i => Atch.TryGetValue(i, out var p) && Convert(p, out mod), - ShpIdentifier i => Shp.TryGetValue(i, out var p) && Convert(p, out mod), - AtrIdentifier i => Atr.TryGetValue(i, out var p) && Convert(p, out mod), GlobalEqpManipulation i => GlobalEqp.TryGetValue(i, out mod), _ => false, }; @@ -102,8 +92,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) ImcIdentifier i => Imc.RevertMod(i, out mod), RspIdentifier i => Rsp.RevertMod(i, out mod), AtchIdentifier i => Atch.RevertMod(i, out mod), - ShpIdentifier i => Shp.RevertMod(i, out mod), - AtrIdentifier i => Atr.RevertMod(i, out mod), GlobalEqpManipulation i => GlobalEqp.RevertMod(i, out mod), _ => (mod = null) != null, }; @@ -120,8 +108,6 @@ public class MetaCache(MetaFileManager manager, ModCollection collection) ImcIdentifier i when entry is ImcEntry e => Imc.ApplyMod(mod, i, e), RspIdentifier i when entry is RspEntry e => Rsp.ApplyMod(mod, i, e), AtchIdentifier i when entry is AtchEntry e => Atch.ApplyMod(mod, i, e), - ShpIdentifier i when entry is ShpEntry e => Shp.ApplyMod(mod, i, e), - AtrIdentifier i when entry is AtrEntry e => Atr.ApplyMod(mod, i, e), GlobalEqpManipulation i => GlobalEqp.ApplyMod(mod, i), _ => false, }; diff --git a/Penumbra/Collections/Cache/ShapeAttributeHashSet.cs b/Penumbra/Collections/Cache/ShapeAttributeHashSet.cs deleted file mode 100644 index 4c61bdd2..00000000 --- a/Penumbra/Collections/Cache/ShapeAttributeHashSet.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System.Collections.Frozen; -using OtterGui.Extensions; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; - -namespace Penumbra.Collections.Cache; - -public sealed class ShapeAttributeHashSet : Dictionary<(HumanSlot Slot, PrimaryId Id), ulong> -{ - public static readonly IReadOnlyList GenderRaceValues = - [ - GenderRace.Unknown, GenderRace.MidlanderMale, GenderRace.MidlanderFemale, GenderRace.HighlanderMale, GenderRace.HighlanderFemale, - GenderRace.ElezenMale, GenderRace.ElezenFemale, GenderRace.MiqoteMale, GenderRace.MiqoteFemale, GenderRace.RoegadynMale, - GenderRace.RoegadynFemale, GenderRace.LalafellMale, GenderRace.LalafellFemale, GenderRace.AuRaMale, GenderRace.AuRaFemale, - GenderRace.HrothgarMale, GenderRace.HrothgarFemale, GenderRace.VieraMale, GenderRace.VieraFemale, - ]; - - public static readonly FrozenDictionary GenderRaceIndices = - GenderRaceValues.WithIndex().ToFrozenDictionary(p => p.Value, p => p.Index); - - private readonly BitArray _allIds = new(2 * (ShapeAttributeManager.ModelSlotSize + 1) * GenderRaceValues.Count); - - public bool? this[HumanSlot slot] - => AllCheck(ToIndex(slot, 0)); - - public bool? this[GenderRace genderRace] - => ToIndex(HumanSlot.Unknown, genderRace, out var index) ? AllCheck(index) : null; - - public bool? this[HumanSlot slot, GenderRace genderRace] - => ToIndex(slot, genderRace, out var index) ? AllCheck(index) : null; - - public bool? All - => Convert(_allIds[2 * AllIndex], _allIds[2 * AllIndex + 1]); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private bool? AllCheck(int idx) - => Convert(_allIds[idx], _allIds[idx + 1]); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static int ToIndex(HumanSlot slot, int genderRaceIndex) - => 2 * (slot is HumanSlot.Unknown ? genderRaceIndex + AllIndex : genderRaceIndex + (int)slot * GenderRaceValues.Count); - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public bool? CheckEntry(HumanSlot slot, PrimaryId id, GenderRace genderRace) - { - if (!GenderRaceIndices.TryGetValue(genderRace, out var index)) - return null; - - // Check for specific ID. - if (TryGetValue((slot, id), out var flags)) - { - // Check completely specified entry. - if (Convert(flags, 2 * index) is { } specified) - return specified; - - // Check any gender / race. - if (Convert(flags, 0) is { } anyGr) - return anyGr; - } - - // Check for specified gender / race and slot, but no ID. - if (AllCheck(ToIndex(slot, index)) is { } noIdButGr) - return noIdButGr; - - // Check for specified gender / race but no slot or ID. - if (AllCheck(ToIndex(HumanSlot.Unknown, index)) is { } noSlotButGr) - return noSlotButGr; - - // Check for specified slot but no gender / race or ID. - if (AllCheck(ToIndex(slot, 0)) is { } noGrButSlot) - return noGrButSlot; - - return All; - } - - public bool TrySet(HumanSlot slot, PrimaryId? id, GenderRace genderRace, bool? value, out bool which) - { - which = false; - if (!GenderRaceIndices.TryGetValue(genderRace, out var index)) - return false; - - if (!id.HasValue) - { - var slotIndex = ToIndex(slot, index); - var ret = false; - if (value is true) - { - if (!_allIds[slotIndex]) - ret = true; - _allIds[slotIndex] = true; - _allIds[slotIndex + 1] = false; - } - else if (value is false) - { - if (!_allIds[slotIndex + 1]) - ret = true; - _allIds[slotIndex] = false; - _allIds[slotIndex + 1] = true; - } - else - { - if (_allIds[slotIndex]) - { - which = true; - ret = true; - } - else if (_allIds[slotIndex + 1]) - { - which = false; - ret = true; - } - - _allIds[slotIndex] = false; - _allIds[slotIndex + 1] = false; - } - - return ret; - } - - if (TryGetValue((slot, id.Value), out var flags)) - { - index *= 2; - var newFlags = value switch - { - true => (flags | (1ul << index)) & ~(1ul << (index + 1)), - false => (flags & ~(1ul << index)) | (1ul << (index + 1)), - _ => flags & ~(1ul << index) & ~(1ul << (index + 1)), - }; - if (newFlags == flags) - return false; - - this[(slot, id.Value)] = newFlags; - which = (flags & (1ul << index)) is not 0; - return true; - } - - if (value is null) - return false; - - this[(slot, id.Value)] = 1ul << (2 * index + (value.Value ? 0 : 1)); - return true; - } - - public new void Clear() - { - base.Clear(); - _allIds.SetAll(false); - } - - public bool IsEmpty - => !_allIds.HasAnySet() && Count is 0; - - private static readonly int AllIndex = ShapeAttributeManager.ModelSlotSize * GenderRaceValues.Count; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool ToIndex(HumanSlot slot, GenderRace genderRace, out int index) - { - if (!GenderRaceIndices.TryGetValue(genderRace, out index)) - return false; - - index = ToIndex(slot, index); - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool? Convert(bool trueValue, bool falseValue) - => trueValue ? true : falseValue ? false : null; - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool? Convert(ulong mask, int idx) - { - mask >>= idx; - return (mask & 3) switch - { - 1 => true, - 2 => false, - _ => null, - }; - } -} diff --git a/Penumbra/Collections/Cache/ShpCache.cs b/Penumbra/Collections/Cache/ShpCache.cs deleted file mode 100644 index d8c3a036..00000000 --- a/Penumbra/Collections/Cache/ShpCache.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.Collections.Cache; - -public sealed class ShpCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase(manager, collection) -{ - public bool ShouldBeEnabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace) - => EnabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.CheckEntry(slot, id, genderRace) is true; - - public bool ShouldBeDisabled(in ShapeAttributeString shape, HumanSlot slot, PrimaryId id, GenderRace genderRace) - => DisabledCount > 0 && _shpData.TryGetValue(shape, out var value) && value.CheckEntry(slot, id, genderRace) is false; - - internal IReadOnlyDictionary State(ShapeConnectorCondition connector) - => connector switch - { - ShapeConnectorCondition.None => _shpData, - ShapeConnectorCondition.Wrists => _wristConnectors, - ShapeConnectorCondition.Waist => _waistConnectors, - ShapeConnectorCondition.Ankles => _ankleConnectors, - _ => [], - }; - - public int EnabledCount { get; private set; } - public int DisabledCount { get; private set; } - - private readonly Dictionary _shpData = []; - private readonly Dictionary _wristConnectors = []; - private readonly Dictionary _waistConnectors = []; - private readonly Dictionary _ankleConnectors = []; - - public void Reset() - { - Clear(); - _shpData.Clear(); - _wristConnectors.Clear(); - _waistConnectors.Clear(); - _ankleConnectors.Clear(); - EnabledCount = 0; - DisabledCount = 0; - } - - protected override void Dispose(bool _) - => Reset(); - - protected override void ApplyModInternal(ShpIdentifier identifier, ShpEntry entry) - { - switch (identifier.ConnectorCondition) - { - case ShapeConnectorCondition.None: Func(_shpData); break; - case ShapeConnectorCondition.Wrists: Func(_wristConnectors); break; - case ShapeConnectorCondition.Waist: Func(_waistConnectors); break; - case ShapeConnectorCondition.Ankles: Func(_ankleConnectors); break; - } - - return; - - void Func(Dictionary dict) - { - if (!dict.TryGetValue(identifier.Shape, out var value)) - { - value = []; - dict.Add(identifier.Shape, value); - } - - if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, entry.Value, out _)) - { - if (entry.Value) - ++EnabledCount; - else - ++DisabledCount; - } - } - } - - protected override void RevertModInternal(ShpIdentifier identifier) - { - switch (identifier.ConnectorCondition) - { - case ShapeConnectorCondition.None: Func(_shpData); break; - case ShapeConnectorCondition.Wrists: Func(_wristConnectors); break; - case ShapeConnectorCondition.Waist: Func(_waistConnectors); break; - case ShapeConnectorCondition.Ankles: Func(_ankleConnectors); break; - } - - return; - - void Func(Dictionary dict) - { - if (!dict.TryGetValue(identifier.Shape, out var value)) - return; - - if (value.TrySet(identifier.Slot, identifier.Id, identifier.GenderRaceCondition, null, out var which)) - { - if (which) - --EnabledCount; - else - --DisabledCount; - if (value.IsEmpty) - dict.Remove(identifier.Shape); - } - } - } -} diff --git a/Penumbra/Collections/CollectionAutoSelector.cs b/Penumbra/Collections/CollectionAutoSelector.cs deleted file mode 100644 index f6e6bf72..00000000 --- a/Penumbra/Collections/CollectionAutoSelector.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Dalamud.Plugin.Services; -using OtterGui.Services; -using Penumbra.Collections.Manager; -using Penumbra.GameData.Interop; -using Penumbra.Interop.PathResolving; - -namespace Penumbra.Collections; - -public sealed class CollectionAutoSelector : IService, IDisposable -{ - private readonly Configuration _config; - private readonly ActiveCollections _collections; - private readonly IClientState _clientState; - private readonly CollectionResolver _resolver; - private readonly ObjectManager _objects; - - public CollectionAutoSelector(Configuration config, ActiveCollections collections, IClientState clientState, CollectionResolver resolver, - ObjectManager objects) - { - _config = config; - _collections = collections; - _clientState = clientState; - _resolver = resolver; - _objects = objects; - - if (_config.AutoSelectCollection) - Attach(); - } - - public bool Disposed { get; private set; } - - public void SetAutomaticSelection(bool value) - { - _config.AutoSelectCollection = value; - if (value) - Attach(); - else - Detach(); - } - - private void Attach() - { - if (Disposed) - return; - - _clientState.Login += OnLogin; - Select(); - } - - private void OnLogin() - => Select(); - - private void Detach() - => _clientState.Login -= OnLogin; - - private void Select() - { - if (!_objects[0].IsCharacter) - return; - - var collection = _resolver.PlayerCollection(); - if (collection.Identity.Id == Guid.Empty) - { - Penumbra.Log.Debug($"Not setting current collection because character has no mods assigned."); - } - else - { - Penumbra.Log.Debug($"Setting current collection to {collection.Identity.Identifier} through automatic collection selection."); - _collections.SetCollection(collection, CollectionType.Current); - } - } - - - public void Dispose() - { - if (Disposed) - return; - - Disposed = true; - Detach(); - } -} diff --git a/Penumbra/Collections/CollectionCounters.cs b/Penumbra/Collections/CollectionCounters.cs deleted file mode 100644 index 6ca0d0a0..00000000 --- a/Penumbra/Collections/CollectionCounters.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Penumbra.Collections; - -public struct CollectionCounters(int changeCounter) -{ - /// Count the number of changes of the effective file list. - public int Change { get; private set; } = changeCounter; - - /// Count the number of IMC-relevant changes of the effective file list. - public int Imc { get; private set; } - - /// Count the number of ATCH-relevant changes of the effective file list. - public int Atch { get; private set; } - - /// Increment the number of changes in the effective file list. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IncrementChange() - => ++Change; - - /// Increment the number of IMC-relevant changes in the effective file list. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IncrementImc() - => ++Imc; - - /// Increment the number of ATCH-relevant changes in the effective file list. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IncrementAtch() - => ++Atch; -} diff --git a/Penumbra/Collections/Manager/ActiveCollectionMigration.cs b/Penumbra/Collections/Manager/ActiveCollectionMigration.cs index b4af0998..19f781fc 100644 --- a/Penumbra/Collections/Manager/ActiveCollectionMigration.cs +++ b/Penumbra/Collections/Manager/ActiveCollectionMigration.cs @@ -48,7 +48,7 @@ public static class ActiveCollectionMigration if (!storage.ByName(collectionName, out var collection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Identity.Name}.", NotificationType.Warning); + $"Last choice of <{player}>'s Collection {collectionName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning); dict.Add(player, ModCollection.Empty); } else diff --git a/Penumbra/Collections/Manager/ActiveCollections.cs b/Penumbra/Collections/Manager/ActiveCollections.cs index ffec7fd2..60f9a427 100644 --- a/Penumbra/Collections/Manager/ActiveCollections.cs +++ b/Penumbra/Collections/Manager/ActiveCollections.cs @@ -1,8 +1,8 @@ using Dalamud.Interface.ImGuiNotification; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.Communication; using Penumbra.GameData.Actors; @@ -219,7 +219,7 @@ public class ActiveCollections : ISavable, IDisposable, IService _ => null, }; - if (oldCollection == null || collection == oldCollection || collection.Identity.Index >= _storage.Count) + if (oldCollection == null || collection == oldCollection || collection.Index >= _storage.Count) return; switch (collectionType) @@ -262,13 +262,13 @@ public class ActiveCollections : ISavable, IDisposable, IService var jObj = new JObject { { nameof(Version), Version }, - { nameof(Default), Default.Identity.Id }, - { nameof(Interface), Interface.Identity.Id }, - { nameof(Current), Current.Identity.Id }, + { nameof(Default), Default.Id }, + { nameof(Interface), Interface.Id }, + { nameof(Current), Current.Id }, }; foreach (var (type, collection) in SpecialCollections.WithIndex().Where(p => p.Value != null) .Select(p => ((CollectionType)p.Index, p.Value!))) - jObj.Add(type.ToString(), collection.Identity.Id); + jObj.Add(type.ToString(), collection.Id); jObj.Add(nameof(Individuals), Individuals.ToJObject()); using var j = new JsonTextWriter(writer); @@ -282,7 +282,7 @@ public class ActiveCollections : ISavable, IDisposable, IService .Prepend(Interface) .Prepend(Default) .Concat(Individuals.Assignments.Select(kvp => kvp.Collection)) - .SelectMany(c => c.Inheritance.FlatHierarchy).Contains(Current); + .SelectMany(c => c.GetFlattenedInheritance()).Contains(Current); /// Save if any of the active collections is changed and set new collections to Current. private void OnCollectionChange(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string _3) @@ -300,7 +300,7 @@ public class ActiveCollections : ISavable, IDisposable, IService if (oldCollection == Interface) SetCollection(ModCollection.Empty, CollectionType.Interface); if (oldCollection == Current) - SetCollection(Default.Identity.Index > ModCollection.Empty.Identity.Index ? Default : _storage.DefaultNamed, CollectionType.Current); + SetCollection(Default.Index > ModCollection.Empty.Index ? Default : _storage.DefaultNamed, CollectionType.Current); for (var i = 0; i < SpecialCollections.Length; ++i) { @@ -325,11 +325,11 @@ public class ActiveCollections : ISavable, IDisposable, IService { var configChanged = false; // Load the default collection. If the name does not exist take the empty collection. - var defaultName = jObject[nameof(Default)]?.ToObject() ?? ModCollection.Empty.Identity.Name; + var defaultName = jObject[nameof(Default)]?.ToObject() ?? ModCollection.Empty.Name; if (!_storage.ByName(defaultName, out var defaultCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Identity.Name}.", + $"Last choice of {TutorialService.DefaultCollection} {defaultName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning); Default = ModCollection.Empty; configChanged = true; @@ -340,11 +340,11 @@ public class ActiveCollections : ISavable, IDisposable, IService } // Load the interface collection. If no string is set, use the name of whatever was set as Default. - var interfaceName = jObject[nameof(Interface)]?.ToObject() ?? Default.Identity.Name; + var interfaceName = jObject[nameof(Interface)]?.ToObject() ?? Default.Name; if (!_storage.ByName(interfaceName, out var interfaceCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Identity.Name}.", + $"Last choice of {TutorialService.InterfaceCollection} {interfaceName} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning); Interface = ModCollection.Empty; configChanged = true; @@ -355,11 +355,11 @@ public class ActiveCollections : ISavable, IDisposable, IService } // Load the current collection. - var currentName = jObject[nameof(Current)]?.ToObject() ?? Default.Identity.Name; + var currentName = jObject[nameof(Current)]?.ToObject() ?? Default.Name; if (!_storage.ByName(currentName, out var currentCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.", + $"Last choice of {TutorialService.SelectedCollection} {currentName} is not available, reset to {ModCollection.DefaultCollectionName}.", NotificationType.Warning); Current = _storage.DefaultNamed; configChanged = true; @@ -404,7 +404,7 @@ public class ActiveCollections : ISavable, IDisposable, IService if (!_storage.ById(defaultId, out var defaultCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Identity.Name}.", + $"Last choice of {TutorialService.DefaultCollection} {defaultId} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning); Default = ModCollection.Empty; configChanged = true; @@ -415,11 +415,11 @@ public class ActiveCollections : ISavable, IDisposable, IService } // Load the interface collection. If no string is set, use the name of whatever was set as Default. - var interfaceId = jObject[nameof(Interface)]?.ToObject() ?? Default.Identity.Id; + var interfaceId = jObject[nameof(Interface)]?.ToObject() ?? Default.Id; if (!_storage.ById(interfaceId, out var interfaceCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Identity.Name}.", + $"Last choice of {TutorialService.InterfaceCollection} {interfaceId} is not available, reset to {ModCollection.Empty.Name}.", NotificationType.Warning); Interface = ModCollection.Empty; configChanged = true; @@ -430,11 +430,11 @@ public class ActiveCollections : ISavable, IDisposable, IService } // Load the current collection. - var currentId = jObject[nameof(Current)]?.ToObject() ?? _storage.DefaultNamed.Identity.Id; + var currentId = jObject[nameof(Current)]?.ToObject() ?? _storage.DefaultNamed.Id; if (!_storage.ById(currentId, out var currentCollection)) { Penumbra.Messager.NotificationMessage( - $"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollectionIdentity.DefaultCollectionName}.", + $"Last choice of {TutorialService.SelectedCollection} {currentId} is not available, reset to {ModCollection.DefaultCollectionName}.", NotificationType.Warning); Current = _storage.DefaultNamed; configChanged = true; @@ -587,7 +587,7 @@ public class ActiveCollections : ISavable, IDisposable, IService case IdentifierType.Player when id.HomeWorld != ushort.MaxValue: { var global = ByType(CollectionType.Individual, _actors.CreatePlayer(id.PlayerName, ushort.MaxValue)); - return (global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index + return global?.Index == checkAssignment.Index ? "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it." : string.Empty; } @@ -596,12 +596,12 @@ public class ActiveCollections : ISavable, IDisposable, IService { var global = ByType(CollectionType.Individual, _actors.CreateOwned(id.PlayerName, ushort.MaxValue, id.Kind, id.DataId)); - if ((global != null ? global.Identity.Index : null) == checkAssignment.Identity.Index) + if (global?.Index == checkAssignment.Index) return "Assignment is redundant due to an identical Any-World assignment existing.\nYou can remove it."; } var unowned = ByType(CollectionType.Individual, _actors.CreateNpc(id.Kind, id.DataId)); - return (unowned != null ? unowned.Identity.Index : null) == checkAssignment.Identity.Index + return unowned?.Index == checkAssignment.Index ? "Assignment is redundant due to an identical unowned NPC assignment existing.\nYou can remove it." : string.Empty; } @@ -617,7 +617,7 @@ public class ActiveCollections : ISavable, IDisposable, IService if (maleNpc == null) { maleNpc = Default; - if (maleNpc.Identity.Index != checkAssignment.Identity.Index) + if (maleNpc.Index != checkAssignment.Index) return string.Empty; collection1 = CollectionType.Default; @@ -626,7 +626,7 @@ public class ActiveCollections : ISavable, IDisposable, IService if (femaleNpc == null) { femaleNpc = Default; - if (femaleNpc.Identity.Index != checkAssignment.Identity.Index) + if (femaleNpc.Index != checkAssignment.Index) return string.Empty; collection2 = CollectionType.Default; @@ -646,7 +646,7 @@ public class ActiveCollections : ISavable, IDisposable, IService if (assignment == null) continue; - if (assignment.Identity.Index == checkAssignment.Identity.Index) + if (assignment.Index == checkAssignment.Index) return $"Assignment is currently redundant due to overwriting {parentType.ToName()} with an identical collection.\nYou can remove it."; } diff --git a/Penumbra/Collections/Manager/CollectionEditor.cs b/Penumbra/Collections/Manager/CollectionEditor.cs index f62eea3f..caff2c86 100644 --- a/Penumbra/Collections/Manager/CollectionEditor.cs +++ b/Penumbra/Collections/Manager/CollectionEditor.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Mods; @@ -26,12 +26,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu /// public bool SetModState(ModCollection collection, Mod mod, bool newValue) { - var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Enabled ?? false; + var oldValue = collection.Settings[mod.Index]?.Enabled ?? collection[mod.Index].Settings?.Enabled ?? false; if (newValue == oldValue) return false; var inheritance = FixInheritance(collection, mod, false); - collection.GetOwnSettings(mod.Index)!.Enabled = newValue; + ((List)collection.Settings)[mod.Index]!.Enabled = newValue; InvokeChange(collection, ModSettingChange.EnableState, mod, inheritance ? Setting.Indefinite : newValue ? Setting.False : Setting.True, 0); return true; @@ -55,13 +55,13 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu var changes = false; foreach (var mod in mods) { - var oldValue = collection.GetOwnSettings(mod.Index)?.Enabled; + var oldValue = collection.Settings[mod.Index]?.Enabled; if (newValue == oldValue) continue; FixInheritance(collection, mod, false); - collection.GetOwnSettings(mod.Index)!.Enabled = newValue; - changes = true; + ((List)collection.Settings)[mod.Index]!.Enabled = newValue; + changes = true; } if (!changes) @@ -76,64 +76,35 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu /// public bool SetModPriority(ModCollection collection, Mod mod, ModPriority newValue) { - var oldValue = collection.GetInheritedSettings(mod.Index).Settings?.Priority ?? ModPriority.Default; + var oldValue = collection.Settings[mod.Index]?.Priority ?? collection[mod.Index].Settings?.Priority ?? ModPriority.Default; if (newValue == oldValue) return false; var inheritance = FixInheritance(collection, mod, false); - collection.GetOwnSettings(mod.Index)!.Priority = newValue; + ((List)collection.Settings)[mod.Index]!.Priority = newValue; InvokeChange(collection, ModSettingChange.Priority, mod, inheritance ? Setting.Indefinite : oldValue.AsSetting, 0); return true; } /// /// Set a given setting group settingName of mod idx to newValue if it differs from the current value and fix it if necessary. - /// If the mod is currently inherited, stop the inheritance. + /// /// If the mod is currently inherited, stop the inheritance. /// public bool SetModSetting(ModCollection collection, Mod mod, int groupIdx, Setting newValue) { - var settings = collection.GetInheritedSettings(mod.Index).Settings?.Settings; + var settings = collection.Settings[mod.Index] != null + ? collection.Settings[mod.Index]!.Settings + : collection[mod.Index].Settings?.Settings; var oldValue = settings?[groupIdx] ?? mod.Groups[groupIdx].DefaultSettings; if (oldValue == newValue) return false; var inheritance = FixInheritance(collection, mod, false); - collection.GetOwnSettings(mod.Index)!.SetValue(mod, groupIdx, newValue); + ((List)collection.Settings)[mod.Index]!.SetValue(mod, groupIdx, newValue); InvokeChange(collection, ModSettingChange.Setting, mod, inheritance ? Setting.Indefinite : oldValue, groupIdx); return true; } - public bool SetTemporarySettings(ModCollection collection, Mod mod, TemporaryModSettings? settings, int key = 0) - { - key = settings?.Lock ?? key; - if (!CanSetTemporarySettings(collection, mod, key)) - return false; - - collection.Settings.SetTemporary(mod.Index, settings); - InvokeChange(collection, ModSettingChange.TemporarySetting, mod, Setting.Indefinite, 0); - return true; - } - - public int ClearTemporarySettings(ModCollection collection, int key = 0) - { - var numRemoved = 0; - for (var i = 0; i < collection.Settings.Count; ++i) - { - if (collection.GetTempSettings(i) is { } tempSettings - && tempSettings.Lock == key - && SetTemporarySettings(collection, modStorage[i], null, key)) - ++numRemoved; - } - - return numRemoved; - } - - public bool CanSetTemporarySettings(ModCollection collection, Mod mod, int key) - { - var old = collection.GetTempSettings(mod.Index); - return old is not { Lock: > 0 } || old.Lock == key; - } - /// Copy the settings of an existing (sourceMod != null) or stored (sourceName) mod to another mod, if they exist. public bool CopyModSettings(ModCollection collection, Mod? sourceMod, string sourceName, Mod? targetMod, string targetName) { @@ -144,10 +115,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu // If it does not exist, check unused settings. // If it does not exist and has no unused settings, also use null. ModSettings.SavedSettings? savedSettings = sourceMod != null - ? collection.GetOwnSettings(sourceMod.Index) is { } ownSettings - ? new ModSettings.SavedSettings(ownSettings, sourceMod) + ? collection.Settings[sourceMod.Index] != null + ? new ModSettings.SavedSettings(collection.Settings[sourceMod.Index]!, sourceMod) : null - : collection.Settings.Unused.TryGetValue(sourceName, out var s) + : collection.UnusedSettings.TryGetValue(sourceName, out var s) ? s : null; @@ -177,10 +148,10 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu // or remove any unused settings for the target if they are inheriting. if (savedSettings != null) { - ((Dictionary)collection.Settings.Unused)[targetName] = savedSettings.Value; + ((Dictionary)collection.UnusedSettings)[targetName] = savedSettings.Value; saveService.QueueSave(new ModCollectionSave(modStorage, collection)); } - else if (((Dictionary)collection.Settings.Unused).Remove(targetName)) + else if (((Dictionary)collection.UnusedSettings).Remove(targetName)) { saveService.QueueSave(new ModCollectionSave(modStorage, collection)); } @@ -195,12 +166,12 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu /// private static bool FixInheritance(ModCollection collection, Mod mod, bool inherit) { - var settings = collection.GetOwnSettings(mod.Index); + var settings = collection.Settings[mod.Index]; if (inherit == (settings == null)) return false; - var settings1 = inherit ? null : collection.GetInheritedSettings(mod.Index).Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod); - collection.Settings.Set(mod.Index, settings1); + ((List)collection.Settings)[mod.Index] = + inherit ? null : collection[mod.Index].Settings?.DeepCopy() ?? ModSettings.DefaultSettings(mod); return true; } @@ -208,18 +179,16 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void InvokeChange(ModCollection changedCollection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx) { - if (type is not ModSettingChange.TemporarySetting) - saveService.QueueSave(new ModCollectionSave(modStorage, changedCollection)); + saveService.QueueSave(new ModCollectionSave(modStorage, changedCollection)); communicator.ModSettingChanged.Invoke(changedCollection, type, mod, oldValue, groupIdx, false); - if (type is not ModSettingChange.TemporarySetting) - RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx); + RecurseInheritors(changedCollection, type, mod, oldValue, groupIdx); } /// Trigger changes in all inherited collections. [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void RecurseInheritors(ModCollection directParent, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx) { - foreach (var directInheritor in directParent.Inheritance.DirectlyInheritedBy) + foreach (var directInheritor in directParent.DirectParentOf) { switch (type) { @@ -228,7 +197,7 @@ public class CollectionEditor(SaveService saveService, CommunicatorService commu communicator.ModSettingChanged.Invoke(directInheritor, type, null, oldValue, groupIdx, true); break; default: - if (directInheritor.GetOwnSettings(mod!.Index) == null) + if (directInheritor.Settings[mod!.Index] == null) communicator.ModSettingChanged.Invoke(directInheritor, type, mod, oldValue, groupIdx, true); break; } diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index 531b6333..a326fb92 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.ImGuiNotification; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods; @@ -41,8 +41,8 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer public ModCollection CreateFromData(Guid id, string name, int version, Dictionary allSettings, IReadOnlyList inheritances) { - var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, - new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings, inheritances); + var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, id, name, CurrentCollectionId, version, Count, allSettings, + inheritances); _collectionsByLocal[CurrentCollectionId] = newCollection; CurrentCollectionId += 1; return newCollection; @@ -57,7 +57,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer } public void Delete(ModCollection collection) - => _collectionsByLocal.Remove(collection.Identity.LocalId); + => _collectionsByLocal.Remove(collection.LocalId); /// The empty collection is always available at Index 0. private readonly List _collections = @@ -92,7 +92,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer public bool ByName(string name, [NotNullWhen(true)] out ModCollection? collection) { if (name.Length != 0) - return _collections.FindFirst(c => string.Equals(c.Identity.Name, name, StringComparison.OrdinalIgnoreCase), out collection); + return _collections.FindFirst(c => string.Equals(c.Name, name, StringComparison.OrdinalIgnoreCase), out collection); collection = ModCollection.Empty; return true; @@ -102,7 +102,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer public bool ById(Guid id, [NotNullWhen(true)] out ModCollection? collection) { if (id != Guid.Empty) - return _collections.FindFirst(c => c.Identity.Id == id, out collection); + return _collections.FindFirst(c => c.Id == id, out collection); collection = ModCollection.Empty; return true; @@ -158,7 +158,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer var newCollection = Create(name, _collections.Count, duplicate); _collections.Add(newCollection); _saveService.ImmediateSave(new ModCollectionSave(_modStorage, newCollection)); - Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.Identity.AnonymizedName}.", NotificationType.Success, false); + Penumbra.Messager.NotificationMessage($"Created new collection {newCollection.AnonymizedName}.", NotificationType.Success, false); _communicator.CollectionChange.Invoke(CollectionType.Inactive, null, newCollection, string.Empty); return true; } @@ -168,13 +168,13 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer /// public bool RemoveCollection(ModCollection collection) { - if (collection.Identity.Index <= ModCollection.Empty.Identity.Index || collection.Identity.Index >= _collections.Count) + if (collection.Index <= ModCollection.Empty.Index || collection.Index >= _collections.Count) { Penumbra.Messager.NotificationMessage("Can not remove the empty collection.", NotificationType.Error, false); return false; } - if (collection.Identity.Index == DefaultNamed.Identity.Index) + if (collection.Index == DefaultNamed.Index) { Penumbra.Messager.NotificationMessage("Can not remove the default collection.", NotificationType.Error, false); return false; @@ -182,34 +182,30 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer Delete(collection); _saveService.ImmediateDelete(new ModCollectionSave(_modStorage, collection)); - _collections.RemoveAt(collection.Identity.Index); + _collections.RemoveAt(collection.Index); // Update indices. - for (var i = collection.Identity.Index; i < Count; ++i) - _collections[i].Identity.Index = i; - _collectionsByLocal.Remove(collection.Identity.LocalId); + for (var i = collection.Index; i < Count; ++i) + _collections[i].Index = i; + _collectionsByLocal.Remove(collection.LocalId); - Penumbra.Messager.NotificationMessage($"Deleted collection {collection.Identity.AnonymizedName}.", NotificationType.Success, false); + Penumbra.Messager.NotificationMessage($"Deleted collection {collection.AnonymizedName}.", NotificationType.Success, false); _communicator.CollectionChange.Invoke(CollectionType.Inactive, collection, null, string.Empty); return true; } /// Remove all settings for not currently-installed mods from the given collection. - public int CleanUnavailableSettings(ModCollection collection) + public void CleanUnavailableSettings(ModCollection collection) { - var count = collection.Settings.Unused.Count; - if (count > 0) - { - ((Dictionary)collection.Settings.Unused).Clear(); + var any = collection.UnusedSettings.Count > 0; + ((Dictionary)collection.UnusedSettings).Clear(); + if (any) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); - } - - return count; } /// Remove a specific setting for not currently-installed mods from the given collection. public void CleanUnavailableSetting(ModCollection collection, string? setting) { - if (setting != null && ((Dictionary)collection.Settings.Unused).Remove(setting)) + if (setting != null && ((Dictionary)collection.UnusedSettings).Remove(setting)) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); } @@ -250,13 +246,13 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer { File.Move(file.FullName, correctName, false); Penumbra.Messager.NotificationMessage( - $"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, renamed.", + $"Collection {file.Name} does not correspond to {collection.Identifier}, renamed.", NotificationType.Warning); } catch (Exception ex) { Penumbra.Messager.NotificationMessage( - $"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, rename failed:\n{ex}", + $"Collection {file.Name} does not correspond to {collection.Identifier}, rename failed:\n{ex}", NotificationType.Warning); } } @@ -277,7 +273,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer catch (Exception e) { Penumbra.Messager.NotificationMessage(e, - $"Collection {file.Name} does not correspond to {collection.Identity.Identifier}, but could not rename.", + $"Collection {file.Name} does not correspond to {collection.Identifier}, but could not rename.", NotificationType.Error); } @@ -295,14 +291,14 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer /// private ModCollection SetDefaultNamedCollection() { - if (ByName(ModCollectionIdentity.DefaultCollectionName, out var collection)) + if (ByName(ModCollection.DefaultCollectionName, out var collection)) return collection; - if (AddCollection(ModCollectionIdentity.DefaultCollectionName, null)) + if (AddCollection(ModCollection.DefaultCollectionName, null)) return _collections[^1]; Penumbra.Messager.NotificationMessage( - $"Unknown problem creating a collection with the name {ModCollectionIdentity.DefaultCollectionName}, which is required to exist.", + $"Unknown problem creating a collection with the name {ModCollection.DefaultCollectionName}, which is required to exist.", NotificationType.Error); return Count > 1 ? _collections[1] : _collections[0]; } @@ -311,7 +307,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer private void OnModDiscoveryStarted() { foreach (var collection in this) - collection.Settings.PrepareModDiscovery(_modStorage); + collection.PrepareModDiscovery(_modStorage); } /// Restore all settings in all collections to mods. @@ -319,7 +315,7 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer { // Re-apply all mod settings. foreach (var collection in this) - collection.Settings.ApplyModSettings(collection, _saveService, _modStorage); + collection.ApplyModSettings(_saveService, _modStorage); } /// Add or remove a mod from all collections, or re-save all collections where the mod has settings. @@ -330,22 +326,21 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer { case ModPathChangeType.Added: foreach (var collection in this) - collection.Settings.AddMod(mod); + collection.AddMod(mod); break; case ModPathChangeType.Deleted: foreach (var collection in this) - collection.Settings.RemoveMod(mod); + collection.RemoveMod(mod); break; case ModPathChangeType.Moved: - foreach (var collection in this.Where(collection => collection.GetOwnSettings(mod.Index) != null)) + foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null)) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); break; case ModPathChangeType.Reloaded: foreach (var collection in this) { - if (collection.GetOwnSettings(mod.Index)?.Settings.FixAll(mod) ?? false) + if (collection.Settings[mod.Index]?.Settings.FixAll(mod) ?? false) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); - collection.Settings.SetTemporary(mod.Index, null); } break; @@ -362,9 +357,8 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer foreach (var collection in this) { - if (collection.GetOwnSettings(mod.Index)?.HandleChanges(type, mod, group, option, movedToIdx) ?? false) + if (collection.Settings[mod.Index]?.HandleChanges(type, mod, group, option, movedToIdx) ?? false) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); - collection.Settings.SetTemporary(mod.Index, null); } } @@ -376,9 +370,9 @@ public class CollectionStorage : IReadOnlyList, IDisposable, ISer foreach (var collection in this) { - var (settings, _) = collection.GetActualSettings(mod.Index); + var (settings, _) = collection[mod.Index]; if (settings is { Enabled: true }) - collection.Counters.IncrementChange(); + collection.IncrementCounter(); } } } diff --git a/Penumbra/Collections/Manager/IndividualCollections.Files.cs b/Penumbra/Collections/Manager/IndividualCollections.Files.cs index 60e9fc5f..f7a26384 100644 --- a/Penumbra/Collections/Manager/IndividualCollections.Files.cs +++ b/Penumbra/Collections/Manager/IndividualCollections.Files.cs @@ -18,7 +18,7 @@ public partial class IndividualCollections foreach (var (name, identifiers, collection) in Assignments) { var tmp = identifiers[0].ToJson(); - tmp.Add("Collection", collection.Identity.Id); + tmp.Add("Collection", collection.Id); tmp.Add("Display", name); ret.Add(tmp); } @@ -182,7 +182,7 @@ public partial class IndividualCollections Penumbra.Log.Information($"Migrated {name} ({kind.ToName()}) to NPC Identifiers [{ids}]."); else Penumbra.Messager.NotificationMessage( - $"Could not migrate {name} ({collection.Identity.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.", + $"Could not migrate {name} ({collection.AnonymizedName}) which was assumed to be a {kind.ToName()} with IDs [{ids}], please look through your individual collections.", NotificationType.Error); } // If it is not a valid NPC name, check if it can be a player name. @@ -192,16 +192,16 @@ public partial class IndividualCollections var shortName = string.Join(" ", name.Split().Select(n => $"{n[0]}.")); // Try to migrate the player name without logging full names. if (Add($"{name} ({_actors.Data.ToWorldName(identifier.HomeWorld)})", [identifier], collection)) - Penumbra.Log.Information($"Migrated {shortName} ({collection.Identity.AnonymizedName}) to Player Identifier."); + Penumbra.Log.Information($"Migrated {shortName} ({collection.AnonymizedName}) to Player Identifier."); else Penumbra.Messager.NotificationMessage( - $"Could not migrate {shortName} ({collection.Identity.AnonymizedName}), please look through your individual collections.", + $"Could not migrate {shortName} ({collection.AnonymizedName}), please look through your individual collections.", NotificationType.Error); } else { Penumbra.Messager.NotificationMessage( - $"Could not migrate {name} ({collection.Identity.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.", + $"Could not migrate {name} ({collection.AnonymizedName}), which can not be a player name nor is it a known NPC name, please look through your individual collections.", NotificationType.Error); } } diff --git a/Penumbra/Collections/Manager/InheritanceManager.cs b/Penumbra/Collections/Manager/InheritanceManager.cs index 34582677..bc1a362c 100644 --- a/Penumbra/Collections/Manager/InheritanceManager.cs +++ b/Penumbra/Collections/Manager/InheritanceManager.cs @@ -1,6 +1,7 @@ using Dalamud.Interface.ImGuiNotification; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; +using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.Communication; using Penumbra.Mods.Manager; @@ -62,10 +63,10 @@ public class InheritanceManager : IDisposable, IService if (ReferenceEquals(potentialParent, potentialInheritor)) return ValidInheritance.Self; - if (potentialInheritor.Inheritance.DirectlyInheritsFrom.Contains(potentialParent)) + if (potentialInheritor.DirectlyInheritsFrom.Contains(potentialParent)) return ValidInheritance.Contained; - if (potentialParent.Inheritance.FlatHierarchy.Any(c => ReferenceEquals(c, potentialInheritor))) + if (ModCollection.InheritedCollections(potentialParent).Any(c => ReferenceEquals(c, potentialInheritor))) return ValidInheritance.Circle; return ValidInheritance.Valid; @@ -82,23 +83,25 @@ public class InheritanceManager : IDisposable, IService /// Remove an existing inheritance from a collection. public void RemoveInheritance(ModCollection inheritor, int idx) { - var parent = inheritor.Inheritance.RemoveInheritanceAt(inheritor, idx); + var parent = inheritor.DirectlyInheritsFrom[idx]; + ((List)inheritor.DirectlyInheritsFrom).RemoveAt(idx); + ((List)parent.DirectParentOf).Remove(inheritor); _saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor)); _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); - RecurseInheritanceChanges(inheritor, true); - Penumbra.Log.Debug($"Removed {parent.Identity.AnonymizedName} from {inheritor.Identity.AnonymizedName} inheritances."); + RecurseInheritanceChanges(inheritor); + Penumbra.Log.Debug($"Removed {parent.AnonymizedName} from {inheritor.AnonymizedName} inheritances."); } /// Order in the inheritance list is relevant. public void MoveInheritance(ModCollection inheritor, int from, int to) { - if (!inheritor.Inheritance.MoveInheritance(inheritor, from, to)) + if (!((List)inheritor.DirectlyInheritsFrom).Move(from, to)) return; _saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor)); _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); - RecurseInheritanceChanges(inheritor, true); - Penumbra.Log.Debug($"Moved {inheritor.Identity.AnonymizedName}s inheritance {from} to {to}."); + RecurseInheritanceChanges(inheritor); + Penumbra.Log.Debug($"Moved {inheritor.AnonymizedName}s inheritance {from} to {to}."); } /// @@ -107,16 +110,16 @@ public class InheritanceManager : IDisposable, IService if (CheckValidInheritance(inheritor, parent) != ValidInheritance.Valid) return false; - inheritor.Inheritance.AddInheritance(inheritor, parent); + ((List)inheritor.DirectlyInheritsFrom).Add(parent); + ((List)parent.DirectParentOf).Add(inheritor); if (invokeEvent) { _saveService.QueueSave(new ModCollectionSave(_modStorage, inheritor)); _communicator.CollectionInheritanceChanged.Invoke(inheritor, false); + RecurseInheritanceChanges(inheritor); } - RecurseInheritanceChanges(inheritor, invokeEvent); - - Penumbra.Log.Debug($"Added {parent.Identity.AnonymizedName} to {inheritor.Identity.AnonymizedName} inheritances."); + Penumbra.Log.Debug($"Added {parent.AnonymizedName} to {inheritor.AnonymizedName} inheritances."); return true; } @@ -128,11 +131,11 @@ public class InheritanceManager : IDisposable, IService { foreach (var collection in _storage) { - if (collection.Inheritance.ConsumeNames() is not { } byName) + if (collection.InheritanceByName == null) continue; var changes = false; - foreach (var subCollectionName in byName) + foreach (var subCollectionName in collection.InheritanceByName) { if (Guid.TryParse(subCollectionName, out var guid) && _storage.ById(guid, out var subCollection)) { @@ -140,30 +143,29 @@ public class InheritanceManager : IDisposable, IService continue; changes = true; - Penumbra.Messager.NotificationMessage( - $"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.", + Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning); } else if (_storage.ByName(subCollectionName, out subCollection)) { changes = true; - Penumbra.Log.Information($"Migrating inheritance for {collection.Identity.AnonymizedName} from name to GUID."); + Penumbra.Log.Information($"Migrating inheritance for {collection.AnonymizedName} from name to GUID."); if (AddInheritance(collection, subCollection, false)) continue; - Penumbra.Messager.NotificationMessage( - $"{collection.Identity.Name} can not inherit from {subCollection.Identity.Name}, removed.", + Penumbra.Messager.NotificationMessage($"{collection.Name} can not inherit from {subCollection.Name}, removed.", NotificationType.Warning); } else { Penumbra.Messager.NotificationMessage( - $"Inherited collection {subCollectionName} for {collection.Identity.AnonymizedName} does not exist, it was removed.", + $"Inherited collection {subCollectionName} for {collection.AnonymizedName} does not exist, it was removed.", NotificationType.Warning); changes = true; } } + collection.InheritanceByName = null; if (changes) _saveService.ImmediateSave(new ModCollectionSave(_modStorage, collection)); } @@ -176,22 +178,20 @@ public class InheritanceManager : IDisposable, IService foreach (var c in _storage) { - var inheritedIdx = c.Inheritance.DirectlyInheritsFrom.IndexOf(old); + var inheritedIdx = c.DirectlyInheritsFrom.IndexOf(old); if (inheritedIdx >= 0) RemoveInheritance(c, inheritedIdx); - c.Inheritance.RemoveChild(old); + ((List)c.DirectParentOf).Remove(old); } } - private void RecurseInheritanceChanges(ModCollection newInheritor, bool invokeEvent) + private void RecurseInheritanceChanges(ModCollection newInheritor) { - foreach (var inheritor in newInheritor.Inheritance.DirectlyInheritedBy) + foreach (var inheritor in newInheritor.DirectParentOf) { - ModCollectionInheritance.UpdateFlattenedInheritance(inheritor); - RecurseInheritanceChanges(inheritor, invokeEvent); - if (invokeEvent) - _communicator.CollectionInheritanceChanged.Invoke(inheritor, true); + _communicator.CollectionInheritanceChanged.Invoke(inheritor, true); + RecurseInheritanceChanges(inheritor); } } } diff --git a/Penumbra/Collections/Manager/ModCollectionMigration.cs b/Penumbra/Collections/Manager/ModCollectionMigration.cs index 7db375f7..fe61285d 100644 --- a/Penumbra/Collections/Manager/ModCollectionMigration.cs +++ b/Penumbra/Collections/Manager/ModCollectionMigration.cs @@ -26,12 +26,12 @@ internal static class ModCollectionMigration // Remove all completely defaulted settings from active and inactive mods. for (var i = 0; i < collection.Settings.Count; ++i) { - if (SettingIsDefaultV0(collection.GetOwnSettings(i))) - collection.Settings.SetAll(i, FullModSettings.Empty); + if (SettingIsDefaultV0(collection.Settings[i])) + ((List)collection.Settings)[i] = null; } - foreach (var (key, _) in collection.Settings.Unused.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList()) - collection.Settings.RemoveUnused(key); + foreach (var (key, _) in collection.UnusedSettings.Where(kvp => SettingIsDefaultV0(kvp.Value)).ToList()) + ((Dictionary)collection.UnusedSettings).Remove(key); return true; } diff --git a/Penumbra/Collections/Manager/TempCollectionManager.cs b/Penumbra/Collections/Manager/TempCollectionManager.cs index 9476e38c..5c893232 100644 --- a/Penumbra/Collections/Manager/TempCollectionManager.cs +++ b/Penumbra/Collections/Manager/TempCollectionManager.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using OtterGui.Services; using Penumbra.Api; using Penumbra.Communication; @@ -44,7 +44,7 @@ public class TempCollectionManager : IDisposable, IService => _customCollections.Values; public bool CollectionByName(string name, [NotNullWhen(true)] out ModCollection? collection) - => _customCollections.Values.FindFirst(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase), out collection); + => _customCollections.Values.FindFirst(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase), out collection); public bool CollectionById(Guid id, [NotNullWhen(true)] out ModCollection? collection) => _customCollections.TryGetValue(id, out collection); @@ -54,12 +54,12 @@ public class TempCollectionManager : IDisposable, IService if (GlobalChangeCounter == int.MaxValue) GlobalChangeCounter = 0; var collection = _storage.CreateTemporary(name, ~Count, GlobalChangeCounter++); - Penumbra.Log.Debug($"Creating temporary collection {collection.Identity.Name} with {collection.Identity.Id}."); - if (_customCollections.TryAdd(collection.Identity.Id, collection)) + Penumbra.Log.Debug($"Creating temporary collection {collection.Name} with {collection.Id}."); + if (_customCollections.TryAdd(collection.Id, collection)) { // Temporary collection created. _communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, string.Empty); - return collection.Identity.Id; + return collection.Id; } return Guid.Empty; @@ -74,8 +74,8 @@ public class TempCollectionManager : IDisposable, IService } _storage.Delete(collection); - Penumbra.Log.Debug($"Deleted temporary collection {collection.Identity.Id}."); - GlobalChangeCounter += Math.Max(collection.Counters.Change + 1 - GlobalChangeCounter, 0); + Penumbra.Log.Debug($"Deleted temporary collection {collection.Id}."); + GlobalChangeCounter += Math.Max(collection.ChangeCounter + 1 - GlobalChangeCounter, 0); for (var i = 0; i < Collections.Count; ++i) { if (Collections[i].Collection != collection) @@ -83,7 +83,7 @@ public class TempCollectionManager : IDisposable, IService // Temporary collection assignment removed. _communicator.CollectionChange.Invoke(CollectionType.Temporary, collection, null, Collections[i].DisplayName); - Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Identity.Id} from {Collections[i].DisplayName}."); + Penumbra.Log.Verbose($"Unassigned temporary collection {collection.Id} from {Collections[i].DisplayName}."); Collections.Delete(i--); } @@ -96,7 +96,7 @@ public class TempCollectionManager : IDisposable, IService return false; // Temporary collection assignment added. - Penumbra.Log.Verbose($"Assigned temporary collection {collection.Identity.AnonymizedName} to {Collections.Last().DisplayName}."); + Penumbra.Log.Verbose($"Assigned temporary collection {collection.AnonymizedName} to {Collections.Last().DisplayName}."); _communicator.CollectionChange.Invoke(CollectionType.Temporary, null, collection, Collections.Last().DisplayName); return true; } @@ -127,6 +127,6 @@ public class TempCollectionManager : IDisposable, IService return false; var identifier = _actors.CreatePlayer(byteString, worldId); - return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Identity.Id); + return Collections.TryGetValue(identifier, out var collection) && RemoveTemporaryCollection(collection.Id); } } diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index 716b153e..0b38dde8 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -46,8 +46,8 @@ public partial class ModCollection internal IReadOnlyDictionary ResolvedFiles => _cache?.ResolvedFiles ?? new ConcurrentDictionary(); - internal IReadOnlyDictionary, IIdentifiedObjectData)> ChangedItems - => _cache?.ChangedItems ?? new Dictionary, IIdentifiedObjectData)>(); + internal IReadOnlyDictionary, IIdentifiedObjectData?)> ChangedItems + => _cache?.ChangedItems ?? new Dictionary, IIdentifiedObjectData?)>(); internal IEnumerable> AllConflicts => _cache?.AllConflicts ?? Array.Empty>(); diff --git a/Penumbra/Collections/ModCollection.cs b/Penumbra/Collections/ModCollection.cs index 69f82458..db9c19cb 100644 --- a/Penumbra/Collections/ModCollection.cs +++ b/Penumbra/Collections/ModCollection.cs @@ -1,3 +1,4 @@ +using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Collections.Manager; using Penumbra.Mods.Settings; @@ -12,83 +13,111 @@ namespace Penumbra.Collections; /// - Index is the collections index in the ModCollection.Manager /// - Settings has the same size as ModManager.Mods. /// - any change in settings or inheritance of the collection causes a Save. +/// - the name can not contain invalid path characters and has to be unique when lower-cased. /// public partial class ModCollection { - public const int CurrentVersion = 2; + public const int CurrentVersion = 2; + public const string DefaultCollectionName = "Default"; + public const string EmptyCollectionName = "None"; /// /// Create the always available Empty Collection that will always sit at index 0, /// can not be deleted and does never create a cache. /// - public static readonly ModCollection Empty = new(ModCollectionIdentity.Empty, 0, CurrentVersion, new ModSettingProvider(), - new ModCollectionInheritance()); + public static readonly ModCollection Empty = new(Guid.Empty, EmptyCollectionName, LocalCollectionId.Zero, 0, 0, CurrentVersion, [], [], []); - public ModCollectionIdentity Identity; + /// The name of a collection. + public string Name { get; set; } + + public Guid Id { get; } + + public LocalCollectionId LocalId { get; } + + public string Identifier + => Id.ToString(); + + public string ShortIdentifier + => Identifier[..8]; public override string ToString() - => Identity.ToString(); + => Name.Length > 0 ? Name : ShortIdentifier; - public readonly ModSettingProvider Settings; - public ModCollectionInheritance Inheritance; - public CollectionCounters Counters; + /// Get the first two letters of a collection name and its Index (or None if it is the empty collection). + public string AnonymizedName + => this == Empty ? Empty.Name : Name == DefaultCollectionName ? Name : ShortIdentifier; + /// The index of the collection is set and kept up-to-date by the CollectionManager. + public int Index { get; internal set; } - public ModSettings? GetOwnSettings(Index idx) + /// + /// Count the number of changes of the effective file list. + /// This is used for material and imc changes. + /// + public int ChangeCounter { get; private set; } + + public uint ImcChangeCounter { get; set; } + public uint AtchChangeCounter { get; set; } + + /// Increment the number of changes in the effective file list. + public int IncrementCounter() + => ++ChangeCounter; + + /// + /// If a ModSetting is null, it can be inherited from other collections. + /// If no collection provides a setting for the mod, it is just disabled. + /// + public readonly IReadOnlyList Settings; + + /// Settings for deleted mods will be kept via the mods identifier (directory name). + public readonly IReadOnlyDictionary UnusedSettings; + + /// Inheritances stored before they can be applied. + public IReadOnlyList? InheritanceByName; + + /// Contains all direct parent collections this collection inherits settings from. + public readonly IReadOnlyList DirectlyInheritsFrom; + + /// Contains all direct child collections that inherit from this collection. + public readonly IReadOnlyList DirectParentOf = new List(); + + /// All inherited collections in application order without filtering for duplicates. + public static IEnumerable InheritedCollections(ModCollection collection) + => collection.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(collection); + + /// + /// Iterate over all collections inherited from in depth-first order. + /// Skip already visited collections to avoid circular dependencies. + /// + public IEnumerable GetFlattenedInheritance() + => InheritedCollections(this).Distinct(); + + /// + /// Obtain the actual settings for a given mod via index. + /// Also returns the collection the settings are taken from. + /// If no collection provides settings for this mod, this collection is returned together with null. + /// + public (ModSettings? Settings, ModCollection Collection) this[Index idx] { - if (Identity.Index <= 0) - return ModSettings.Empty; - - return Settings.Settings[idx].Settings; - } - - public TemporaryModSettings? GetTempSettings(Index idx) - { - if (Identity.Index <= 0) - return null; - - return Settings.Settings[idx].TempSettings; - } - - public (ModSettings? Settings, ModCollection Collection) GetInheritedSettings(Index idx) - { - if (Identity.Index <= 0) - return (ModSettings.Empty, this); - - foreach (var collection in Inheritance.FlatHierarchy) + get { - var settings = collection.Settings.Settings[idx].Settings; - if (settings != null) - return (settings, collection); + if (Index <= 0) + return (ModSettings.Empty, this); + + foreach (var collection in GetFlattenedInheritance()) + { + var settings = collection.Settings[idx]; + if (settings != null) + return (settings, collection); + } + + return (null, this); } - - return (null, this); - } - - public (ModSettings? Settings, ModCollection Collection) GetActualSettings(Index idx) - { - if (Identity.Index <= 0) - return (ModSettings.Empty, this); - - // Check temp settings. - var ownTempSettings = Settings.Settings[idx].Resolve(); - if (ownTempSettings != null) - return (ownTempSettings, this); - - // Ignore temp settings for inherited collections. - foreach (var collection in Inheritance.FlatHierarchy.Skip(1)) - { - var settings = collection.Settings.Settings[idx].Settings; - if (settings != null) - return (settings, collection); - } - - return (null, this); } /// Evaluates all settings along the whole inheritance tree. public IEnumerable ActualSettings - => Enumerable.Range(0, Settings.Count).Select(i => GetActualSettings(i).Settings); + => Enumerable.Range(0, Settings.Count).Select(i => this[i].Settings); /// /// Constructor for duplication. Deep copies all settings and parent collections and adds the new collection to their children lists. @@ -96,16 +125,21 @@ public partial class ModCollection public ModCollection Duplicate(string name, LocalCollectionId localId, int index) { Debug.Assert(index > 0, "Collection duplicated with non-positive index."); - return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, Settings.Clone(), Inheritance.Clone()); + return new ModCollection(Guid.NewGuid(), name, localId, index, 0, CurrentVersion, Settings.Select(s => s?.DeepCopy()).ToList(), + [.. DirectlyInheritsFrom], UnusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy())); } /// Constructor for reading from files. - public static ModCollection CreateFromData(SaveService saver, ModStorage mods, ModCollectionIdentity identity, int version, + public static ModCollection CreateFromData(SaveService saver, ModStorage mods, Guid id, string name, LocalCollectionId localId, int version, + int index, Dictionary allSettings, IReadOnlyList inheritances) { - Debug.Assert(identity.Index > 0, "Collection read with non-positive index."); - var ret = new ModCollection(identity, 0, version, new ModSettingProvider(allSettings), new ModCollectionInheritance(inheritances)); - ret.Settings.ApplyModSettings(ret, saver, mods); + Debug.Assert(index > 0, "Collection read with non-positive index."); + var ret = new ModCollection(id, name, localId, index, 0, version, [], [], allSettings) + { + InheritanceByName = inheritances, + }; + ret.ApplyModSettings(saver, mods); ModCollectionMigration.Migrate(saver, mods, version, ret); return ret; } @@ -114,8 +148,7 @@ public partial class ModCollection public static ModCollection CreateTemporary(string name, LocalCollectionId localId, int index, int changeCounter) { Debug.Assert(index < 0, "Temporary collection created with non-negative index."); - var ret = new ModCollection(ModCollectionIdentity.New(name, localId, index), changeCounter, CurrentVersion, new ModSettingProvider(), - new ModCollectionInheritance()); + var ret = new ModCollection(Guid.NewGuid(), name, localId, index, changeCounter, CurrentVersion, [], [], []); return ret; } @@ -123,18 +156,68 @@ public partial class ModCollection public static ModCollection CreateEmpty(string name, LocalCollectionId localId, int index, int modCount) { Debug.Assert(index >= 0, "Empty collection created with negative index."); - return new ModCollection(ModCollectionIdentity.New(name, localId, index), 0, CurrentVersion, ModSettingProvider.Empty(modCount), - new ModCollectionInheritance()); + return new ModCollection(Guid.NewGuid(), name, localId, index, 0, CurrentVersion, + Enumerable.Repeat((ModSettings?)null, modCount).ToList(), [], + []); } - private ModCollection(ModCollectionIdentity identity, int changeCounter, int version, ModSettingProvider settings, - ModCollectionInheritance inheritance) + /// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. + internal bool AddMod(Mod mod) { - Identity = identity; - Counters = new CollectionCounters(changeCounter); - Settings = settings; - Inheritance = inheritance; - ModCollectionInheritance.UpdateChildren(this); - ModCollectionInheritance.UpdateFlattenedInheritance(this); + if (UnusedSettings.TryGetValue(mod.ModPath.Name, out var save)) + { + var ret = save.ToSettings(mod, out var settings); + ((List)Settings).Add(settings); + ((Dictionary)UnusedSettings).Remove(mod.ModPath.Name); + return ret; + } + + ((List)Settings).Add(null); + return false; + } + + /// Move settings from the current mod list to the unused mod settings. + internal void RemoveMod(Mod mod) + { + var settings = Settings[mod.Index]; + if (settings != null) + ((Dictionary)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(settings, mod); + + ((List)Settings).RemoveAt(mod.Index); + } + + /// Move all settings to unused settings for rediscovery. + internal void PrepareModDiscovery(ModStorage mods) + { + foreach (var (mod, setting) in mods.Zip(Settings).Where(s => s.Second != null)) + ((Dictionary)UnusedSettings)[mod.ModPath.Name] = new ModSettings.SavedSettings(setting!, mod); + + ((List)Settings).Clear(); + } + + /// + /// Apply all mod settings from unused settings to the current set of mods. + /// Also fixes invalid settings. + /// + internal void ApplyModSettings(SaveService saver, ModStorage mods) + { + ((List)Settings).Capacity = Math.Max(((List)Settings).Capacity, mods.Count); + if (mods.Aggregate(false, (current, mod) => current | AddMod(mod))) + saver.ImmediateSave(new ModCollectionSave(mods, this)); + } + + private ModCollection(Guid id, string name, LocalCollectionId localId, int index, int changeCounter, int version, + List appliedSettings, List inheritsFrom, Dictionary settings) + { + Name = name; + Id = id; + LocalId = localId; + Index = index; + ChangeCounter = changeCounter; + Settings = appliedSettings; + UnusedSettings = settings; + DirectlyInheritsFrom = inheritsFrom; + foreach (var c in DirectlyInheritsFrom) + ((List)c.DirectParentOf).Add(this); } } diff --git a/Penumbra/Collections/ModCollectionIdentity.cs b/Penumbra/Collections/ModCollectionIdentity.cs deleted file mode 100644 index 7050450c..00000000 --- a/Penumbra/Collections/ModCollectionIdentity.cs +++ /dev/null @@ -1,43 +0,0 @@ -using OtterGui; -using OtterGui.Extensions; -using Penumbra.Collections.Manager; - -namespace Penumbra.Collections; - -public struct ModCollectionIdentity(Guid id, LocalCollectionId localId) -{ - public const string DefaultCollectionName = "Default"; - public const string EmptyCollectionName = "None"; - - public static readonly ModCollectionIdentity Empty = new(Guid.Empty, LocalCollectionId.Zero, EmptyCollectionName, 0); - - public string Name { get; set; } = string.Empty; - public Guid Id { get; } = id; - public LocalCollectionId LocalId { get; } = localId; - - /// The index of the collection is set and kept up-to-date by the CollectionManager. - public int Index { get; internal set; } - - public string Identifier - => Id.ToString(); - - public string ShortIdentifier - => Id.ShortGuid(); - - /// Get the short identifier of a collection unless it is a well-known collection name. - public string AnonymizedName - => Id == Guid.Empty ? EmptyCollectionName : Name == DefaultCollectionName ? Name : ShortIdentifier; - - public override string ToString() - => Name.Length > 0 ? Name : ShortIdentifier; - - public ModCollectionIdentity(Guid id, LocalCollectionId localId, string name, int index) - : this(id, localId) - { - Name = name; - Index = index; - } - - public static ModCollectionIdentity New(string name, LocalCollectionId id, int index) - => new(Guid.NewGuid(), id, name, index); -} diff --git a/Penumbra/Collections/ModCollectionInheritance.cs b/Penumbra/Collections/ModCollectionInheritance.cs deleted file mode 100644 index 151ed7db..00000000 --- a/Penumbra/Collections/ModCollectionInheritance.cs +++ /dev/null @@ -1,92 +0,0 @@ -using OtterGui.Filesystem; - -namespace Penumbra.Collections; - -public struct ModCollectionInheritance -{ - public IReadOnlyList? InheritanceByName { get; private set; } - private readonly List _directlyInheritsFrom = []; - private readonly List _directlyInheritedBy = []; - private readonly List _flatHierarchy = []; - - public ModCollectionInheritance() - { } - - private ModCollectionInheritance(List inheritsFrom) - => _directlyInheritsFrom = [.. inheritsFrom]; - - public ModCollectionInheritance(IReadOnlyList byName) - => InheritanceByName = byName; - - public ModCollectionInheritance Clone() - => new(_directlyInheritsFrom); - - public IEnumerable Identifiers - => InheritanceByName ?? _directlyInheritsFrom.Select(c => c.Identity.Identifier); - - public IReadOnlyList? ConsumeNames() - { - var ret = InheritanceByName; - InheritanceByName = null; - return ret; - } - - public static void UpdateChildren(ModCollection parent) - { - foreach (var inheritance in parent.Inheritance.DirectlyInheritsFrom) - inheritance.Inheritance._directlyInheritedBy.Add(parent); - } - - public void AddInheritance(ModCollection inheritor, ModCollection newParent) - { - _directlyInheritsFrom.Add(newParent); - newParent.Inheritance._directlyInheritedBy.Add(inheritor); - UpdateFlattenedInheritance(inheritor); - } - - public ModCollection RemoveInheritanceAt(ModCollection inheritor, int idx) - { - var parent = DirectlyInheritsFrom[idx]; - _directlyInheritsFrom.RemoveAt(idx); - parent.Inheritance._directlyInheritedBy.Remove(parent); - UpdateFlattenedInheritance(inheritor); - return parent; - } - - public bool MoveInheritance(ModCollection inheritor, int from, int to) - { - if (!_directlyInheritsFrom.Move(from, to)) - return false; - - UpdateFlattenedInheritance(inheritor); - return true; - } - - public void RemoveChild(ModCollection child) - => _directlyInheritedBy.Remove(child); - - /// Contains all direct parent collections this collection inherits settings from. - public readonly IReadOnlyList DirectlyInheritsFrom - => _directlyInheritsFrom; - - /// Contains all direct child collections that inherit from this collection. - public readonly IReadOnlyList DirectlyInheritedBy - => _directlyInheritedBy; - - /// - /// Iterate over all collections inherited from in depth-first order. - /// Skip already visited collections to avoid circular dependencies. - /// - public readonly IReadOnlyList FlatHierarchy - => _flatHierarchy; - - public static void UpdateFlattenedInheritance(ModCollection parent) - { - parent.Inheritance._flatHierarchy.Clear(); - parent.Inheritance._flatHierarchy.AddRange(InheritedCollections(parent).Distinct()); - } - - /// All inherited collections in application order without filtering for duplicates. - private static IEnumerable InheritedCollections(ModCollection parent) - => parent.Inheritance.DirectlyInheritsFrom.SelectMany(InheritedCollections).Prepend(parent); -} diff --git a/Penumbra/Collections/ModCollectionSave.cs b/Penumbra/Collections/ModCollectionSave.cs index 4c41a28c..e6bb069b 100644 --- a/Penumbra/Collections/ModCollectionSave.cs +++ b/Penumbra/Collections/ModCollectionSave.cs @@ -15,7 +15,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection => fileNames.CollectionFile(modCollection); public string LogName(string _) - => modCollection.Identity.AnonymizedName; + => modCollection.AnonymizedName; public string TypeName => "Collection"; @@ -28,23 +28,23 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection j.WriteStartObject(); j.WritePropertyName("Version"); j.WriteValue(ModCollection.CurrentVersion); - j.WritePropertyName(nameof(ModCollectionIdentity.Id)); - j.WriteValue(modCollection.Identity.Identifier); - j.WritePropertyName(nameof(ModCollectionIdentity.Name)); - j.WriteValue(modCollection.Identity.Name); - j.WritePropertyName("Settings"); + j.WritePropertyName(nameof(ModCollection.Id)); + j.WriteValue(modCollection.Identifier); + j.WritePropertyName(nameof(ModCollection.Name)); + j.WriteValue(modCollection.Name); + j.WritePropertyName(nameof(ModCollection.Settings)); // Write all used and unused settings by mod directory name. j.WriteStartObject(); - var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.Settings.Unused.Count); + var list = new List<(string, ModSettings.SavedSettings)>(modCollection.Settings.Count + modCollection.UnusedSettings.Count); for (var i = 0; i < modCollection.Settings.Count; ++i) { - var settings = modCollection.GetOwnSettings(i); + var settings = modCollection.Settings[i]; if (settings != null) list.Add((modStorage[i].ModPath.Name, new ModSettings.SavedSettings(settings, modStorage[i]))); } - list.AddRange(modCollection.Settings.Unused.Select(kvp => (kvp.Key, kvp.Value))); + list.AddRange(modCollection.UnusedSettings.Select(kvp => (kvp.Key, kvp.Value))); list.Sort((a, b) => string.Compare(a.Item1, b.Item1, StringComparison.OrdinalIgnoreCase)); foreach (var (modDir, settings) in list) @@ -57,7 +57,7 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection // Inherit by collection name. j.WritePropertyName("Inheritance"); - x.Serialize(j, modCollection.Inheritance.Identifiers); + x.Serialize(j, modCollection.InheritanceByName ?? modCollection.DirectlyInheritsFrom.Select(c => c.Identifier)); j.WriteEndObject(); } @@ -79,10 +79,10 @@ internal readonly struct ModCollectionSave(ModStorage modStorage, ModCollection { var obj = JObject.Parse(File.ReadAllText(file.FullName)); version = obj["Version"]?.ToObject() ?? 0; - name = obj[nameof(ModCollectionIdentity.Name)]?.ToObject() ?? string.Empty; - id = obj[nameof(ModCollectionIdentity.Id)]?.ToObject() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty); + name = obj[nameof(ModCollection.Name)]?.ToObject() ?? string.Empty; + id = obj[nameof(ModCollection.Id)]?.ToObject() ?? (version == 1 ? Guid.NewGuid() : Guid.Empty); // Custom deserialization that is converted with the constructor. - settings = obj["Settings"]?.ToObject>() ?? settings; + settings = obj[nameof(ModCollection.Settings)]?.ToObject>() ?? settings; inheritance = obj["Inheritance"]?.ToObject>() ?? inheritance; return true; } diff --git a/Penumbra/Collections/ModSettingProvider.cs b/Penumbra/Collections/ModSettingProvider.cs deleted file mode 100644 index 3bf2f949..00000000 --- a/Penumbra/Collections/ModSettingProvider.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Penumbra.Mods; -using Penumbra.Mods.Manager; -using Penumbra.Mods.Settings; -using Penumbra.Services; - -namespace Penumbra.Collections; - -public readonly struct ModSettingProvider -{ - private ModSettingProvider(IEnumerable settings, Dictionary unusedSettings) - { - _settings = settings.Select(s => s.DeepCopy()).ToList(); - _unused = unusedSettings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.DeepCopy()); - } - - public ModSettingProvider() - { } - - public static ModSettingProvider Empty(int count) - => new(Enumerable.Repeat(FullModSettings.Empty, count), []); - - public ModSettingProvider(Dictionary allSettings) - => _unused = allSettings; - - private readonly List _settings = []; - - /// Settings for deleted mods will be kept via the mods identifier (directory name). - private readonly Dictionary _unused = []; - - public int Count - => _settings.Count; - - public bool RemoveUnused(string key) - => _unused.Remove(key); - - internal void Set(Index index, ModSettings? settings) - => _settings[index] = _settings[index] with { Settings = settings }; - - internal void SetTemporary(Index index, TemporaryModSettings? settings) - => _settings[index] = _settings[index] with { TempSettings = settings }; - - internal void SetAll(Index index, FullModSettings settings) - => _settings[index] = settings; - - public IReadOnlyList Settings - => _settings; - - public IReadOnlyDictionary Unused - => _unused; - - public ModSettingProvider Clone() - => new(_settings, _unused); - - /// Add settings for a new appended mod, by checking if the mod had settings from a previous deletion. - internal bool AddMod(Mod mod) - { - if (_unused.Remove(mod.ModPath.Name, out var save)) - { - var ret = save.ToSettings(mod, out var settings); - _settings.Add(new FullModSettings(settings)); - return ret; - } - - _settings.Add(FullModSettings.Empty); - return false; - } - - /// Move settings from the current mod list to the unused mod settings. - internal void RemoveMod(Mod mod) - { - var settings = _settings[mod.Index]; - if (settings.Settings != null) - _unused[mod.ModPath.Name] = new ModSettings.SavedSettings(settings.Settings, mod); - - _settings.RemoveAt(mod.Index); - } - - /// Move all settings to unused settings for rediscovery. - internal void PrepareModDiscovery(ModStorage mods) - { - foreach (var (mod, setting) in mods.Zip(_settings).Where(s => s.Second.Settings != null)) - _unused[mod.ModPath.Name] = new ModSettings.SavedSettings(setting.Settings!, mod); - - _settings.Clear(); - } - - /// - /// Apply all mod settings from unused settings to the current set of mods. - /// Also fixes invalid settings. - /// - internal void ApplyModSettings(ModCollection parent, SaveService saver, ModStorage mods) - { - _settings.Capacity = Math.Max(_settings.Capacity, mods.Count); - var settings = this; - if (mods.Aggregate(false, (current, mod) => current | settings.AddMod(mod))) - saver.ImmediateSave(new ModCollectionSave(mods, parent)); - } -} diff --git a/Penumbra/Collections/ResolveData.cs b/Penumbra/Collections/ResolveData.cs index bda877ff..8fe160b3 100644 --- a/Penumbra/Collections/ResolveData.cs +++ b/Penumbra/Collections/ResolveData.cs @@ -23,7 +23,7 @@ public readonly struct ResolveData(ModCollection collection, nint gameObject) { } public override string ToString() - => ModCollection.Identity.Name; + => ModCollection.Name; } public static class ResolveDataExtensions diff --git a/Penumbra/CommandHandler.cs b/Penumbra/CommandHandler.cs index b5d307ef..db8d9aca 100644 --- a/Penumbra/CommandHandler.cs +++ b/Penumbra/CommandHandler.cs @@ -1,10 +1,9 @@ using Dalamud.Game.Command; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Classes; using OtterGui.Services; -using Penumbra.Api.Api; using Penumbra.Api.Enums; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -75,21 +74,20 @@ public class CommandHandler : IDisposable, IApiService _ = argumentList[0].ToLowerInvariant() switch { - "window" => ToggleWindow(arguments), - "enable" => SetPenumbraState(arguments, true), - "disable" => SetPenumbraState(arguments, false), - "toggle" => SetPenumbraState(arguments, null), - "reload" => Reload(arguments), - "redraw" => Redraw(arguments), - "lockui" => SetUiLockState(arguments), - "size" => SetUiMinimumSize(arguments), - "debug" => SetDebug(arguments), - "collection" => SetCollection(arguments), - "mod" => SetMod(arguments), - "bulktag" => SetTag(arguments), - "clearsettings" => ClearSettings(arguments), - "knowledge" => HandleKnowledge(arguments), - _ => PrintHelp(argumentList[0]), + "window" => ToggleWindow(arguments), + "enable" => SetPenumbraState(arguments, true), + "disable" => SetPenumbraState(arguments, false), + "toggle" => SetPenumbraState(arguments, null), + "reload" => Reload(arguments), + "redraw" => Redraw(arguments), + "lockui" => SetUiLockState(arguments), + "size" => SetUiMinimumSize(arguments), + "debug" => SetDebug(arguments), + "collection" => SetCollection(arguments), + "mod" => SetMod(arguments), + "bulktag" => SetTag(arguments), + "knowledge" => HandleKnowledge(arguments), + _ => PrintHelp(argumentList[0]), }; } @@ -127,21 +125,6 @@ public class CommandHandler : IDisposable, IApiService _chat.Print(new SeStringBuilder() .AddCommand("bulktag", "Change multiple mods settings based on their tags. Use without further parameters for more detailed help.") .BuiltString); - _chat.Print(new SeStringBuilder() - .AddCommand("clearsettings", - "Clear all temporary settings applied manually through Penumbra in the current or all collections. Use with 'all' parameter for all.") - .BuiltString); - return true; - } - - private bool ClearSettings(string arguments) - { - if (arguments.Trim().ToLowerInvariant() is "all") - foreach (var collection in _collectionManager.Storage) - _collectionEditor.ClearTemporarySettings(collection); - else - _collectionEditor.ClearTemporarySettings(_collectionManager.Active.Current); - return true; } @@ -342,7 +325,7 @@ public class CommandHandler : IDisposable, IApiService { _chat.Print(collection == null ? $"The {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}" : string.Empty)} is already unassigned" - : $"{collection.Identity.Name} already is the {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}"); + : $"{collection.Name} already is the {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}"); continue; } @@ -379,13 +362,13 @@ public class CommandHandler : IDisposable, IApiService } Print( - $"Removed {oldCollection.Identity.Name} as {type.ToName()} Collection assignment {(identifier.IsValid ? $" for {identifier}." : ".")}"); + $"Removed {oldCollection.Name} as {type.ToName()} Collection assignment {(identifier.IsValid ? $" for {identifier}." : ".")}"); anySuccess = true; continue; } _collectionManager.Active.SetCollection(collection!, type, individualIndex); - Print($"Assigned {collection!.Identity.Name} as {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}"); + Print($"Assigned {collection!.Name} as {type.ToName()} Collection{(identifier.IsValid ? $" for {identifier}." : ".")}"); } return anySuccess; @@ -396,18 +379,16 @@ public class CommandHandler : IDisposable, IApiService if (arguments.Length == 0) { var seString = new SeStringBuilder() - .AddText("Use with /penumbra mod ").AddBlue("[enable|disable|inherit|toggle|").AddGreen("setting").AddBlue("]").AddText(" ") - .AddYellow("[Collection Name]") + .AddText("Use with /penumbra mod ").AddBlue("[enable|disable|inherit|toggle]").AddText(" ").AddYellow("[Collection Name]") .AddText(" | ") - .AddPurple("[Mod Name or Mod Directory Name]") - .AddGreen(" <| [Option Group Name] | [Option1;Option2;...]>"); + .AddPurple("[Mod Name or Mod Directory Name]"); _chat.Print(seString.BuiltString); return true; } var split = arguments.Split(' ', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); var nameSplit = split.Length != 2 - ? [] + ? Array.Empty() : split[1].Split('|', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (nameSplit.Length != 2) { @@ -425,24 +406,6 @@ public class CommandHandler : IDisposable, IApiService if (!GetModCollection(nameSplit[0], out var collection) || collection == ModCollection.Empty) return false; - var groupName = string.Empty; - var optionNames = Array.Empty(); - if (state is 4) - { - var split2 = nameSplit[1].Split('|', 3, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - if (split2.Length < 2) - { - _chat.Print( - "Not enough arguments for changing settings provided. Please add a group name and a list of setting names - which can be empty for multi options."); - return false; - } - - nameSplit[1] = split2[0]; - groupName = split2[1]; - if (split2.Length == 3) - optionNames = split2[2].Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - } - if (!_modManager.TryGetMod(nameSplit[1], nameSplit[1], out var mod)) { _chat.Print(new SeStringBuilder().AddText("The mod ").AddRed(nameSplit[1], true).AddText(" does not exist.") @@ -450,35 +413,12 @@ public class CommandHandler : IDisposable, IApiService return false; } - if (state < 4) - { - if (HandleModState(state, collection!, mod)) - return true; - - _chat.Print(new SeStringBuilder().AddText("Mod ").AddPurple(mod.Name, true) - .AddText("already had the desired state in collection ") - .AddYellow(collection!.Identity.Name, true).AddText(".").BuiltString); - return false; - } - - switch (ModSettingsApi.ConvertModSetting(mod, groupName, optionNames, out var groupIndex, out var setting)) - { - case PenumbraApiEc.OptionGroupMissing: - _chat.Print(new SeStringBuilder().AddText("The mod ").AddRed(nameSplit[1], true).AddText(" has no group ") - .AddGreen(groupName, true).AddText(".").BuiltString); - return false; - case PenumbraApiEc.OptionMissing: - _chat.Print(new SeStringBuilder().AddText("Not all set options in the mod ").AddRed(nameSplit[1], true) - .AddText(" could be found in group ").AddGreen(groupName, true).AddText(".").BuiltString); - return false; - case PenumbraApiEc.Success: - _collectionEditor.SetModSetting(collection!, mod, groupIndex, setting); - Print(() => new SeStringBuilder().AddText("Changed settings of group ").AddGreen(groupName, true).AddText(" in mod ") - .AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection!.Identity.Name, true).AddText(".").BuiltString); - return true; - } + if (HandleModState(state, collection!, mod)) + return true; + _chat.Print(new SeStringBuilder().AddText("Mod ").AddPurple(mod.Name, true) + .AddText("already had the desired state in collection ") + .AddYellow(collection!.Name, true).AddText(".").BuiltString); return false; } @@ -560,7 +500,7 @@ public class CommandHandler : IDisposable, IApiService changes |= HandleModState(state, collection!, mod); if (!changes) - Print(() => new SeStringBuilder().AddText("No mod states were changed in collection ").AddYellow(collection!.Identity.Name, true) + Print(() => new SeStringBuilder().AddText("No mod states were changed in collection ").AddYellow(collection!.Name, true) .AddText(".").BuiltString); return true; @@ -575,7 +515,7 @@ public class CommandHandler : IDisposable, IApiService return true; } - collection = string.Equals(lowerName, ModCollection.Empty.Identity.Name, StringComparison.OrdinalIgnoreCase) + collection = string.Equals(lowerName, ModCollection.Empty.Name, StringComparison.OrdinalIgnoreCase) ? ModCollection.Empty : _collectionManager.Storage.ByIdentifier(lowerName, out var c) ? c @@ -616,14 +556,12 @@ public class CommandHandler : IDisposable, IApiService "toggle" => 2, "inherit" => 3, "inherited" => 3, - "setting" => 4, - "settings" => 4, _ => -1, }; private bool HandleModState(int settingState, ModCollection collection, Mod mod) { - var settings = collection.GetOwnSettings(mod.Index); + var settings = collection.Settings[mod.Index]; switch (settingState) { case 0: @@ -631,7 +569,7 @@ public class CommandHandler : IDisposable, IApiService return false; Print(() => new SeStringBuilder().AddText("Enabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Identity.Name, true) + .AddYellow(collection.Name, true) .AddText(".").BuiltString); return true; @@ -640,7 +578,7 @@ public class CommandHandler : IDisposable, IApiService return false; Print(() => new SeStringBuilder().AddText("Disabled mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Identity.Name, true) + .AddYellow(collection.Name, true) .AddText(".").BuiltString); return true; @@ -651,7 +589,7 @@ public class CommandHandler : IDisposable, IApiService Print(() => new SeStringBuilder().AddText(setting ? "Enabled mod " : "Disabled mod ").AddPurple(mod.Name, true) .AddText(" in collection ") - .AddYellow(collection.Identity.Name, true) + .AddYellow(collection.Name, true) .AddText(".").BuiltString); return true; @@ -660,7 +598,7 @@ public class CommandHandler : IDisposable, IApiService return false; Print(() => new SeStringBuilder().AddText("Set mod ").AddPurple(mod.Name, true).AddText(" in collection ") - .AddYellow(collection.Identity.Name, true) + .AddYellow(collection.Name, true) .AddText(" to inherit.").BuiltString); return true; } diff --git a/Penumbra/Communication/ChangedItemClick.cs b/Penumbra/Communication/ChangedItemClick.cs index 2d27f36a..1aac4454 100644 --- a/Penumbra/Communication/ChangedItemClick.cs +++ b/Penumbra/Communication/ChangedItemClick.cs @@ -12,7 +12,7 @@ namespace Penumbra.Communication; /// Parameter is the clicked object data if any. /// /// -public sealed class ChangedItemClick() : EventWrapper(nameof(ChangedItemClick)) +public sealed class ChangedItemClick() : EventWrapper(nameof(ChangedItemClick)) { public enum Priority { diff --git a/Penumbra/Communication/ChangedItemHover.cs b/Penumbra/Communication/ChangedItemHover.cs index 92d770f7..4e72b558 100644 --- a/Penumbra/Communication/ChangedItemHover.cs +++ b/Penumbra/Communication/ChangedItemHover.cs @@ -10,7 +10,7 @@ namespace Penumbra.Communication; /// Parameter is the hovered object data if any. /// /// -public sealed class ChangedItemHover() : EventWrapper(nameof(ChangedItemHover)) +public sealed class ChangedItemHover() : EventWrapper(nameof(ChangedItemHover)) { public enum Priority { diff --git a/Penumbra/Communication/CharacterUtilityFinished.cs b/Penumbra/Communication/CharacterUtilityFinished.cs deleted file mode 100644 index fbeeb8a7..00000000 --- a/Penumbra/Communication/CharacterUtilityFinished.cs +++ /dev/null @@ -1,23 +0,0 @@ -using OtterGui.Classes; -using Penumbra.Api; -using Penumbra.Interop.Services; - -namespace Penumbra.Communication; - -/// -/// Triggered when the Character Utility becomes ready. -/// -public sealed class CharacterUtilityFinished() : EventWrapper(nameof(CharacterUtilityFinished)) -{ - public enum Priority - { - /// - OnFinishedLoading = int.MaxValue, - - /// - IpcProvider = int.MinValue, - - /// - CollectionCacheManager = 0, - } -} diff --git a/Penumbra/Communication/ModPathChanged.cs b/Penumbra/Communication/ModPathChanged.cs index efe59482..1e4f8d36 100644 --- a/Penumbra/Communication/ModPathChanged.cs +++ b/Penumbra/Communication/ModPathChanged.cs @@ -3,7 +3,6 @@ using Penumbra.Api; using Penumbra.Api.Api; using Penumbra.Mods; using Penumbra.Mods.Manager; -using Penumbra.Services; namespace Penumbra.Communication; @@ -21,14 +20,11 @@ public sealed class ModPathChanged() { public enum Priority { - /// - PcpService = int.MinValue, - /// - ApiMods = int.MinValue + 1, + ApiMods = int.MinValue, /// - ApiModSettings = int.MinValue + 1, + ApiModSettings = int.MinValue, /// EphemeralConfig = -500, diff --git a/Penumbra/Communication/PcpCreation.cs b/Penumbra/Communication/PcpCreation.cs deleted file mode 100644 index ca0cfcf6..00000000 --- a/Penumbra/Communication/PcpCreation.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Classes; - -namespace Penumbra.Communication; - -/// -/// Triggered when the character.json file for a .pcp file is written. -/// -/// Parameter is the JObject that gets written to file. -/// Parameter is the object index of the game object this is written for. -/// Parameter is the full path to the directory being set up for the PCP creation. -/// -/// -public sealed class PcpCreation() : EventWrapper(nameof(PcpCreation)) -{ - public enum Priority - { - /// - ModsApi = int.MinValue, - } -} diff --git a/Penumbra/Communication/PcpParsing.cs b/Penumbra/Communication/PcpParsing.cs deleted file mode 100644 index 95b78951..00000000 --- a/Penumbra/Communication/PcpParsing.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Classes; - -namespace Penumbra.Communication; - -/// -/// Triggered when the character.json file for a .pcp file is parsed and applied. -/// -/// Parameter is parsed JObject that contains the data. -/// Parameter is the identifier of the created mod. -/// Parameter is the GUID of the created collection. -/// -/// -public sealed class PcpParsing() : EventWrapper(nameof(PcpParsing)) -{ - public enum Priority - { - /// - ModsApi = int.MinValue, - } -} diff --git a/Penumbra/Communication/ResolvedFileChanged.cs b/Penumbra/Communication/ResolvedFileChanged.cs index 0c91a18b..75444340 100644 --- a/Penumbra/Communication/ResolvedFileChanged.cs +++ b/Penumbra/Communication/ResolvedFileChanged.cs @@ -1,5 +1,6 @@ using OtterGui.Classes; using Penumbra.Collections; +using Penumbra.Mods; using Penumbra.Mods.Editor; using Penumbra.String.Classes; @@ -32,8 +33,5 @@ public sealed class ResolvedFileChanged() { /// DalamudSubstitutionProvider = 0, - - /// - SchedulerResourceManagementService = 0, } } diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 2991230e..50426b38 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -1,8 +1,8 @@ using Dalamud.Configuration; using Dalamud.Interface.ImGuiNotification; using Newtonsoft.Json; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Services; using OtterGui.Widgets; @@ -18,15 +18,6 @@ using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs; namespace Penumbra; -public record PcpSettings -{ - public bool CreateCollection { get; set; } = true; - public bool AssignCollection { get; set; } = true; - public bool AllowIpc { get; set; } = true; - public bool DisableHandling { get; set; } = false; - public string FolderName { get; set; } = "PCP"; -} - [Serializable] public class Configuration : IPluginConfiguration, ISavable, IService { @@ -48,12 +39,15 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool EnableMods { get => _enableMods; - set => SetField(ref _enableMods, value, ModsEnabled); + set + { + _enableMods = value; + ModsEnabled?.Invoke(value); + } } public string ModDirectory { get; set; } = string.Empty; public string ExportDirectory { get; set; } = string.Empty; - public string WatchDirectory { get; set; } = string.Empty; public bool? UseCrashHandler { get; set; } = null; public bool OpenWindowAtStart { get; set; } = false; @@ -62,28 +56,20 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool HideUiWhenUiHidden { get; set; } = false; public bool UseDalamudUiTextureRedirection { get; set; } = true; - public bool AutoSelectCollection { get; set; } = false; - - public bool ShowModsInLobby { get; set; } = true; - public bool UseCharacterCollectionInMainWindow { get; set; } = true; - public bool UseCharacterCollectionsInCards { get; set; } = true; - public bool UseCharacterCollectionInInspect { get; set; } = true; - public bool UseCharacterCollectionInTryOn { get; set; } = true; - public bool UseOwnerNameForCharacterCollection { get; set; } = true; - public bool UseNoModsInInspect { get; set; } = false; - public bool HideChangedItemFilters { get; set; } = false; - public bool ReplaceNonAsciiOnImport { get; set; } = false; - public bool HidePrioritiesInSelector { get; set; } = false; - public bool HideRedrawBar { get; set; } = false; - public bool HideMachinistOffhandFromChangedItems { get; set; } = true; - public bool DefaultTemporaryMode { get; set; } = false; - public bool EnableDirectoryWatch { get; set; } = false; - public bool EnableAutomaticModImport { get; set; } = false; - public bool EnableCustomShapes { get; set; } = true; - public PcpSettings PcpSettings = new(); - public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; - public ChangedItemMode ChangedItemDisplay { get; set; } = ChangedItemMode.GroupedCollapsed; - public int OptionGroupCollapsibleMin { get; set; } = 5; + public bool ShowModsInLobby { get; set; } = true; + public bool UseCharacterCollectionInMainWindow { get; set; } = true; + public bool UseCharacterCollectionsInCards { get; set; } = true; + public bool UseCharacterCollectionInInspect { get; set; } = true; + public bool UseCharacterCollectionInTryOn { get; set; } = true; + public bool UseOwnerNameForCharacterCollection { get; set; } = true; + public bool UseNoModsInInspect { get; set; } = false; + public bool HideChangedItemFilters { get; set; } = false; + public bool ReplaceNonAsciiOnImport { get; set; } = false; + public bool HidePrioritiesInSelector { get; set; } = false; + public bool HideRedrawBar { get; set; } = false; + public bool HideMachinistOffhandFromChangedItems { get; set; } = true; + public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; + public int OptionGroupCollapsibleMin { get; set; } = 5; public Vector2 MinimumSize = new(Constants.MinimumSizeX, Constants.MinimumSizeY); @@ -98,6 +84,9 @@ public class Configuration : IPluginConfiguration, ISavable, IService [JsonProperty(Order = int.MaxValue)] public ISortMode SortMode = ISortMode.FoldersFirst; + public bool ScaleModSelector { get; set; } = false; + public float ModSelectorAbsoluteSize { get; set; } = Constants.DefaultAbsoluteSize; + public int ModSelectorScaledSize { get; set; } = Constants.DefaultScaledSize; public bool OpenFoldersByDefault { get; set; } = false; public int SingleGroupRadioMax { get; set; } = 2; public string DefaultImportFolder { get; set; } = string.Empty; @@ -105,14 +94,13 @@ public class Configuration : IPluginConfiguration, ISavable, IService public string QuickMoveFolder2 { get; set; } = string.Empty; public string QuickMoveFolder3 { get; set; } = string.Empty; public DoubleModifier DeleteModModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); - public DoubleModifier IncognitoModifier { get; set; } = new(ModifierHotkey.Control); public bool PrintSuccessfulCommandsToChat { get; set; } = true; public bool AutoDeduplicateOnImport { get; set; } = true; public bool AutoReduplicateUiOnImport { get; set; } = true; public bool UseFileSystemCompression { get; set; } = true; public bool EnableHttpApi { get; set; } = true; - public bool MigrateImportedModelsToV6 { get; set; } = true; + public bool MigrateImportedModelsToV6 { get; set; } = true; public bool MigrateImportedMaterialsToLegacy { get; set; } = true; public string DefaultModImportPath { get; set; } = string.Empty; @@ -120,7 +108,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool KeepDefaultMetaChanges { get; set; } = false; public string DefaultModAuthor { get; set; } = DefaultTexToolsData.Author; public bool EditRawTileTransforms { get; set; } = false; - public bool HdrRenderTargets { get; set; } = true; public Dictionary Colors { get; set; } = Enum.GetValues().ToDictionary(c => c, c => c.Data().DefaultColor); @@ -226,45 +213,4 @@ public class Configuration : IPluginConfiguration, ISavable, IService var serializer = new JsonSerializer { Formatting = Formatting.Indented }; serializer.Serialize(jWriter, this); } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool SetField(ref T field, T value, Action? @event, [CallerMemberName] string? propertyName = null) - { - if (EqualityComparer.Default.Equals(value)) - return false; - - var oldValue = field; - field = value; - try - { - @event?.Invoke(oldValue, field); - } - catch (Exception ex) - { - Penumbra.Log.Error($"Error in subscribers updating configuration field {propertyName} from {oldValue} to {field}:\n{ex}"); - throw; - } - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool SetField(ref T field, T value, Action? @event, [CallerMemberName] string? propertyName = null) - { - if (EqualityComparer.Default.Equals(value)) - return false; - - field = value; - try - { - @event?.Invoke(field); - } - catch (Exception ex) - { - Penumbra.Log.Error($"Error in subscribers updating configuration field {propertyName} to {field}:\n{ex}"); - throw; - } - - return true; - } } diff --git a/Penumbra/DebugConfiguration.cs b/Penumbra/DebugConfiguration.cs deleted file mode 100644 index 3f9e8207..00000000 --- a/Penumbra/DebugConfiguration.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Penumbra; - -public class DebugConfiguration -{ - public static bool WriteImcBytesToLog = false; - public static bool UseSkinMaterialProcessing = true; -} diff --git a/Penumbra/EphemeralConfig.cs b/Penumbra/EphemeralConfig.cs index ecb0218f..24ab466b 100644 --- a/Penumbra/EphemeralConfig.cs +++ b/Penumbra/EphemeralConfig.cs @@ -1,7 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Newtonsoft.Json; using OtterGui.Classes; -using OtterGui.FileSystem.Selector; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Communication; @@ -24,10 +23,6 @@ public class EphemeralConfig : ISavable, IDisposable, IService [JsonIgnore] private readonly ModPathChanged _modPathChanged; - public float CurrentModSelectorWidth { get; set; } = 200f; - public float ModSelectorMinimumScale { get; set; } = 0.1f; - public float ModSelectorMaximumScale { get; set; } = 0.5f; - public int Version { get; set; } = Configuration.Constants.CurrentVersion; public int LastSeenVersion { get; set; } = PenumbraChangelog.LastChangelogVersion; public bool DebugSeparateWindow { get; set; } = false; @@ -46,7 +41,6 @@ public class EphemeralConfig : ISavable, IDisposable, IService public string LastModPath { get; set; } = string.Empty; public bool AdvancedEditingOpen { get; set; } = false; public bool ForceRedrawOnFileChange { get; set; } = false; - public bool IncognitoMode { get; set; } = false; /// /// Load the current configuration. diff --git a/Penumbra/Import/Models/Export/MaterialExporter.cs b/Penumbra/Import/Models/Export/MaterialExporter.cs index 0d91534e..121e6eed 100644 --- a/Penumbra/Import/Models/Export/MaterialExporter.cs +++ b/Penumbra/Import/Models/Export/MaterialExporter.cs @@ -1,7 +1,6 @@ using Lumina.Data.Parsing; using Penumbra.GameData.Files; using Penumbra.GameData.Files.MaterialStructs; -using Penumbra.UI.AdvancedWindow.Materials; using SharpGLTF.Materials; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Advanced; @@ -141,13 +140,13 @@ public class MaterialExporter // 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(MtrlTab.PseudoSqrtRgb(lerpedDiffuse), 1)); + baseColorSpan[x].FromVector4(new Vector4(lerpedDiffuse, 1)); var lerpedSpecularColor = Vector3.Lerp((Vector3)prevRow.SpecularColor, (Vector3)nextRow.SpecularColor, rowBlend); - specularSpan[x].FromVector4(new Vector4(MtrlTab.PseudoSqrtRgb(lerpedSpecularColor), 1)); + specularSpan[x].FromVector4(new Vector4(lerpedSpecularColor, 1)); var lerpedEmissive = Vector3.Lerp((Vector3)prevRow.EmissiveColor, (Vector3)nextRow.EmissiveColor, rowBlend); - emissiveSpan[x].FromVector4(new Vector4(MtrlTab.PseudoSqrtRgb(lerpedEmissive), 1)); + emissiveSpan[x].FromVector4(new Vector4(lerpedEmissive, 1)); } } } @@ -289,7 +288,7 @@ public class MaterialExporter const uint valueFace = 0x6E5B8F10; var isFace = material.Mtrl.ShaderPackage.ShaderKeys - .Any(key => key is { Key: categoryHairType, Value: valueFace }); + .Any(key => key is { Category: categoryHairType, Value: valueFace }); var normal = material.Textures[TextureUsage.SamplerNormal]; var mask = material.Textures[TextureUsage.SamplerMask]; @@ -364,7 +363,7 @@ public class MaterialExporter // Face is the default for the skin shader, so a lack of skin type category is also correct. var isFace = !material.Mtrl.ShaderPackage.ShaderKeys - .Any(key => key.Key == categorySkinType && key.Value != valueFace); + .Any(key => key.Category == categorySkinType && key.Value != valueFace); // TODO: There's more nuance to skin than this, but this should be enough for a baseline reference. // TODO: Specular? diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index 6ea2b284..73160615 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -2,11 +2,12 @@ using System.Collections.Immutable; using System.Text.Json; using System.Text.Json.Nodes; using Lumina.Extensions; -using OtterGui.Extensions; +using OtterGui; using Penumbra.GameData.Files; using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Geometry; using SharpGLTF.Geometry.VertexTypes; +using SharpGLTF.IO; using SharpGLTF.Materials; using SharpGLTF.Scenes; @@ -83,12 +84,9 @@ public class MeshExporter _boneIndexMap = BuildBoneIndexMap(skeleton.Value); var usages = _mdl.VertexDeclarations[_meshIndex].VertexElements - .GroupBy(ele => (MdlFile.VertexUsage)ele.Usage, ele => ele) .ToImmutableDictionary( - g => g.Key, - g => g.OrderBy(ele => ele.UsageIndex) // OrderBy UsageIndex is probably unnecessary as they're probably already be in order - .Select(ele => (MdlFile.VertexType)ele.Type) - .ToList() + element => (MdlFile.VertexUsage)element.Usage, + element => (MdlFile.VertexType)element.Type ); _geometryType = GetGeometryType(usages); @@ -114,7 +112,6 @@ public class MeshExporter var indexMap = new Dictionary(); // #TODO @ackwell maybe fix for V6 Models, I think this works fine. - foreach (var (xivBoneIndex, tableIndex) in xivBoneTable.BoneIndex.Take((int)xivBoneTable.BoneCount).WithIndex()) { var boneName = _mdl.Bones[xivBoneIndex]; @@ -281,22 +278,18 @@ public class MeshExporter var sortedElements = _mdl.VertexDeclarations[_meshIndex].VertexElements .OrderBy(element => element.Offset) + .Select(element => ((MdlFile.VertexUsage)element.Usage, element)) .ToList(); + var vertices = new List(); - var attributes = new Dictionary>(); + var attributes = new Dictionary(); for (var vertexIndex = 0; vertexIndex < XivMesh.VertexCount; vertexIndex++) { attributes.Clear(); - attributes = sortedElements - .GroupBy(element => element.Usage) - .ToDictionary( - x => (MdlFile.VertexUsage)x.Key, - x => x.OrderBy(ele => ele.UsageIndex) // Once again, OrderBy UsageIndex is probably unnecessary - .Select(ele => ReadVertexAttribute((MdlFile.VertexType)ele.Type, streams[ele.Stream])) - .ToList() - ); - + + foreach (var (usage, element) in sortedElements) + attributes[usage] = ReadVertexAttribute((MdlFile.VertexType)element.Type, streams[element.Stream]); var vertexGeometry = BuildVertexGeometry(attributes); var vertexMaterial = BuildVertexMaterial(attributes); @@ -327,7 +320,7 @@ public class MeshExporter } /// Get the vertex geometry type for this mesh's vertex usages. - private Type GetGeometryType(IReadOnlyDictionary> usages) + private Type GetGeometryType(IReadOnlyDictionary usages) { if (!usages.ContainsKey(MdlFile.VertexUsage.Position)) throw _notifier.Exception("Mesh does not contain position vertex elements."); @@ -342,29 +335,29 @@ public class MeshExporter } /// Build a geometry vertex from a vertex's attributes. - private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary> attributes) + private IVertexGeometry BuildVertexGeometry(IReadOnlyDictionary attributes) { if (_geometryType == typeof(VertexPosition)) return new VertexPosition( - ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)) + ToVector3(attributes[MdlFile.VertexUsage.Position]) ); if (_geometryType == typeof(VertexPositionNormal)) return new VertexPositionNormal( - ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)), - ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)) + ToVector3(attributes[MdlFile.VertexUsage.Position]), + ToVector3(attributes[MdlFile.VertexUsage.Normal]) ); if (_geometryType == typeof(VertexPositionNormalTangent)) { // (Bi)tangents are universally stored as ByteFloat4, which uses 0..1 to represent the full -1..1 range. // TODO: While this assumption is safe, it would be sensible to actually check. - var bitangent = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Tangent1)) * 2 - Vector4.One; - + var bitangent = ToVector4(attributes[MdlFile.VertexUsage.Tangent1]) * 2 - Vector4.One; + return new VertexPositionNormalTangent( - ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Position)), - ToVector3(GetFirstSafe(attributes, MdlFile.VertexUsage.Normal)), - bitangent.SanitizeTangent() + ToVector3(attributes[MdlFile.VertexUsage.Position]), + ToVector3(attributes[MdlFile.VertexUsage.Normal]), + bitangent ); } @@ -372,90 +365,60 @@ public class MeshExporter } /// Get the vertex material type for this mesh's vertex usages. - private Type GetMaterialType(IReadOnlyDictionary> usages) + private Type GetMaterialType(IReadOnlyDictionary usages) { var uvCount = 0; - if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var list)) - { - foreach (var type in list) + if (usages.TryGetValue(MdlFile.VertexUsage.UV, out var type)) + uvCount = type switch { - uvCount += type switch - { - MdlFile.VertexType.Half2 => 1, - MdlFile.VertexType.Half4 => 2, - MdlFile.VertexType.Single2 => 1, - MdlFile.VertexType.Single4 => 2, - _ => throw _notifier.Exception($"Unexpected UV vertex type {type}."), - }; - } - } - - usages.TryGetValue(MdlFile.VertexUsage.Color, out var colours); - var nColors = colours?.Count ?? 0; + MdlFile.VertexType.Half2 => 1, + MdlFile.VertexType.Half4 => 2, + MdlFile.VertexType.Single2 => 1, + MdlFile.VertexType.Single4 => 2, + _ => throw _notifier.Exception($"Unexpected UV vertex type {type}."), + }; var materialUsages = ( uvCount, - nColors + usages.ContainsKey(MdlFile.VertexUsage.Color) ); return materialUsages switch { - (3, 2) => typeof(VertexTexture3Color2Ffxiv), - (3, 1) => typeof(VertexTexture3ColorFfxiv), - (3, 0) => typeof(VertexTexture3), - (2, 2) => typeof(VertexTexture2Color2Ffxiv), - (2, 1) => typeof(VertexTexture2ColorFfxiv), - (2, 0) => typeof(VertexTexture2), - (1, 2) => typeof(VertexTexture1Color2Ffxiv), - (1, 1) => typeof(VertexTexture1ColorFfxiv), - (1, 0) => typeof(VertexTexture1), - (0, 2) => typeof(VertexColor2Ffxiv), - (0, 1) => typeof(VertexColorFfxiv), - (0, 0) => typeof(VertexEmpty), + (2, true) => typeof(VertexTexture2ColorFfxiv), + (2, false) => typeof(VertexTexture2), + (1, true) => typeof(VertexTexture1ColorFfxiv), + (1, false) => typeof(VertexTexture1), + (0, true) => typeof(VertexColorFfxiv), + (0, false) => typeof(VertexEmpty), - _ => throw _notifier.Exception($"Unhandled UV/color count of {uvCount}/{nColors} encountered."), + _ => throw new Exception("Unreachable."), }; } /// Build a material vertex from a vertex's attributes. - private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary> attributes) + private IVertexMaterial BuildVertexMaterial(IReadOnlyDictionary attributes) { if (_materialType == typeof(VertexEmpty)) return new VertexEmpty(); if (_materialType == typeof(VertexColorFfxiv)) - return new VertexColorFfxiv(ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color))); - - if (_materialType == typeof(VertexColor2Ffxiv)) - { - var (color0, color1) = GetBothSafe(attributes, MdlFile.VertexUsage.Color); - return new VertexColor2Ffxiv(ToVector4(color0), ToVector4(color1)); - } + return new VertexColorFfxiv(ToVector4(attributes[MdlFile.VertexUsage.Color])); if (_materialType == typeof(VertexTexture1)) - return new VertexTexture1(ToVector2(GetFirstSafe(attributes, MdlFile.VertexUsage.UV))); + return new VertexTexture1(ToVector2(attributes[MdlFile.VertexUsage.UV])); if (_materialType == typeof(VertexTexture1ColorFfxiv)) return new VertexTexture1ColorFfxiv( - ToVector2(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)), - ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) + ToVector2(attributes[MdlFile.VertexUsage.UV]), + ToVector4(attributes[MdlFile.VertexUsage.Color]) ); - if (_materialType == typeof(VertexTexture1Color2Ffxiv)) - { - var (color0, color1) = GetBothSafe(attributes, MdlFile.VertexUsage.Color); - return new VertexTexture1Color2Ffxiv( - ToVector2(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)), - ToVector4(color0), - ToVector4(color1) - ); - } - // XIV packs two UVs into a single vec4 attribute. if (_materialType == typeof(VertexTexture2)) { - var uv = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)); + var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]); return new VertexTexture2( new Vector2(uv.X, uv.Y), new Vector2(uv.Z, uv.W) @@ -464,63 +427,11 @@ public class MeshExporter if (_materialType == typeof(VertexTexture2ColorFfxiv)) { - var uv = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)); + var uv = ToVector4(attributes[MdlFile.VertexUsage.UV]); return new VertexTexture2ColorFfxiv( new Vector2(uv.X, uv.Y), new Vector2(uv.Z, uv.W), - ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) - ); - } - - if (_materialType == typeof(VertexTexture2Color2Ffxiv)) - { - var uv = ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.UV)); - var (color0, color1) = GetBothSafe(attributes, MdlFile.VertexUsage.Color); - - return new VertexTexture2Color2Ffxiv( - new Vector2(uv.X, uv.Y), - new Vector2(uv.Z, uv.W), - ToVector4(color0), - ToVector4(color1) - ); - } - - if (_materialType == typeof(VertexTexture3)) - { - // Not 100% sure about this - var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]); - var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]); - return new VertexTexture3( - new Vector2(uv0.X, uv0.Y), - new Vector2(uv0.Z, uv0.W), - new Vector2(uv1.X, uv1.Y) - ); - } - - if (_materialType == typeof(VertexTexture3ColorFfxiv)) - { - var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]); - var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]); - return new VertexTexture3ColorFfxiv( - new Vector2(uv0.X, uv0.Y), - new Vector2(uv0.Z, uv0.W), - new Vector2(uv1.X, uv1.Y), - ToVector4(GetFirstSafe(attributes, MdlFile.VertexUsage.Color)) - ); - } - - if (_materialType == typeof(VertexTexture3Color2Ffxiv)) - { - var uv0 = ToVector4(attributes[MdlFile.VertexUsage.UV][0]); - var uv1 = ToVector4(attributes[MdlFile.VertexUsage.UV][1]); - var (color0, color1) = GetBothSafe(attributes, MdlFile.VertexUsage.Color); - - return new VertexTexture3Color2Ffxiv( - new Vector2(uv0.X, uv0.Y), - new Vector2(uv0.Z, uv0.W), - new Vector2(uv1.X, uv1.Y), - ToVector4(color0), - ToVector4(color1) + ToVector4(attributes[MdlFile.VertexUsage.Color]) ); } @@ -528,20 +439,25 @@ public class MeshExporter } /// Get the vertex skinning type for this mesh's vertex usages. - private Type GetSkinningType(IReadOnlyDictionary> usages) + private static Type GetSkinningType(IReadOnlyDictionary usages) { if (usages.ContainsKey(MdlFile.VertexUsage.BlendWeights) && usages.ContainsKey(MdlFile.VertexUsage.BlendIndices)) { - return GetFirstSafe(usages, MdlFile.VertexUsage.BlendWeights) == MdlFile.VertexType.UShort4 - ? typeof(VertexJoints8) - : typeof(VertexJoints4); + if (usages[MdlFile.VertexUsage.BlendWeights] == MdlFile.VertexType.UShort4) + { + return typeof(VertexJoints8); + } + else + { + return typeof(VertexJoints4); + } } return typeof(VertexEmpty); } /// Build a skinning vertex from a vertex's attributes. - private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary> attributes) + private IVertexSkinning BuildVertexSkinning(IReadOnlyDictionary attributes) { if (_skinningType == typeof(VertexEmpty)) return new VertexEmpty(); @@ -551,8 +467,8 @@ public class MeshExporter if (_boneIndexMap == null) throw _notifier.Exception("Tried to build skinned vertex but no bone mappings are available."); - var indiciesData = GetFirstSafe(attributes, MdlFile.VertexUsage.BlendIndices); - var weightsData = GetFirstSafe(attributes, MdlFile.VertexUsage.BlendWeights); + var indiciesData = attributes[MdlFile.VertexUsage.BlendIndices]; + var weightsData = attributes[MdlFile.VertexUsage.BlendWeights]; var indices = ToByteArray(indiciesData); var weights = ToFloatArray(weightsData); @@ -579,28 +495,6 @@ public class MeshExporter throw _notifier.Exception($"Unknown skinning type {_skinningType}"); } - /// Check that the list has length 1 for any case where this is expected and return the one entry. - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private T GetFirstSafe(IReadOnlyDictionary> attributes, MdlFile.VertexUsage usage) - { - var list = attributes[usage]; - if (list.Count != 1) - throw _notifier.Exception($"Multiple usage indices encountered for {usage}."); - - return list[0]; - } - - /// Check that the list has length 2 for any case where this is expected and return both entries. - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private (T First, T Second) GetBothSafe(IReadOnlyDictionary> attributes, MdlFile.VertexUsage usage) - { - var list = attributes[usage]; - if (list.Count != 2) - throw _notifier.Exception($"{list.Count} usage indices encountered for {usage}, but expected 2."); - - return (list[0], list[1]); - } - /// Convert a vertex attribute value to a Vector2. Supported inputs are Vector2, Vector3, and Vector4. private static Vector2 ToVector2(object data) => data switch diff --git a/Penumbra/Import/Models/Export/VertexFragment.cs b/Penumbra/Import/Models/Export/VertexFragment.cs index 463c59fc..eff34d54 100644 --- a/Penumbra/Import/Models/Export/VertexFragment.cs +++ b/Penumbra/Import/Models/Export/VertexFragment.cs @@ -1,3 +1,4 @@ +using System; using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.Memory; using SharpGLTF.Schema2; @@ -10,7 +11,7 @@ Realistically, it will need to stick around until transforms/mutations are built and there's reason to overhaul the export pipeline. */ -public struct VertexColorFfxiv(Vector4 ffxivColor) : IVertexCustom +public struct VertexColorFfxiv : IVertexCustom { public IEnumerable> GetEncodingAttributes() { @@ -19,7 +20,7 @@ public struct VertexColorFfxiv(Vector4 ffxivColor) : IVertexCustom new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); } - public Vector4 FfxivColor = ffxivColor; + public Vector4 FfxivColor; public int MaxColors => 0; @@ -32,6 +33,9 @@ public struct VertexColorFfxiv(Vector4 ffxivColor) : IVertexCustom public IEnumerable CustomAttributes => CustomNames; + public VertexColorFfxiv(Vector4 ffxivColor) + => FfxivColor = ffxivColor; + public void Add(in VertexMaterialDelta delta) { } @@ -84,104 +88,7 @@ public struct VertexColorFfxiv(Vector4 ffxivColor) : IVertexCustom } } -public struct VertexColor2Ffxiv(Vector4 ffxivColor0, Vector4 ffxivColor1) : IVertexCustom -{ - public IEnumerable> GetEncodingAttributes() - { - yield return new KeyValuePair("_FFXIV_COLOR_0", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - yield return new KeyValuePair("_FFXIV_COLOR_1", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - } - - public Vector4 FfxivColor0 = ffxivColor0; - public Vector4 FfxivColor1 = ffxivColor1; - - public int MaxColors - => 0; - - public int MaxTextCoords - => 0; - - private static readonly string[] CustomNames = ["_FFXIV_COLOR_0", "_FFXIV_COLOR_1"]; - - public IEnumerable CustomAttributes - => CustomNames; - - public void Add(in VertexMaterialDelta delta) - { } - - public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => 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) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0": - value = FfxivColor0; - return true; - - case "_FFXIV_COLOR_1": - value = FfxivColor1; - return true; - - default: - value = null; - return false; - } - } - - public void SetCustomAttribute(string attributeName, object value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0" when value is Vector4 valueVector4: - FfxivColor0 = valueVector4; - break; - case "_FFXIV_COLOR_1" when value is Vector4 valueVector4: - FfxivColor1 = valueVector4; - break; - } - } - - public Vector4 GetColor(int index) - => throw new ArgumentOutOfRangeException(nameof(index)); - - public void SetColor(int setIndex, Vector4 color) - { } - - public void Validate() - { - var components = new[] - { - FfxivColor0.X, - FfxivColor0.Y, - FfxivColor0.Z, - FfxivColor0.W, - }; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor0)); - components = - [ - FfxivColor1.X, - FfxivColor1.Y, - FfxivColor1.Z, - FfxivColor1.W, - ]; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor1)); - } -} - - -public struct VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) : IVertexCustom +public struct VertexTexture1ColorFfxiv : IVertexCustom { public IEnumerable> GetEncodingAttributes() { @@ -191,9 +98,9 @@ public struct VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) : new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); } - public Vector2 TexCoord0 = texCoord0; + public Vector2 TexCoord0; - public Vector4 FfxivColor = ffxivColor; + public Vector4 FfxivColor; public int MaxColors => 0; @@ -206,6 +113,12 @@ public struct VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) : public IEnumerable CustomAttributes => CustomNames; + public VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) + { + TexCoord0 = texCoord0; + FfxivColor = ffxivColor; + } + public void Add(in VertexMaterialDelta delta) { TexCoord0 += delta.TexCoord0Delta; @@ -269,119 +182,7 @@ public struct VertexTexture1ColorFfxiv(Vector2 texCoord0, Vector4 ffxivColor) : } } -public struct VertexTexture1Color2Ffxiv(Vector2 texCoord0, Vector4 ffxivColor0, Vector4 ffxivColor1) : IVertexCustom -{ - public IEnumerable> GetEncodingAttributes() - { - yield return new KeyValuePair("TEXCOORD_0", - new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false)); - yield return new KeyValuePair("_FFXIV_COLOR_0", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - yield return new KeyValuePair("_FFXIV_COLOR_1", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - } - - public Vector2 TexCoord0 = texCoord0; - - public Vector4 FfxivColor0 = ffxivColor0; - public Vector4 FfxivColor1 = ffxivColor1; - - public int MaxColors - => 0; - - public int MaxTextCoords - => 1; - - private static readonly string[] CustomNames = ["_FFXIV_COLOR_0", "_FFXIV_COLOR_1"]; - - public IEnumerable CustomAttributes - => CustomNames; - - public void Add(in VertexMaterialDelta delta) - { - TexCoord0 += delta.TexCoord0Delta; - } - - public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), Vector2.Zero); - - public Vector2 GetTexCoord(int index) - => index switch - { - 0 => TexCoord0, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - public void SetTexCoord(int setIndex, Vector2 coord) - { - if (setIndex == 0) - TexCoord0 = coord; - if (setIndex >= 1) - throw new ArgumentOutOfRangeException(nameof(setIndex)); - } - - public bool TryGetCustomAttribute(string attributeName, out object? value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0": - value = FfxivColor0; - return true; - - case "_FFXIV_COLOR_1": - value = FfxivColor1; - return true; - - default: - value = null; - return false; - } - } - - public void SetCustomAttribute(string attributeName, object value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0" when value is Vector4 valueVector4: - FfxivColor0 = valueVector4; - break; - case "_FFXIV_COLOR_1" when value is Vector4 valueVector4: - FfxivColor1 = valueVector4; - break; - } - } - - public Vector4 GetColor(int index) - => throw new ArgumentOutOfRangeException(nameof(index)); - - public void SetColor(int setIndex, Vector4 color) - { } - - public void Validate() - { - var components = new[] - { - FfxivColor0.X, - FfxivColor0.Y, - FfxivColor0.Z, - FfxivColor0.W, - }; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor0)); - components = - [ - FfxivColor1.X, - FfxivColor1.Y, - FfxivColor1.Z, - FfxivColor1.W, - ]; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor1)); - } -} - - -public struct VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector4 ffxivColor) : IVertexCustom +public struct VertexTexture2ColorFfxiv : IVertexCustom { public IEnumerable> GetEncodingAttributes() { @@ -393,9 +194,9 @@ public struct VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vec new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); } - public Vector2 TexCoord0 = texCoord0; - public Vector2 TexCoord1 = texCoord1; - public Vector4 FfxivColor = ffxivColor; + public Vector2 TexCoord0; + public Vector2 TexCoord1; + public Vector4 FfxivColor; public int MaxColors => 0; @@ -408,6 +209,13 @@ public struct VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vec public IEnumerable CustomAttributes => CustomNames; + public VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector4 ffxivColor) + { + TexCoord0 = texCoord0; + TexCoord1 = texCoord1; + FfxivColor = ffxivColor; + } + public void Add(in VertexMaterialDelta delta) { TexCoord0 += delta.TexCoord0Delta; @@ -474,346 +282,3 @@ public struct VertexTexture2ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vec throw new ArgumentOutOfRangeException(nameof(FfxivColor)); } } - -public struct VertexTexture2Color2Ffxiv(Vector2 texCoord0, Vector2 texCoord1, Vector4 ffxivColor0, Vector4 ffxivColor1) : IVertexCustom -{ - 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_0", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - yield return new KeyValuePair("_FFXIV_COLOR_1", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - } - - public Vector2 TexCoord0 = texCoord0; - public Vector2 TexCoord1 = texCoord1; - public Vector4 FfxivColor0 = ffxivColor0; - public Vector4 FfxivColor1 = ffxivColor1; - - public int MaxColors - => 0; - - public int MaxTextCoords - => 2; - - private static readonly string[] CustomNames = ["_FFXIV_COLOR_0", "_FFXIV_COLOR_1"]; - - public IEnumerable CustomAttributes - => CustomNames; - - public void Add(in VertexMaterialDelta delta) - { - TexCoord0 += delta.TexCoord0Delta; - TexCoord1 += delta.TexCoord1Delta; - } - - public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); - - public Vector2 GetTexCoord(int index) - => index switch - { - 0 => TexCoord0, - 1 => TexCoord1, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - 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)); - } - - public bool TryGetCustomAttribute(string attributeName, out object? value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0": - value = FfxivColor0; - return true; - - case "_FFXIV_COLOR_1": - value = FfxivColor1; - return true; - - default: - value = null; - return false; - } - } - - public void SetCustomAttribute(string attributeName, object value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0" when value is Vector4 valueVector4: - FfxivColor0 = valueVector4; - break; - case "_FFXIV_COLOR_1" when value is Vector4 valueVector4: - FfxivColor1 = valueVector4; - break; - } - } - - public Vector4 GetColor(int index) - => throw new ArgumentOutOfRangeException(nameof(index)); - - public void SetColor(int setIndex, Vector4 color) - { } - - public void Validate() - { - var components = new[] - { - FfxivColor0.X, - FfxivColor0.Y, - FfxivColor0.Z, - FfxivColor0.W, - }; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor0)); - components = - [ - FfxivColor1.X, - FfxivColor1.Y, - FfxivColor1.Z, - FfxivColor1.W, - ]; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor1)); - } - -} - -public struct VertexTexture3ColorFfxiv(Vector2 texCoord0, Vector2 texCoord1, Vector2 texCoord2, Vector4 ffxivColor) - : IVertexCustom -{ - 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("TEXCOORD_2", - new AttributeFormat(DimensionType.VEC2, EncodingType.FLOAT, false)); - yield return new KeyValuePair("_FFXIV_COLOR", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - } - - public Vector2 TexCoord0 = texCoord0; - public Vector2 TexCoord1 = texCoord1; - public Vector2 TexCoord2 = texCoord2; - public Vector4 FfxivColor = ffxivColor; - - public int MaxColors - => 0; - - public int MaxTextCoords - => 3; - - private static readonly string[] CustomNames = ["_FFXIV_COLOR"]; - - public IEnumerable CustomAttributes - => CustomNames; - - public void Add(in VertexMaterialDelta delta) - { - TexCoord0 += delta.TexCoord0Delta; - TexCoord1 += delta.TexCoord1Delta; - TexCoord2 += delta.TexCoord2Delta; - } - - public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); - - public Vector2 GetTexCoord(int index) - => index switch - { - 0 => TexCoord0, - 1 => TexCoord1, - 2 => TexCoord2, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - public void SetTexCoord(int setIndex, Vector2 coord) - { - if (setIndex == 0) - TexCoord0 = coord; - if (setIndex == 1) - TexCoord1 = coord; - if (setIndex == 2) - TexCoord2 = coord; - if (setIndex >= 3) - throw new ArgumentOutOfRangeException(nameof(setIndex)); - } - - public bool TryGetCustomAttribute(string attributeName, out object? value) - { - switch (attributeName) - { - case "_FFXIV_COLOR": - value = FfxivColor; - return true; - - default: - value = null; - return false; - } - } - - public void SetCustomAttribute(string attributeName, object value) - { - if (attributeName == "_FFXIV_COLOR" && value is Vector4 valueVector4) - FfxivColor = valueVector4; - } - - public Vector4 GetColor(int index) - => 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, - }; - if (components.Any(component => component is < 0f or > 1f)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor)); - } -} - -public struct VertexTexture3Color2Ffxiv(Vector2 texCoord0, Vector2 texCoord1, Vector2 texCoord2, Vector4 ffxivColor0, Vector4 ffxivColor1) - : IVertexCustom -{ - 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_0", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - yield return new KeyValuePair("_FFXIV_COLOR_1", - new AttributeFormat(DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true)); - } - - public Vector2 TexCoord0 = texCoord0; - public Vector2 TexCoord1 = texCoord1; - public Vector2 TexCoord2 = texCoord2; - public Vector4 FfxivColor0 = ffxivColor0; - public Vector4 FfxivColor1 = ffxivColor1; - - public int MaxColors - => 0; - - public int MaxTextCoords - => 3; - - private static readonly string[] CustomNames = ["_FFXIV_COLOR_0", "_FFXIV_COLOR_1"]; - - public IEnumerable CustomAttributes - => CustomNames; - - public void Add(in VertexMaterialDelta delta) - { - TexCoord0 += delta.TexCoord0Delta; - TexCoord1 += delta.TexCoord1Delta; - TexCoord2 += delta.TexCoord2Delta; - } - - public VertexMaterialDelta Subtract(IVertexMaterial baseValue) - => new(Vector4.Zero, Vector4.Zero, TexCoord0 - baseValue.GetTexCoord(0), TexCoord1 - baseValue.GetTexCoord(1)); - - public Vector2 GetTexCoord(int index) - => index switch - { - 0 => TexCoord0, - 1 => TexCoord1, - 2 => TexCoord2, - _ => throw new ArgumentOutOfRangeException(nameof(index)), - }; - - public void SetTexCoord(int setIndex, Vector2 coord) - { - if (setIndex == 0) - TexCoord0 = coord; - if (setIndex == 1) - TexCoord1 = coord; - if (setIndex == 2) - TexCoord2 = coord; - if (setIndex >= 3) - throw new ArgumentOutOfRangeException(nameof(setIndex)); - } - - public bool TryGetCustomAttribute(string attributeName, out object? value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0": - value = FfxivColor0; - return true; - - case "_FFXIV_COLOR_1": - value = FfxivColor1; - return true; - - default: - value = null; - return false; - } - } - - public void SetCustomAttribute(string attributeName, object value) - { - switch (attributeName) - { - case "_FFXIV_COLOR_0" when value is Vector4 valueVector4: - FfxivColor0 = valueVector4; - break; - case "_FFXIV_COLOR_1" when value is Vector4 valueVector4: - FfxivColor1 = valueVector4; - break; - } - } - - public Vector4 GetColor(int index) - => throw new ArgumentOutOfRangeException(nameof(index)); - - public void SetColor(int setIndex, Vector4 color) - { } - - public void Validate() - { - var components = new[] - { - FfxivColor0.X, - FfxivColor0.Y, - FfxivColor0.Z, - FfxivColor0.W, - }; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor0)); - components = - [ - FfxivColor1.X, - FfxivColor1.Y, - FfxivColor1.Z, - FfxivColor1.W, - ]; - if (components.Any(component => component is < 0 or > 1)) - throw new ArgumentOutOfRangeException(nameof(FfxivColor1)); - } -} diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index 16fe2ca0..6a46fb9f 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -1,5 +1,5 @@ using Lumina.Data.Parsing; -using OtterGui.Extensions; +using OtterGui; using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Schema2; diff --git a/Penumbra/Import/Models/Import/ModelImporter.cs b/Penumbra/Import/Models/Import/ModelImporter.cs index f4eefccc..a141d754 100644 --- a/Penumbra/Import/Models/Import/ModelImporter.cs +++ b/Penumbra/Import/Models/Import/ModelImporter.cs @@ -1,5 +1,5 @@ using Lumina.Data.Parsing; -using OtterGui.Extensions; +using OtterGui; using Penumbra.GameData.Files; using Penumbra.GameData.Files.ModelStructs; using SharpGLTF.Schema2; @@ -8,9 +8,6 @@ namespace Penumbra.Import.Models.Import; public partial class ModelImporter(ModelRoot model, IoNotifier notifier) { - public const int BoneLimit = 128; - public const int MaterialLimit = 10; - public static MdlFile Import(ModelRoot model, IoNotifier notifier) { var importer = new ModelImporter(model, notifier); @@ -211,9 +208,10 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) if (index >= 0) return (ushort)index; + // If there's already 4 materials, we can't add any more. // TODO: permit, with a warning to reduce, and validation in MdlTab. var count = _materials.Count; - if (count >= MaterialLimit) + if (count >= 4) return 0; _materials.Add(materialName); @@ -236,11 +234,11 @@ public partial class ModelImporter(ModelRoot model, IoNotifier notifier) boneIndices.Add((ushort)boneIndex); } - if (boneIndices.Count > BoneLimit) - throw notifier.Exception($"XIV does not support meshes weighted to a total of more than {BoneLimit} bones."); + if (boneIndices.Count > 64) + throw notifier.Exception("XIV does not support meshes weighted to a total of more than 64 bones."); - var boneIndicesArray = new ushort[BoneLimit]; - boneIndices.CopyTo(boneIndicesArray); + var boneIndicesArray = new ushort[64]; + Array.Copy(boneIndices.ToArray(), boneIndicesArray, boneIndices.Count); var boneTableIndex = _boneTables.Count; _boneTables.Add(new BoneTableStruct() diff --git a/Penumbra/Import/Models/Import/PrimitiveImporter.cs b/Penumbra/Import/Models/Import/PrimitiveImporter.cs index 57c7929f..5df7597e 100644 --- a/Penumbra/Import/Models/Import/PrimitiveImporter.cs +++ b/Penumbra/Import/Models/Import/PrimitiveImporter.cs @@ -1,5 +1,5 @@ using Lumina.Data.Parsing; -using OtterGui.Extensions; +using OtterGui; using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Import; diff --git a/Penumbra/Import/Models/Import/SubMeshImporter.cs b/Penumbra/Import/Models/Import/SubMeshImporter.cs index 6aa46fb6..df08eea3 100644 --- a/Penumbra/Import/Models/Import/SubMeshImporter.cs +++ b/Penumbra/Import/Models/Import/SubMeshImporter.cs @@ -1,6 +1,6 @@ using System.Text.Json; using Lumina.Data.Parsing; -using OtterGui.Extensions; +using OtterGui; using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Import; diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index 155fa833..a1c3246b 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -319,7 +319,7 @@ public class VertexAttribute var normals = normalAccessor.AsVector3Array(); var tangents = accessors.TryGetValue("TANGENT", out var accessor) - ? accessor.AsVector4Array().ToArray() + ? accessor.AsVector4Array() : CalculateTangents(accessors, indices, normals, notifier); if (tangents == null) diff --git a/Penumbra/Import/Models/ModelExtensions.cs b/Penumbra/Import/Models/ModelExtensions.cs deleted file mode 100644 index 2edb3ca4..00000000 --- a/Penumbra/Import/Models/ModelExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Penumbra.Import.Models; - -public static class ModelExtensions -{ - // https://github.com/vpenades/SharpGLTF/blob/2073cf3cd671f8ecca9667f9a8c7f04ed865d3ac/src/Shared/_Extensions.cs#L158 - private const float UnitLengthThresholdVec3 = 0.00674f; - private const float UnitLengthThresholdVec4 = 0.00769f; - - internal static bool _IsFinite(this float value) - { - return float.IsFinite(value); - } - - internal static bool _IsFinite(this Vector2 v) - { - return v.X._IsFinite() && v.Y._IsFinite(); - } - - internal static bool _IsFinite(this Vector3 v) - { - return v.X._IsFinite() && v.Y._IsFinite() && v.Z._IsFinite(); - } - - internal static bool _IsFinite(this in Vector4 v) - { - return v.X._IsFinite() && v.Y._IsFinite() && v.Z._IsFinite() && v.W._IsFinite(); - } - - internal static Boolean IsNormalized(this Vector3 normal) - { - if (!normal._IsFinite()) return false; - - return Math.Abs(normal.Length() - 1) <= UnitLengthThresholdVec3; - } - - internal static void ValidateNormal(this Vector3 normal, string msg) - { - if (!normal._IsFinite()) throw new NotFiniteNumberException($"{msg} is invalid."); - - if (!normal.IsNormalized()) throw new ArithmeticException($"{msg} is not unit length."); - } - - internal static void ValidateTangent(this Vector4 tangent, string msg) - { - if (tangent.W != 1 && tangent.W != -1) throw new ArithmeticException(msg); - - new Vector3(tangent.X, tangent.Y, tangent.Z).ValidateNormal(msg); - } - - internal static Vector3 SanitizeNormal(this Vector3 normal) - { - if (normal == Vector3.Zero) return Vector3.UnitX; - return normal.IsNormalized() ? normal : Vector3.Normalize(normal); - } - - internal static bool IsValidTangent(this Vector4 tangent) - { - if (tangent.W != 1 && tangent.W != -1) return false; - - return new Vector3(tangent.X, tangent.Y, tangent.Z).IsNormalized(); - } - - internal static Vector4 SanitizeTangent(this Vector4 tangent) - { - var n = new Vector3(tangent.X, tangent.Y, tangent.Z).SanitizeNormal(); - var s = float.IsNaN(tangent.W) ? 1 : tangent.W; - return new Vector4(n, s > 0 ? 1 : -1); - } -} diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index 6818ad64..0c19bc0a 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -1,6 +1,6 @@ using Dalamud.Plugin.Services; using Lumina.Data.Parsing; -using OtterGui.Extensions; +using OtterGui; using OtterGui.Services; using OtterGui.Tasks; using Penumbra.Collections.Manager; @@ -63,7 +63,7 @@ public sealed class ModelManager(IFramework framework, MetaFileManager metaFileM if (info.FileType is not FileType.Model) return []; - var baseSkeleton = GamePaths.Sklb.Customization(info.GenderRace, "base", 1); + var baseSkeleton = GamePaths.Skeleton.Sklb.Path(info.GenderRace, "base", 1); return info.ObjectType switch { @@ -79,9 +79,9 @@ public sealed class ModelManager(IFramework framework, MetaFileManager metaFileM ObjectType.Character when info.BodySlot is BodySlot.Face or BodySlot.Ear => [baseSkeleton, ..ResolveEstSkeleton(EstType.Face, info, estManipulations)], ObjectType.Character => throw new Exception($"Currently unsupported human model type \"{info.BodySlot}\"."), - ObjectType.DemiHuman => [GamePaths.Sklb.DemiHuman(info.PrimaryId)], - ObjectType.Monster => [GamePaths.Sklb.Monster(info.PrimaryId)], - ObjectType.Weapon => [GamePaths.Sklb.Weapon(info.PrimaryId)], + ObjectType.DemiHuman => [GamePaths.DemiHuman.Sklb.Path(info.PrimaryId)], + ObjectType.Monster => [GamePaths.Monster.Sklb.Path(info.PrimaryId)], + ObjectType.Weapon => [GamePaths.Weapon.Sklb.Path(info.PrimaryId)], _ => [], }; } @@ -105,7 +105,7 @@ public sealed class ModelManager(IFramework framework, MetaFileManager metaFileM if (targetId == EstEntry.Zero) return []; - return [GamePaths.Sklb.Customization(info.GenderRace, type.ToName(), targetId.AsId)]; + return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, type.ToName(), targetId.AsId)]; } /// Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. @@ -137,7 +137,7 @@ public sealed class ModelManager(IFramework framework, MetaFileManager metaFileM var resolvedPath = info.ObjectType switch { - ObjectType.Character => GamePaths.Mtrl.Customization( + ObjectType.Character => GamePaths.Character.Mtrl.Path( info.GenderRace, info.BodySlot, info.PrimaryId, relativePath, out _, out _, info.Variant), _ => absolutePath, }; diff --git a/Penumbra/Import/Models/SkeletonConverter.cs b/Penumbra/Import/Models/SkeletonConverter.cs index e180662d..25e74332 100644 --- a/Penumbra/Import/Models/SkeletonConverter.cs +++ b/Penumbra/Import/Models/SkeletonConverter.cs @@ -1,5 +1,5 @@ using System.Xml; -using OtterGui.Extensions; +using OtterGui; using Penumbra.Import.Models.Export; namespace Penumbra.Import.Models; diff --git a/Penumbra/Import/Structs/TexToolsStructs.cs b/Penumbra/Import/Structs/TexToolsStructs.cs index f5b5ef4a..bf3bccd8 100644 --- a/Penumbra/Import/Structs/TexToolsStructs.cs +++ b/Penumbra/Import/Structs/TexToolsStructs.cs @@ -26,7 +26,7 @@ public class SimpleMod public class ModPackPage { public int PageIndex = 0; - public ModGroup[] ModGroups = []; + public ModGroup[] ModGroups = Array.Empty(); } [Serializable] @@ -34,7 +34,7 @@ public class ModGroup { public string GroupName = string.Empty; public GroupType SelectionType = GroupType.Single; - public OptionList[] OptionList = []; + public OptionList[] OptionList = Array.Empty(); public string Description = string.Empty; } @@ -44,7 +44,7 @@ public class OptionList public string Name = string.Empty; public string Description = string.Empty; public string ImagePath = string.Empty; - public SimpleMod[] ModsJsons = []; + public SimpleMod[] ModsJsons = Array.Empty(); public string GroupName = string.Empty; public GroupType SelectionType = GroupType.Single; public bool IsChecked = false; @@ -59,8 +59,8 @@ public class ExtendedModPack public string Version = string.Empty; public string Description = DefaultTexToolsData.Description; public string Url = string.Empty; - public ModPackPage[] ModPackPages = []; - public SimpleMod[] SimpleModsList = []; + public ModPackPage[] ModPackPages = Array.Empty(); + public SimpleMod[] SimpleModsList = Array.Empty(); } [Serializable] @@ -72,5 +72,5 @@ public class SimpleModPack public string Version = string.Empty; public string Description = DefaultTexToolsData.Description; public string Url = string.Empty; - public SimpleMod[] SimpleModsList = []; + public SimpleMod[] SimpleModsList = Array.Empty(); } diff --git a/Penumbra/Import/TexToolsImport.cs b/Penumbra/Import/TexToolsImport.cs index 8e4fea41..fed06573 100644 --- a/Penumbra/Import/TexToolsImport.cs +++ b/Penumbra/Import/TexToolsImport.cs @@ -119,7 +119,7 @@ public partial class TexToolsImporter : IDisposable // Puts out warnings if extension does not correspond to data. private DirectoryInfo VerifyVersionAndImport(FileInfo modPackFile) { - if (modPackFile.Extension.ToLowerInvariant() is ".pmp" or ".pcp" or ".zip" or ".7z" or ".rar") + if (modPackFile.Extension.ToLowerInvariant() is ".pmp" or ".zip" or ".7z" or ".rar") return HandleRegularArchive(modPackFile); using var zfs = modPackFile.OpenRead(); diff --git a/Penumbra/Import/TexToolsImporter.Archives.cs b/Penumbra/Import/TexToolsImporter.Archives.cs index a80730bf..febbe179 100644 --- a/Penumbra/Import/TexToolsImporter.Archives.cs +++ b/Penumbra/Import/TexToolsImporter.Archives.cs @@ -4,7 +4,6 @@ using Newtonsoft.Json.Linq; using OtterGui.Filesystem; using Penumbra.Import.Structs; using Penumbra.Mods; -using Penumbra.Services; using SharpCompress.Archives; using SharpCompress.Archives.Rar; using SharpCompress.Archives.SevenZip; @@ -97,36 +96,17 @@ public partial class TexToolsImporter _token.ThrowIfCancellationRequested(); var oldName = _currentModDirectory.FullName; - - // Try renaming the folder three times because sometimes we get AccessDenied here for some unknown reason. - const int numTries = 3; - for (var i = 1;; ++i) + // Use either the top-level directory as the mods base name, or the (fixed for path) name in the json. + if (leadDir) { - // Use either the top-level directory as the mods base name, or the (fixed for path) name in the json. - try - { - if (leadDir) - { - _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, baseName, _config.ReplaceNonAsciiOnImport, false); - Directory.Move(Path.Combine(oldName, baseName), _currentModDirectory.FullName); - Directory.Delete(oldName); - } - else - { - _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, name, _config.ReplaceNonAsciiOnImport, false); - Directory.Move(oldName, _currentModDirectory.FullName); - } - } - catch (IOException io) - { - if (i == numTries) - throw; - - Penumbra.Log.Warning($"Error when renaming the extracted mod, try {i}/{numTries}: {io.Message}."); - continue; - } - - break; + _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, baseName, _config.ReplaceNonAsciiOnImport, false); + Directory.Move(Path.Combine(oldName, baseName), _currentModDirectory.FullName); + Directory.Delete(oldName); + } + else + { + _currentModDirectory = ModCreator.CreateModFolder(_baseDirectory, name, _config.ReplaceNonAsciiOnImport, false); + Directory.Move(oldName, _currentModDirectory.FullName); } _currentModDirectory.Refresh(); @@ -147,9 +127,6 @@ public partial class TexToolsImporter case ".mtrl": _migrationManager.MigrateMtrlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions); break; - case ".tex": - _migrationManager.FixMipMaps(reader, _currentModDirectory!.FullName, _extractionOptions); - break; default: reader.WriteEntryToDirectory(_currentModDirectory!.FullName, _extractionOptions); break; diff --git a/Penumbra/Import/TexToolsImporter.Gui.cs b/Penumbra/Import/TexToolsImporter.Gui.cs index 5cb99d72..f145f560 100644 --- a/Penumbra/Import/TexToolsImporter.Gui.cs +++ b/Penumbra/Import/TexToolsImporter.Gui.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Import.Structs; diff --git a/Penumbra/Import/TexToolsImporter.ModPack.cs b/Penumbra/Import/TexToolsImporter.ModPack.cs index fd9e50c0..7bbb762e 100644 --- a/Penumbra/Import/TexToolsImporter.ModPack.cs +++ b/Penumbra/Import/TexToolsImporter.ModPack.cs @@ -1,5 +1,5 @@ using Newtonsoft.Json; -using OtterGui.Extensions; +using OtterGui; using Penumbra.Api.Enums; using Penumbra.Import.Structs; using Penumbra.Mods; @@ -259,7 +259,6 @@ public partial class TexToolsImporter { ".mdl" => _migrationManager.MigrateTtmpModel(extractedFile.FullName, data.Data), ".mtrl" => _migrationManager.MigrateTtmpMaterial(extractedFile.FullName, data.Data), - ".tex" => _migrationManager.FixTtmpMipMaps(extractedFile.FullName, data.Data), _ => data.Data, }; diff --git a/Penumbra/Import/TexToolsMeta.Deserialization.cs b/Penumbra/Import/TexToolsMeta.Deserialization.cs index 7861a95b..1f970dfe 100644 --- a/Penumbra/Import/TexToolsMeta.Deserialization.cs +++ b/Penumbra/Import/TexToolsMeta.Deserialization.cs @@ -2,6 +2,7 @@ using Lumina.Extensions; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Import.Structs; +using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Import; @@ -18,7 +19,9 @@ public partial class TexToolsMeta var identifier = new EqpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot); var value = Eqp.FromSlotAndBytes(metaFileInfo.EquipSlot, data) & mask; - MetaManipulations.TryAdd(identifier, value); + var def = ExpandedEqpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId) & mask; + if (_keepDefault || def != value) + MetaManipulations.TryAdd(identifier, value); } // Deserialize and check Eqdp Entries and add them to the list if they are non-default. @@ -38,9 +41,11 @@ public partial class TexToolsMeta continue; var identifier = new EqdpIdentifier(metaFileInfo.PrimaryId, metaFileInfo.EquipSlot, gr); - var mask = Eqdp.Mask(metaFileInfo.EquipSlot); - var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2) & mask; - MetaManipulations.TryAdd(identifier, value); + var mask = Eqdp.Mask(metaFileInfo.EquipSlot); + var value = Eqdp.FromSlotAndBits(metaFileInfo.EquipSlot, (byteValue & 1) == 1, (byteValue & 2) == 2) & mask; + var def = ExpandedEqdpFile.GetDefault(_metaFileManager, gr, metaFileInfo.EquipSlot.IsAccessory(), metaFileInfo.PrimaryId) & mask; + if (_keepDefault || def != value) + MetaManipulations.TryAdd(identifier, value); } } @@ -50,9 +55,10 @@ public partial class TexToolsMeta if (data == null) return; - var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5)); - var identifier = new GmpIdentifier(metaFileInfo.PrimaryId); - MetaManipulations.TryAdd(identifier, value); + var value = GmpEntry.FromTexToolsMeta(data.AsSpan(0, 5)); + var def = ExpandedGmpFile.GetDefault(_metaFileManager, metaFileInfo.PrimaryId); + if (_keepDefault || value != def) + MetaManipulations.TryAdd(new GmpIdentifier(metaFileInfo.PrimaryId), value); } // Deserialize and check Est Entries and add them to the list if they are non-default. @@ -80,7 +86,9 @@ public partial class TexToolsMeta continue; var identifier = new EstIdentifier(id, type, gr); - MetaManipulations.TryAdd(identifier, value); + var def = EstFile.GetDefault(_metaFileManager, type, gr, id); + if (_keepDefault || def != value) + MetaManipulations.TryAdd(identifier, value); } } @@ -100,10 +108,15 @@ public partial class TexToolsMeta { var identifier = new ImcIdentifier(metaFileInfo.PrimaryId, 0, metaFileInfo.PrimaryType, metaFileInfo.SecondaryId, metaFileInfo.EquipSlot, metaFileInfo.SecondaryType); + var file = new ImcFile(_metaFileManager, identifier); + var partIdx = ImcFile.PartIndex(identifier.EquipSlot); // Gets turned to unknown for things without equip, and unknown turns to 0. foreach (var value in values) { identifier = identifier with { Variant = (Variant)i }; - MetaManipulations.TryAdd(identifier, value); + var def = file.GetEntry(partIdx, (Variant)i); + if (_keepDefault || def != value && identifier.Validate()) + MetaManipulations.TryAdd(identifier, value); + ++i; } } diff --git a/Penumbra/Import/TexToolsMeta.Rgsp.cs b/Penumbra/Import/TexToolsMeta.Rgsp.cs index 77c70e6c..7b0bb5a8 100644 --- a/Penumbra/Import/TexToolsMeta.Rgsp.cs +++ b/Penumbra/Import/TexToolsMeta.Rgsp.cs @@ -1,5 +1,6 @@ using Penumbra.GameData.Enums; using Penumbra.Meta; +using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; namespace Penumbra.Import; @@ -7,7 +8,7 @@ namespace Penumbra.Import; public partial class TexToolsMeta { // Parse a single rgsp file. - public static TexToolsMeta FromRgspFile(MetaFileManager manager, string filePath, byte[] data) + public static TexToolsMeta FromRgspFile(MetaFileManager manager, string filePath, byte[] data, bool keepDefault) { if (data.Length != 45 && data.Length != 42) { @@ -69,7 +70,9 @@ public partial class TexToolsMeta void Add(RspAttribute attribute, float value) { var identifier = new RspIdentifier(subRace, attribute); - ret.MetaManipulations.TryAdd(identifier, new RspEntry(value)); + var def = CmpFile.GetDefault(manager, subRace, attribute); + if (keepDefault || value != def.Value) + ret.MetaManipulations.TryAdd(identifier, new RspEntry(value)); } } } diff --git a/Penumbra/Import/TexToolsMeta.cs b/Penumbra/Import/TexToolsMeta.cs index f98eddbe..c4a8e81f 100644 --- a/Penumbra/Import/TexToolsMeta.cs +++ b/Penumbra/Import/TexToolsMeta.cs @@ -23,11 +23,15 @@ public partial class TexToolsMeta // The info class determines the files or table locations the changes need to apply to from the filename. public readonly uint Version; public readonly string FilePath; - public readonly MetaDictionary MetaManipulations = new(); + public readonly MetaDictionary MetaManipulations = new(); + private readonly bool _keepDefault; + private readonly MetaFileManager _metaFileManager; - public TexToolsMeta(GamePathParser parser, byte[] data) + public TexToolsMeta(MetaFileManager metaFileManager, GamePathParser parser, byte[] data, bool keepDefault) { + _metaFileManager = metaFileManager; + _keepDefault = keepDefault; try { using var reader = new BinaryReader(new MemoryStream(data)); @@ -75,6 +79,7 @@ public partial class TexToolsMeta private TexToolsMeta(MetaFileManager metaFileManager, string filePath, uint version) { + _metaFileManager = metaFileManager; FilePath = filePath; Version = version; } diff --git a/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs b/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs index 7a7e5888..2d131d71 100644 --- a/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs +++ b/Penumbra/Import/Textures/CombinedTexture.Manipulation.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui; using SixLabors.ImageSharp.PixelFormats; diff --git a/Penumbra/Import/Textures/CombinedTexture.cs b/Penumbra/Import/Textures/CombinedTexture.cs index f5f921be..c1a22088 100644 --- a/Penumbra/Import/Textures/CombinedTexture.cs +++ b/Penumbra/Import/Textures/CombinedTexture.cs @@ -6,10 +6,7 @@ public partial class CombinedTexture : IDisposable { AsIs, Bitmap, - BC1, BC3, - BC4, - BC5, BC7, } diff --git a/Penumbra/Import/Textures/TexFileParser.cs b/Penumbra/Import/Textures/TexFileParser.cs index 04bbf5d8..220095c1 100644 --- a/Penumbra/Import/Textures/TexFileParser.cs +++ b/Penumbra/Import/Textures/TexFileParser.cs @@ -61,76 +61,6 @@ public static class TexFileParser return 13; } - public static unsafe void FixMipOffsets(long size, ref TexFile.TexHeader header, out long newSize) - { - var width = (uint)header.Width; - var height = (uint)header.Height; - var format = header.Format.ToDXGI(); - var bits = format.BitsPerPixel(); - var totalSize = 80u; - size -= totalSize; - var minSize = format.IsCompressed() ? 4u : 1u; - for (var i = 0; i < 13; ++i) - { - var requiredSize = (uint)((long)width * height * bits / 8); - if (requiredSize > size) - { - newSize = totalSize; - if (header.MipCount != i) - { - Penumbra.Log.Debug( - $"-- Mip Map Count in TEX header was {header.MipCount}, but file only contains data for {i} Mip Maps, fixed."); - FixLodOffsets(ref header, i); - } - - return; - } - - if (header.OffsetToSurface[i] != totalSize) - { - Penumbra.Log.Debug( - $"-- Mip Map Offset {i + 1} in TEX header was {header.OffsetToSurface[i]} but should be {totalSize}, fixed."); - header.OffsetToSurface[i] = totalSize; - } - - if (width == minSize && height == minSize) - { - ++i; - newSize = totalSize + requiredSize; - if (header.MipCount != i) - { - Penumbra.Log.Debug($"-- Reduced number of Mip Maps from {header.MipCount} to {i} due to minimum size constraints."); - FixLodOffsets(ref header, i); - } - - return; - } - - totalSize += requiredSize; - size -= requiredSize; - width = Math.Max(width / 2, minSize); - height = Math.Max(height / 2, minSize); - } - - newSize = totalSize; - if (header.MipCount != 13) - { - Penumbra.Log.Debug($"-- Mip Map Count in TEX header was {header.MipCount}, but maximum is 13, fixed."); - FixLodOffsets(ref header, 13); - } - - void FixLodOffsets(ref TexFile.TexHeader header, int index) - { - header.MipCount = index; - if (header.LodOffset[2] >= header.MipCount) - header.LodOffset[2] = (byte)(header.MipCount - 1); - if (header.LodOffset[1] >= header.MipCount) - header.LodOffset[1] = header.MipCount > 2 ? (byte)(header.MipCount - 2) : (byte)(header.MipCount - 1); - for (++index; index < 13; ++index) - header.OffsetToSurface[index] = 0; - } - } - private static unsafe void CopyData(ScratchImage image, BinaryReader r) { fixed (byte* ptr = image.Pixels) diff --git a/Penumbra/Import/Textures/TextureDrawer.cs b/Penumbra/Import/Textures/TextureDrawer.cs index 14203dff..b0a65ac0 100644 --- a/Penumbra/Import/Textures/TextureDrawer.cs +++ b/Penumbra/Import/Textures/TextureDrawer.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using ImGuiNET; using Lumina.Data.Files; using OtterGui; using OtterGui.Raii; @@ -20,7 +20,7 @@ public static class TextureDrawer { size = texture.TextureWrap.Size.Contain(size); - ImGui.Image(texture.TextureWrap.Handle, size); + ImGui.Image(texture.TextureWrap.ImGuiHandle, size); DrawData(texture); } else if (texture.LoadError != null) diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 073fef2f..7118f8af 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -1,4 +1,3 @@ -using Dalamud.Interface; using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; @@ -7,17 +6,15 @@ using OtterGui.Log; using OtterGui.Services; using OtterGui.Tasks; using OtterTex; -using SharpDX.Direct3D11; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; -using DxgiDevice = SharpDX.DXGI.Device; using Image = SixLabors.ImageSharp.Image; namespace Penumbra.Import.Textures; -public sealed class TextureManager(IDataManager gameData, Logger logger, ITextureProvider textureProvider, IUiBuilder uiBuilder) +public sealed class TextureManager(IDataManager gameData, Logger logger, ITextureProvider textureProvider) : SingleTaskQueue, IDisposable, IService { private readonly Logger _logger = logger; @@ -204,11 +201,8 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur 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.BC1 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC1UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC3 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC3UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC4 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC4UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC5 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC5UNorm, cancel, rgba, width, height), - CombinedTexture.TextureSaveType.BC7 => _textures.ConvertToCompressedDds(image, _mipMaps, DXGIFormat.BC7UNorm, 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), _ => throw new Exception("Wrong save type."), }; @@ -326,7 +320,7 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Convert an existing image to a block compressed .dds. Does not create a deep copy of an existing dds of the correct format and just returns the existing one. - public BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel, byte[]? rgba = null, + public static BaseImage ConvertToCompressedDds(BaseImage input, bool mipMaps, bool bc7, CancellationToken cancel, byte[]? rgba = null, int width = 0, int height = 0) { switch (input.Type.ReduceToBehaviour()) @@ -337,12 +331,12 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur cancel.ThrowIfCancellationRequested(); var dds = ConvertToDds(rgba, width, height).AsDds!; cancel.ThrowIfCancellationRequested(); - return CreateCompressed(dds, mipMaps, format, cancel); + return CreateCompressed(dds, mipMaps, bc7, cancel); } case TextureType.Dds: { var scratch = input.AsDds!; - return CreateCompressed(scratch, mipMaps, format, cancel); + return CreateCompressed(scratch, mipMaps, bc7, cancel); } default: return new BaseImage(); } @@ -390,8 +384,9 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur } /// Create a BC3 or BC7 block-compressed .dds from the input (optionally with mipmaps). Returns input (+ mipmaps) if it is already the correct format. - public ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, DXGIFormat format, CancellationToken cancel) + public static ScratchImage CreateCompressed(ScratchImage input, bool mipMaps, bool bc7, CancellationToken cancel) { + var format = bc7 ? DXGIFormat.BC7UNorm : DXGIFormat.BC3UNorm; if (input.Meta.Format == format) return input; @@ -403,16 +398,6 @@ public sealed class TextureManager(IDataManager gameData, Logger logger, ITextur input = AddMipMaps(input, mipMaps); cancel.ThrowIfCancellationRequested(); - // See https://github.com/microsoft/DirectXTex/wiki/Compress#parameters for the format condition. - if (format is DXGIFormat.BC6HUF16 or DXGIFormat.BC6HSF16 or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB) - { - var device = new Device(uiBuilder.DeviceHandle); - var dxgiDevice = device.QueryInterface(); - - using var deviceClone = new Device(dxgiDevice.Adapter, device.CreationFlags, device.FeatureLevel); - return input.Compress(deviceClone.NativePointer, format, CompressFlags.Parallel); - } - return input.Compress(format, CompressFlags.BC7Quick | CompressFlags.Parallel); } diff --git a/Penumbra/Interop/CloudApi.cs b/Penumbra/Interop/CloudApi.cs deleted file mode 100644 index 603d4c9f..00000000 --- a/Penumbra/Interop/CloudApi.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Penumbra.Interop; - -public static unsafe partial class CloudApi -{ - private const int CfSyncRootInfoBasic = 0; - - /// Determines whether a file or directory is cloud-synced using OneDrive or other providers that use the Cloud API. - /// Can be expensive. Callers should cache the result when relevant. - public static bool IsCloudSynced(string path) - { - var buffer = stackalloc long[1]; - int hr; - uint length; - try - { - hr = CfGetSyncRootInfoByPath(path, CfSyncRootInfoBasic, buffer, sizeof(long), out length); - } - catch (DllNotFoundException) - { - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} threw DllNotFoundException"); - return false; - } - catch (EntryPointNotFoundException) - { - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} threw EntryPointNotFoundException"); - return false; - } - - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} returned HRESULT 0x{hr:X8}"); - if (hr < 0) - return false; - - if (length != sizeof(long)) - { - Penumbra.Log.Debug($"Expected {nameof(CfGetSyncRootInfoByPath)} to return {sizeof(long)} bytes, got {length} bytes"); - return false; - } - - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} returned {{ SyncRootFileId = 0x{*buffer:X16} }}"); - - return true; - } - - [LibraryImport("cldapi.dll", StringMarshalling = StringMarshalling.Utf16)] - private static partial int CfGetSyncRootInfoByPath(string filePath, int infoClass, void* infoBuffer, uint infoBufferLength, - out uint returnedLength); -} diff --git a/Penumbra/Interop/GameState.cs b/Penumbra/Interop/GameState.cs index b5171244..7e7abcd8 100644 --- a/Penumbra/Interop/GameState.cs +++ b/Penumbra/Interop/GameState.cs @@ -11,8 +11,7 @@ public class GameState : IService { #region Last Game Object - private readonly ThreadLocal> _lastGameObject = new(() => new Queue()); - public readonly ThreadLocal CharacterAssociated = new(() => false); + private readonly ThreadLocal> _lastGameObject = new(() => new Queue()); public nint LastGameObject => _lastGameObject.IsValueCreated && _lastGameObject.Value!.Count > 0 ? _lastGameObject.Value.Peek() : nint.Zero; @@ -50,15 +49,15 @@ public class GameState : IService public void RestoreAnimationData(ResolveData old) => _animationLoadData.Value = old; - public readonly ThreadLocal InLoadActionTmb = new(() => false); - public readonly ThreadLocal SkipTmbCache = new(() => false); - #endregion #region Sound Data private readonly ThreadLocal _characterSoundData = new(() => ResolveData.Invalid, true); + public ResolveData SoundData + => _animationLoadData.Value; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public ResolveData SetSoundData(ResolveData data) { diff --git a/Penumbra/Interop/Hooks/Animation/GetCachedScheduleResource.cs b/Penumbra/Interop/Hooks/Animation/GetCachedScheduleResource.cs deleted file mode 100644 index 6ce1f899..00000000 --- a/Penumbra/Interop/Hooks/Animation/GetCachedScheduleResource.cs +++ /dev/null @@ -1,53 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Resource; -using JetBrains.Annotations; -using OtterGui.Services; -using Penumbra.GameData; -using Penumbra.Interop.Structs; -using Penumbra.String; - -namespace Penumbra.Interop.Hooks.Animation; - -/// Load a cached TMB resource from SchedulerResourceManagement. -public sealed unsafe class GetCachedScheduleResource : FastHook -{ - private readonly GameState _state; - - public GetCachedScheduleResource(HookManager hooks, GameState state) - { - _state = state; - Task = hooks.CreateHook("Get Cached Schedule Resource", Sigs.GetCachedScheduleResource, Detour, - !HookOverrides.Instance.Animation.GetCachedScheduleResource); - } - - public delegate SchedulerResource* Delegate(SchedulerResourceManagement* a, ScheduleResourceLoadData* b, byte useMap); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private SchedulerResource* Detour(SchedulerResourceManagement* a, ScheduleResourceLoadData* b, byte c) - { - if (_state.SkipTmbCache.Value) - { - Penumbra.Log.Verbose( - $"[GetCachedScheduleResource] Called with 0x{(ulong)a:X}, {b->Id}, {new CiByteString(b->Path, MetaDataComputation.None)}, {c} from LoadActionTmb with forced skipping of cache, returning NULL."); - return null; - } - - var ret = Task.Result.Original(a, b, c); - Penumbra.Log.Excessive( - $"[GetCachedScheduleResource] Called with 0x{(ulong)a:X}, {b->Id}, {new CiByteString(b->Path, MetaDataComputation.None)}, {c}, returning 0x{(ulong)ret:X} ({(ret != null && Resource(ret) != null ? Resource(ret)->FileName().ToString() : "No Path")})."); - return ret; - } - - public struct ScheduleResourceLoadData - { - [UsedImplicitly] - public byte* Path; - - [UsedImplicitly] - public uint Id; - } - - - // #TODO: remove when fixed in CS. - public static ResourceHandle* Resource(SchedulerResource* r) - => ((ResourceHandle**)r)[3]; -} diff --git a/Penumbra/Interop/Hooks/Animation/LoadActionTmb.cs b/Penumbra/Interop/Hooks/Animation/LoadActionTmb.cs deleted file mode 100644 index 457465d2..00000000 --- a/Penumbra/Interop/Hooks/Animation/LoadActionTmb.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Resource; -using OtterGui.Services; -using Penumbra.GameData; -using Penumbra.Interop.Services; -using Penumbra.String; - -namespace Penumbra.Interop.Hooks.Animation; - -/// Load a Action TMB. -public sealed unsafe class LoadActionTmb : FastHook -{ - private readonly GameState _state; - private readonly SchedulerResourceManagementService _scheduler; - - public LoadActionTmb(HookManager hooks, GameState state, SchedulerResourceManagementService scheduler) - { - _state = state; - _scheduler = scheduler; - Task = hooks.CreateHook("Load Action TMB", Sigs.LoadActionTmb, Detour, !HookOverrides.Instance.Animation.LoadActionTmb); - } - - public delegate SchedulerResource* Delegate(SchedulerResourceManagement* scheduler, - GetCachedScheduleResource.ScheduleResourceLoadData* loadData, nint b, byte c, byte d, byte e); - - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private SchedulerResource* Detour(SchedulerResourceManagement* scheduler, GetCachedScheduleResource.ScheduleResourceLoadData* loadData, - nint b, byte c, byte d, byte e) - { - _state.InLoadActionTmb.Value = true; - SchedulerResource* ret; - if (ShouldSkipCache(loadData)) - { - _state.SkipTmbCache.Value = true; - ret = Task.Result.Original(scheduler, loadData, b, c, d, 1); - Penumbra.Log.Verbose( - $"[LoadActionTMB] Called with 0x{(ulong)scheduler:X}, {loadData->Id}, {new CiByteString(loadData->Path, MetaDataComputation.None)}, 0x{b:X}, {c}, {d}, {e}, forced no-cache use, returned 0x{(ulong)ret:X} ({(ret != null && GetCachedScheduleResource.Resource(ret) != null ? GetCachedScheduleResource.Resource(ret)->FileName().ToString() : "No Path")})."); - _state.SkipTmbCache.Value = false; - } - else - { - ret = Task.Result.Original(scheduler, loadData, b, c, d, e); - Penumbra.Log.Excessive( - $"[LoadActionTMB] Called with 0x{(ulong)scheduler:X}, {loadData->Id}, {new CiByteString(loadData->Path)}, 0x{b:X}, {c}, {d}, {e}, returned 0x{(ulong)ret:X} ({(ret != null && GetCachedScheduleResource.Resource(ret) != null ? GetCachedScheduleResource.Resource(ret)->FileName().ToString() : "No Path")})."); - } - - _state.InLoadActionTmb.Value = false; - - return ret; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private bool ShouldSkipCache(GetCachedScheduleResource.ScheduleResourceLoadData* loadData) - => _scheduler.Contains(loadData->Id); -} diff --git a/Penumbra/Interop/Hooks/HookSettings.cs b/Penumbra/Interop/Hooks/HookSettings.cs index bcff25d2..63d93c9d 100644 --- a/Penumbra/Interop/Hooks/HookSettings.cs +++ b/Penumbra/Interop/Hooks/HookSettings.cs @@ -43,8 +43,6 @@ public class HookOverrides public bool SomeMountAnimation; public bool SomePapLoad; public bool SomeParasolAnimation; - public bool GetCachedScheduleResource; - public bool LoadActionTmb; } public struct MetaHooks @@ -76,8 +74,6 @@ public class HookOverrides public bool CreateCharacterBase; public bool EnableDraw; public bool WeaponReload; - public bool SetupPlayerNpc; - public bool ConstructCutsceneCharacter; } public struct PostProcessingHooks @@ -88,7 +84,6 @@ public class HookOverrides public bool ModelRendererOnRenderMaterial; public bool ModelRendererUnkFunc; public bool PrepareColorTable; - public bool RenderTargetManagerInitialize; } public struct ResourceLoadingHooks @@ -100,7 +95,6 @@ public class HookOverrides public bool DecRef; public bool GetResourceSync; public bool GetResourceAsync; - public bool UpdateResourceState; public bool CheckFileState; public bool TexResourceHandleOnLoad; public bool LoadMdlFileExtern; diff --git a/Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs b/Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs index 2a3d7468..07e34a66 100644 --- a/Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs +++ b/Penumbra/Interop/Hooks/Meta/AtchCallerHook1.cs @@ -25,12 +25,12 @@ public unsafe class AtchCallerHook1 : FastHook, IDispo private void Detour(DrawObjectData* data, uint slot, nint unk, Model playerModel) { - var collection = playerModel.Valid ? _collectionResolver.IdentifyCollection(playerModel.AsDrawObject, true) : _collectionResolver.DefaultCollection; + var collection = _collectionResolver.IdentifyCollection(playerModel.AsDrawObject, true); _metaState.AtchCollection.Push(collection); Task.Result.Original(data, slot, unk, playerModel); _metaState.AtchCollection.Pop(); - Penumbra.Log.Excessive( - $"[AtchCaller1] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, identified to {collection.ModCollection.Identity.AnonymizedName}."); + Penumbra.Log.Information( + $"[AtchCaller1] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, identified to {collection.ModCollection.AnonymizedName}."); } public void Dispose() diff --git a/Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs b/Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs index af38ce50..aa2d3f31 100644 --- a/Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs +++ b/Penumbra/Interop/Hooks/Meta/AtchCallerHook2.cs @@ -30,7 +30,7 @@ public unsafe class AtchCallerHook2 : FastHook, IDispo Task.Result.Original(data, slot, unk, playerModel, unk2); _metaState.AtchCollection.Pop(); Penumbra.Log.Excessive( - $"[AtchCaller2] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, {unk2}, identified to {collection.ModCollection.Identity.AnonymizedName}."); + $"[AtchCaller2] Invoked on 0x{(ulong)data:X} with {slot}, {unk:X}, 0x{playerModel.Address:X}, {unk2}, identified to {collection.ModCollection.AnonymizedName}."); } public void Dispose() diff --git a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs index 523ae610..368845b4 100644 --- a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs +++ b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs @@ -16,7 +16,7 @@ public sealed unsafe class ChangeCustomize : FastHook { _collectionResolver = collectionResolver; _metaState = metaState; - Task = hooks.CreateHook("Change Customize", Sigs.UpdateDrawData, Detour, !HookOverrides.Instance.Meta.ChangeCustomize); + Task = hooks.CreateHook("Change Customize", Sigs.ChangeCustomize, Detour, !HookOverrides.Instance.Meta.ChangeCustomize); } public delegate bool Delegate(Human* human, CustomizeArray* data, byte skipEquipment); diff --git a/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs b/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs index 2d8e60b2..7636718e 100644 --- a/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs +++ b/Penumbra/Interop/Hooks/Objects/CharacterBaseDestructor.cs @@ -2,6 +2,7 @@ using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Classes; using OtterGui.Services; +using Penumbra.UI.AdvancedWindow; namespace Penumbra.Interop.Hooks.Objects; @@ -12,7 +13,7 @@ public sealed unsafe class CharacterBaseDestructor : EventWrapperPtr DrawObjectState = 0, - /// + /// MtrlTab = -1000, } @@ -41,7 +42,7 @@ public sealed unsafe class CharacterBaseDestructor : EventWrapperPtr IdentifiedCollectionCache = 0, - - /// - DrawObjectState = 0, } public CharacterDestructor(HookManager hooks) @@ -45,7 +42,7 @@ public sealed unsafe class CharacterDestructor : EventWrapperPtr, IHookService -{ - private readonly GameState _gameState; - private readonly ObjectManager _objects; - - public enum Priority - { - /// - CutsceneService = 0, - } - - public ConstructCutsceneCharacter(GameState gameState, HookManager hooks, ObjectManager objects) - : base("ConstructCutsceneCharacter") - { - _gameState = gameState; - _objects = objects; - _task = hooks.CreateHook(Name, Sigs.ConstructCutsceneCharacter, Detour, !HookOverrides.Instance.Objects.ConstructCutsceneCharacter); - } - - private readonly Task> _task; - - public delegate int Delegate(SetupPlayerNpc.SchedulerStruct* scheduler); - - public int Detour(SetupPlayerNpc.SchedulerStruct* scheduler) - { - // This is the function that actually creates the new game object - // and fills it into the object table at a free index etc. - var ret = _task.Result.Original(scheduler); - // Check for the copy state from SetupPlayerNpc. - if (_gameState.CharacterAssociated.Value) - { - // If the newly created character exists, invoke the event. - var character = _objects[ret + (int)ScreenActor.CutsceneStart].AsCharacter; - if (character != null) - { - Invoke(character); - Penumbra.Log.Verbose( - $"[{Name}] Created indirect copy of player character at 0x{(nint)character}, index {character->ObjectIndex}."); - } - _gameState.CharacterAssociated.Value = false; - } - - return ret; - } - - public IntPtr Address - => _task.Result.Address; - - public void Enable() - => _task.Result.Enable(); - - public void Disable() - => _task.Result.Disable(); - - public Task Awaiter - => _task; - - public bool Finished - => _task.IsCompletedSuccessfully; -} diff --git a/Penumbra/Interop/Hooks/Objects/EnableDraw.cs b/Penumbra/Interop/Hooks/Objects/EnableDraw.cs index 979cb87c..68bb28af 100644 --- a/Penumbra/Interop/Hooks/Objects/EnableDraw.cs +++ b/Penumbra/Interop/Hooks/Objects/EnableDraw.cs @@ -26,7 +26,7 @@ public sealed unsafe class EnableDraw : IHookService private void Detour(GameObject* gameObject) { _state.QueueGameObject(gameObject); - Penumbra.Log.Excessive($"[Enable Draw] Invoked on 0x{(nint)gameObject:X} at {gameObject->ObjectIndex}."); + Penumbra.Log.Excessive($"[Enable Draw] Invoked on 0x{(nint)gameObject:X}."); _task.Result.Original.Invoke(gameObject); _state.DequeueGameObject(); } diff --git a/Penumbra/Interop/Hooks/Objects/SetupPlayerNpc.cs b/Penumbra/Interop/Hooks/Objects/SetupPlayerNpc.cs deleted file mode 100644 index 8f1226c3..00000000 --- a/Penumbra/Interop/Hooks/Objects/SetupPlayerNpc.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Character; -using OtterGui.Services; -using Penumbra.GameData; - -namespace Penumbra.Interop.Hooks.Objects; - -public sealed unsafe class SetupPlayerNpc : FastHook -{ - private readonly GameState _gameState; - - public SetupPlayerNpc(GameState gameState, HookManager hooks) - { - _gameState = gameState; - Task = hooks.CreateHook("SetupPlayerNPC", Sigs.SetupPlayerNpc, Detour, - !HookOverrides.Instance.Objects.SetupPlayerNpc); - } - - public delegate SchedulerStruct* Delegate(byte* npcType, nint unk, NpcSetupData* setupData); - - public SchedulerStruct* Detour(byte* npcType, nint unk, NpcSetupData* setupData) - { - // This function actually seems to generate all NPC. - - // If an ENPC is being created, check the creation parameters. - // If CopyPlayerCustomize is true, the event NPC gets a timeline that copies its customize and glasses from the local player. - // Keep track of this, so we can associate the actor to be created for this with the player character, see ConstructCutsceneCharacter. - if (setupData->CopyPlayerCustomize && npcType != null && *npcType is 8) - _gameState.CharacterAssociated.Value = true; - - var ret = Task.Result.Original.Invoke(npcType, unk, setupData); - Penumbra.Log.Excessive( - $"[Setup Player NPC] Invoked for type {*npcType} with 0x{unk:X} and Copy Player Customize: {setupData->CopyPlayerCustomize}."); - return ret; - } - - [StructLayout(LayoutKind.Explicit)] - public struct NpcSetupData - { - [FieldOffset(0x0B)] - private byte _copyPlayerCustomize; - - public bool CopyPlayerCustomize - { - get => _copyPlayerCustomize != 0; - set => _copyPlayerCustomize = value ? (byte)1 : (byte)0; - } - } - - [StructLayout(LayoutKind.Explicit)] - public struct SchedulerStruct - { - public static Character* GetCharacter(SchedulerStruct* s) - => ((delegate* unmanaged**)s)[0][19](s); - } -} diff --git a/Penumbra/Interop/Hooks/Objects/WeaponReload.cs b/Penumbra/Interop/Hooks/Objects/WeaponReload.cs index 4231b027..b09103f6 100644 --- a/Penumbra/Interop/Hooks/Objects/WeaponReload.cs +++ b/Penumbra/Interop/Hooks/Objects/WeaponReload.cs @@ -35,14 +35,14 @@ public sealed unsafe class WeaponReload : EventWrapperPtr _task.IsCompletedSuccessfully; - private delegate void Delegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g, byte h); + private delegate void Delegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g); - private void Detour(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g, byte h) + private void Detour(DrawDataContainer* drawData, uint slot, ulong weapon, byte d, byte e, byte f, byte g) { var gameObject = drawData->OwnerObject; - Penumbra.Log.Verbose($"[{Name}] Triggered with drawData: 0x{(nint)drawData:X}, {slot}, {weapon}, {d}, {e}, {f}, {g}, {h}."); + Penumbra.Log.Verbose($"[{Name}] Triggered with drawData: 0x{(nint)drawData:X}, {slot}, {weapon}, {d}, {e}, {f}, {g}."); Invoke(drawData, gameObject, (CharacterWeapon*)(&weapon)); - _task.Result.Original(drawData, slot, weapon, d, e, f, g, h); + _task.Result.Original(drawData, slot, weapon, d, e, f, g); _postEvent.Invoke(drawData, gameObject); } diff --git a/Penumbra/Interop/Hooks/PostProcessing/AttributeHook.cs b/Penumbra/Interop/Hooks/PostProcessing/AttributeHook.cs deleted file mode 100644 index 00e5851f..00000000 --- a/Penumbra/Interop/Hooks/PostProcessing/AttributeHook.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Dalamud.Hooking; -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Classes; -using OtterGui.Services; -using Penumbra.Collections; -using Penumbra.GameData; -using Penumbra.GameData.Interop; -using Penumbra.Interop.PathResolving; -using Penumbra.Meta; - -namespace Penumbra.Interop.Hooks.PostProcessing; - -/// -/// Triggered whenever a model recomputes its attribute masks. -/// -/// Parameter is the game object that recomputed its attributes. -/// Parameter is the draw object on which the recomputation was called. -/// Parameter is the collection associated with the game object. -/// Parameter is the slot that was recomputed. If this is Unknown, it is a general new update call. -/// -public sealed unsafe class AttributeHook : EventWrapper, IHookService -{ - public enum Priority - { - /// - ShapeAttributeManager = 0, - } - - private readonly CollectionResolver _resolver; - private readonly Configuration _config; - - public AttributeHook(HookManager hooks, Configuration config, CollectionResolver resolver) - : base("Update Model Attributes") - { - _config = config; - _resolver = resolver; - _task = hooks.CreateHook(Name, Sigs.UpdateAttributes, Detour, config.EnableCustomShapes); - } - - private readonly Task> _task; - - public nint Address - => _task.Result.Address; - - public void Enable() - => SetState(true); - - public void Disable() - => SetState(false); - - public void SetState(bool enabled) - { - if (_config.EnableCustomShapes == enabled) - return; - - _config.EnableCustomShapes = enabled; - _config.Save(); - if (enabled) - _task.Result.Enable(); - else - _task.Result.Disable(); - } - - - public Task Awaiter - => _task; - - public bool Finished - => _task.IsCompletedSuccessfully; - - private delegate void Delegate(Human* human); - - private void Detour(Human* human) - { - _task.Result.Original(human); - var resolveData = _resolver.IdentifyCollection((DrawObject*)human, true); - var identifiedActor = resolveData.AssociatedGameObject; - var identifiedCollection = resolveData.ModCollection; - Penumbra.Log.Excessive($"[{Name}] Invoked on 0x{(ulong)human:X} (0x{identifiedActor:X})."); - Invoke(identifiedActor, human, identifiedCollection); - } - - protected override void Dispose(bool disposing) - => _task.Result.Dispose(); -} diff --git a/Penumbra/Interop/Hooks/PostProcessing/PreBoneDeformerReplacer.cs b/Penumbra/Interop/Hooks/PostProcessing/PreBoneDeformerReplacer.cs index 51af5813..30e643c7 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/PreBoneDeformerReplacer.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/PreBoneDeformerReplacer.cs @@ -63,7 +63,7 @@ public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredServi if (!_framework.IsInFrameworkUpdateThread) Penumbra.Log.Warning( $"{nameof(PreBoneDeformerReplacer)}.{nameof(SetupHssReplacements)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread"); - + var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject); try { diff --git a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs b/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs deleted file mode 100644 index 653d9c1a..00000000 --- a/Penumbra/Interop/Hooks/PostProcessing/RenderTargetHdrEnabler.cs +++ /dev/null @@ -1,170 +0,0 @@ -using System.Collections.Immutable; -using Dalamud.Hooking; -using Dalamud.Plugin; -using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; -using OtterGui.Services; -using Penumbra.GameData; -using Penumbra.Interop.Hooks.ResourceLoading; -using Penumbra.Services; - -namespace Penumbra.Interop.Hooks.PostProcessing; - -public unsafe class RenderTargetHdrEnabler : IService, IDisposable -{ - /// This array must be sorted by CreationOrder ascending. - private static readonly ImmutableArray ForcedTextureConfigs = - [ - new ForcedTextureConfig(9, TextureFormat.R16G16B16A16_FLOAT, "Opaque Diffuse GBuffer"), - new ForcedTextureConfig(10, TextureFormat.R16G16B16A16_FLOAT, "Semitransparent Diffuse GBuffer"), - ]; - - private static readonly IComparer ForcedTextureConfigComparer - = Comparer.Create((lhs, rhs) => lhs.CreationOrder.CompareTo(rhs.CreationOrder)); - - private readonly Configuration _config; - private readonly Tuple _share; - private readonly ThreadLocal _textureIndices = new(() => new TextureIndices(-1, -1)); - - private readonly ThreadLocal?> _textures = new(() => null); - - public TextureReportRecord[]? TextureReport { get; private set; } - - private readonly Hook? _renderTargetManagerInitialize; - private readonly Hook? _createTexture2D; - - public RenderTargetHdrEnabler(IGameInteropProvider interop, Configuration config, IDalamudPluginInterface pi, - DalamudConfigService dalamudConfig, PeSigScanner peScanner) - { - _config = config; - if (peScanner.TryScanText(Sigs.RenderTargetManagerInitialize, out var initializeAddress) - && peScanner.TryScanText(Sigs.DeviceCreateTexture2D, out var createAddress)) - { - _renderTargetManagerInitialize = - interop.HookFromAddress(initializeAddress, RenderTargetManagerInitializeDetour); - _createTexture2D = interop.HookFromAddress(createAddress, CreateTexture2DDetour); - - if (config.HdrRenderTargets && !HookOverrides.Instance.PostProcessing.RenderTargetManagerInitialize) - _renderTargetManagerInitialize.Enable(); - } - - _share = pi.GetOrCreateData("Penumbra.RenderTargetHDR.V1", () => - { - bool? waitForPlugins = dalamudConfig.GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool s) ? s : null; - return new Tuple(waitForPlugins, config.HdrRenderTargets, - !HookOverrides.Instance.PostProcessing.RenderTargetManagerInitialize, [0], [false]); - }); - ++_share.Item4[0]; - } - - public bool? FirstLaunchWaitForPluginsState - => _share.Item1; - - public bool FirstLaunchHdrState - => _share.Item2; - - public bool FirstLaunchHdrHookOverrideState - => _share.Item3; - - public int PenumbraReloadCount - => _share.Item4[0]; - - public bool HdrEnabledSuccess - => _share.Item5[0]; - - ~RenderTargetHdrEnabler() - => Dispose(false); - - - public static ForcedTextureConfig? GetForcedTextureConfig(int creationOrder) - { - var i = ForcedTextureConfigs.BinarySearch(new ForcedTextureConfig(creationOrder, 0, string.Empty), ForcedTextureConfigComparer); - return i >= 0 ? ForcedTextureConfigs[i] : null; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool _) - { - _createTexture2D?.Dispose(); - _renderTargetManagerInitialize?.Dispose(); - } - - private nint RenderTargetManagerInitializeDetour(RenderTargetManager* @this) - { - _createTexture2D!.Enable(); - _share.Item5[0] = true; - _textureIndices.Value = new TextureIndices(0, 0); - _textures.Value = _config.DebugMode ? [] : null; - try - { - return _renderTargetManagerInitialize!.Original(@this); - } - finally - { - if (_textures.Value != null) - { - TextureReport = CreateTextureReport(@this, _textures.Value); - _textures.Value = null; - } - - _textureIndices.Value = new TextureIndices(-1, -1); - _createTexture2D.Disable(); - } - } - - private Texture* CreateTexture2DDetour( - Device* @this, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk) - { - var originalTextureFormat = textureFormat; - var indices = _textureIndices.IsValueCreated ? _textureIndices.Value : new TextureIndices(-1, -1); - if (indices.ConfigIndex >= 0 - && indices.ConfigIndex < ForcedTextureConfigs.Length - && ForcedTextureConfigs[indices.ConfigIndex].CreationOrder == indices.CreationOrder) - { - var config = ForcedTextureConfigs[indices.ConfigIndex++]; - textureFormat = (uint)config.ForcedTextureFormat; - } - - if (indices.CreationOrder >= 0) - { - ++indices.CreationOrder; - _textureIndices.Value = indices; - } - - var texture = _createTexture2D!.Original(@this, size, mipLevel, textureFormat, flags, unk); - if (_textures.IsValueCreated) - _textures.Value?.Add((nint)texture, (indices.CreationOrder - 1, originalTextureFormat)); - return texture; - } - - private static TextureReportRecord[] CreateTextureReport(RenderTargetManager* renderTargetManager, - Dictionary textures) - { - var rtmTextures = new Span(renderTargetManager, sizeof(RenderTargetManager) / sizeof(nint)); - var report = new List(); - for (var i = 0; i < rtmTextures.Length; ++i) - { - if (textures.TryGetValue(rtmTextures[i], out var texture)) - report.Add(new TextureReportRecord(i * sizeof(nint), texture.TextureIndex, (TextureFormat)texture.TextureFormat)); - } - - return report.ToArray(); - } - - private delegate nint RenderTargetManagerInitializeFunc(RenderTargetManager* @this); - - private delegate Texture* CreateTexture2DFunc(Device* @this, int* size, byte mipLevel, uint textureFormat, uint flags, uint unk); - - private record struct TextureIndices(int CreationOrder, int ConfigIndex); - - public readonly record struct ForcedTextureConfig(int CreationOrder, TextureFormat ForcedTextureFormat, string Comment); - - public readonly record struct TextureReportRecord(nint Offset, int CreationOrder, TextureFormat OriginalTextureFormat); -} diff --git a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs index b9c21556..40958eb4 100644 --- a/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs +++ b/Penumbra/Interop/Hooks/PostProcessing/ShaderReplacementFixer.cs @@ -7,11 +7,9 @@ using OtterGui.Classes; using OtterGui.Services; using Penumbra.Communication; using Penumbra.GameData; -using Penumbra.GameData.Files.MaterialStructs; using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.Structs; using Penumbra.Services; -using Penumbra.String.Classes; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using CSModelRenderer = FFXIVClientStructs.FFXIV.Client.Graphics.Render.ModelRenderer; using ModelRenderer = Penumbra.Interop.Services.ModelRenderer; @@ -65,6 +63,8 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic private readonly ResourceHandleDestructor _resourceHandleDestructor; private readonly CommunicatorService _communicator; + private readonly CharacterUtility _utility; + private readonly ModelRenderer _modelRenderer; private readonly HumanSetupScalingHook _humanSetupScalingHook; private readonly ModdedShaderPackageState _skinState; @@ -110,29 +110,31 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic CommunicatorService communicator, HookManager hooks, CharacterBaseVTables vTables, HumanSetupScalingHook humanSetupScalingHook) { _resourceHandleDestructor = resourceHandleDestructor; + _utility = utility; + _modelRenderer = modelRenderer; _communicator = communicator; _humanSetupScalingHook = humanSetupScalingHook; _skinState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&utility.Address->SkinShpkResource, - () => (ShaderPackageResourceHandle*)utility.DefaultSkinShpkResource); + () => (ShaderPackageResourceHandle**)&_utility.Address->SkinShpkResource, + () => (ShaderPackageResourceHandle*)_utility.DefaultSkinShpkResource); _characterStockingsState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&utility.Address->CharacterStockingsShpkResource, - () => (ShaderPackageResourceHandle*)utility.DefaultCharacterStockingsShpkResource); + () => (ShaderPackageResourceHandle**)&_utility.Address->CharacterStockingsShpkResource, + () => (ShaderPackageResourceHandle*)_utility.DefaultCharacterStockingsShpkResource); _characterLegacyState = new ModdedShaderPackageState( - () => (ShaderPackageResourceHandle**)&utility.Address->CharacterLegacyShpkResource, - () => (ShaderPackageResourceHandle*)utility.DefaultCharacterLegacyShpkResource); - _irisState = new ModdedShaderPackageState(() => modelRenderer.IrisShaderPackage, () => modelRenderer.DefaultIrisShaderPackage); - _characterGlassState = new ModdedShaderPackageState(() => modelRenderer.CharacterGlassShaderPackage, - () => modelRenderer.DefaultCharacterGlassShaderPackage); - _characterTransparencyState = new ModdedShaderPackageState(() => modelRenderer.CharacterTransparencyShaderPackage, - () => modelRenderer.DefaultCharacterTransparencyShaderPackage); - _characterTattooState = new ModdedShaderPackageState(() => modelRenderer.CharacterTattooShaderPackage, - () => modelRenderer.DefaultCharacterTattooShaderPackage); - _characterOcclusionState = new ModdedShaderPackageState(() => modelRenderer.CharacterOcclusionShaderPackage, - () => modelRenderer.DefaultCharacterOcclusionShaderPackage); + () => (ShaderPackageResourceHandle**)&_utility.Address->CharacterLegacyShpkResource, + () => (ShaderPackageResourceHandle*)_utility.DefaultCharacterLegacyShpkResource); + _irisState = new ModdedShaderPackageState(() => _modelRenderer.IrisShaderPackage, () => _modelRenderer.DefaultIrisShaderPackage); + _characterGlassState = new ModdedShaderPackageState(() => _modelRenderer.CharacterGlassShaderPackage, + () => _modelRenderer.DefaultCharacterGlassShaderPackage); + _characterTransparencyState = new ModdedShaderPackageState(() => _modelRenderer.CharacterTransparencyShaderPackage, + () => _modelRenderer.DefaultCharacterTransparencyShaderPackage); + _characterTattooState = new ModdedShaderPackageState(() => _modelRenderer.CharacterTattooShaderPackage, + () => _modelRenderer.DefaultCharacterTattooShaderPackage); + _characterOcclusionState = new ModdedShaderPackageState(() => _modelRenderer.CharacterOcclusionShaderPackage, + () => _modelRenderer.DefaultCharacterOcclusionShaderPackage); _hairMaskState = - new ModdedShaderPackageState(() => modelRenderer.HairMaskShaderPackage, () => modelRenderer.DefaultHairMaskShaderPackage); + new ModdedShaderPackageState(() => _modelRenderer.HairMaskShaderPackage, () => _modelRenderer.DefaultHairMaskShaderPackage); _humanSetupScalingHook.SetupReplacements += SetupHssReplacements; _humanOnRenderMaterialHook = hooks.CreateHook("Human.OnRenderMaterial", vTables.HumanVTable[64], @@ -193,7 +195,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic if (shpk == null) return; - var shpkName = mtrl->ShpkName.AsSpan(); + var shpkName = mtrl->ShpkNameSpan; var shpkState = GetStateForHumanSetup(shpkName) ?? GetStateForHumanRender(shpkName) ?? GetStateForModelRendererRender(shpkName) @@ -217,7 +219,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic } private ModdedShaderPackageState? GetStateForHumanSetup(MaterialResourceHandle* mtrlResource) - => mtrlResource == null ? null : GetStateForHumanSetup(mtrlResource->ShpkName.AsSpan()); + => mtrlResource == null ? null : GetStateForHumanSetup(mtrlResource->ShpkNameSpan); private ModdedShaderPackageState? GetStateForHumanSetup(ReadOnlySpan shpkName) => CharacterStockingsShpkName.SequenceEqual(shpkName) ? _characterStockingsState : null; @@ -227,7 +229,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic => _characterStockingsState.MaterialCount; private ModdedShaderPackageState? GetStateForHumanRender(MaterialResourceHandle* mtrlResource) - => mtrlResource == null ? null : GetStateForHumanRender(mtrlResource->ShpkName.AsSpan()); + => mtrlResource == null ? null : GetStateForHumanRender(mtrlResource->ShpkNameSpan); private ModdedShaderPackageState? GetStateForHumanRender(ReadOnlySpan shpkName) => SkinShpkName.SequenceEqual(shpkName) ? _skinState : null; @@ -237,7 +239,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic => _skinState.MaterialCount; private ModdedShaderPackageState? GetStateForModelRendererRender(MaterialResourceHandle* mtrlResource) - => mtrlResource == null ? null : GetStateForModelRendererRender(mtrlResource->ShpkName.AsSpan()); + => mtrlResource == null ? null : GetStateForModelRendererRender(mtrlResource->ShpkNameSpan); private ModdedShaderPackageState? GetStateForModelRendererRender(ReadOnlySpan shpkName) { @@ -264,7 +266,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic + _hairMaskState.MaterialCount; private ModdedShaderPackageState? GetStateForModelRendererUnk(MaterialResourceHandle* mtrlResource) - => mtrlResource == null ? null : GetStateForModelRendererUnk(mtrlResource->ShpkName.AsSpan()); + => mtrlResource == null ? null : GetStateForModelRendererUnk(mtrlResource->ShpkNameSpan); private ModdedShaderPackageState? GetStateForModelRendererUnk(ReadOnlySpan shpkName) { @@ -460,18 +462,8 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic return mtrlResource; } - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static int GetDataSetExpectedSize(uint dataFlags) - => (dataFlags & 4) != 0 - ? ColorTable.Size + ((dataFlags & 8) != 0 ? ColorDyeTable.Size : 0) - : 0; - private Texture* PrepareColorTableDetour(MaterialResourceHandle* thisPtr, byte stain0Id, byte stain1Id) { - if (thisPtr->DataSetSize < GetDataSetExpectedSize(thisPtr->DataFlags) && Utf8GamePath.IsRooted(thisPtr->FileName.AsSpan())) - Penumbra.Log.Warning( - $"Material at {thisPtr->FileName} has data set of size {thisPtr->DataSetSize} bytes, but should have at least {GetDataSetExpectedSize(thisPtr->DataFlags)} bytes. This may cause crashes due to access violations."); - // If we don't have any on-screen instances of modded characterlegacy.shpk, we don't need the slow path at all. if (!Enabled || GetTotalMaterialCountForColorTable() == 0) return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id); @@ -480,7 +472,7 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic if (material == null) return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id); - var shpkState = GetStateForColorTable(thisPtr->ShpkName.AsSpan()); + var shpkState = GetStateForColorTable(thisPtr->ShpkNameSpan); if (shpkState == null || shpkState.MaterialCount == 0) return _prepareColorTableHook.Original(thisPtr, stain0Id, stain1Id); @@ -508,8 +500,9 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic private readonly ConcurrentSet _materials = new(); // ConcurrentDictionary.Count uses a lock in its current implementation. - private uint _materialCount; - private ulong _slowPathCallDelta; + private uint _materialCount = 0; + + private ulong _slowPathCallDelta = 0; public uint MaterialCount { diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs index 35ee86dc..5ba8c975 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapHandler.cs @@ -2,9 +2,9 @@ namespace Penumbra.Interop.Hooks.ResourceLoading; -public sealed class PapHandler(PeSigScanner sigScanner, PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable +public sealed class PapHandler(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable { - private readonly PapRewriter _papRewriter = new(sigScanner, papResourceHandler); + private readonly PapRewriter _papRewriter = new(papResourceHandler); public void Enable() { diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs index caf43d08..2fb1623d 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PapRewriter.cs @@ -1,26 +1,27 @@ using System.Text.Unicode; using Dalamud.Hooking; using Iced.Intel; -using OtterGui.Extensions; +using OtterGui; using Penumbra.String.Classes; using Swan; namespace Penumbra.Interop.Hooks.ResourceLoading; -public sealed class PapRewriter(PeSigScanner sigScanner, PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable +public sealed class PapRewriter(PapRewriter.PapResourceHandlerPrototype papResourceHandler) : IDisposable { public unsafe delegate int PapResourceHandlerPrototype(void* self, byte* path, int length); + private readonly PeSigScanner _scanner = new(); private readonly Dictionary _hooks = []; private readonly Dictionary<(nint, Register, ulong), nint> _nativeAllocPaths = []; private readonly List _nativeAllocCaves = []; public void Rewrite(string sig, string name) { - if (!sigScanner.TryScanText(sig, out var address)) + if (!_scanner.TryScanText(sig, out var address)) throw new Exception($"Signature for {name} [{sig}] could not be found."); - var funcInstructions = sigScanner.GetFunctionInstructions(address).ToArray(); + var funcInstructions = _scanner.GetFunctionInstructions(address).ToArray(); var hookPoints = ScanPapHookPoints(funcInstructions).ToList(); foreach (var hookPoint in hookPoints) @@ -164,6 +165,8 @@ public sealed class PapRewriter(PeSigScanner sigScanner, PapRewriter.PapResource public void Dispose() { + _scanner.Dispose(); + foreach (var hook in _hooks.Values) { hook.Disable(); diff --git a/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs index 620f3160..4be0da00 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/PeSigScanner.cs @@ -1,13 +1,12 @@ using System.IO.MemoryMappedFiles; using Iced.Intel; -using OtterGui.Services; using PeNet; using Decoder = Iced.Intel.Decoder; namespace Penumbra.Interop.Hooks.ResourceLoading; // A good chunk of this was blatantly stolen from Dalamud's SigScanner 'cause Winter could not be faffed, Winter will definitely not rewrite it later -public unsafe class PeSigScanner : IDisposable, IService +public unsafe class PeSigScanner : IDisposable { private readonly MemoryMappedFile _file; private readonly MemoryMappedViewAccessor _textSection; diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs index 6ddcbfda..47f96d98 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceLoader.cs @@ -2,7 +2,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Collections; -using Penumbra.Interop.Hooks.Resources; using Penumbra.Interop.PathResolving; using Penumbra.Interop.SafeHandles; using Penumbra.Interop.Structs; @@ -14,40 +13,29 @@ namespace Penumbra.Interop.Hooks.ResourceLoading; public unsafe class ResourceLoader : IDisposable, IService { - private readonly ResourceService _resources; - private readonly FileReadService _fileReadService; - private readonly RsfService _rsfService; - private readonly PapHandler _papHandler; - private readonly Configuration _config; - private readonly ResourceHandleDestructor _destructor; + private readonly ResourceService _resources; + private readonly FileReadService _fileReadService; + private readonly RsfService _rsfService; + private readonly PapHandler _papHandler; + private readonly Configuration _config; - private readonly ConcurrentDictionary _ongoingLoads = []; - - private readonly ThreadLocal _resolvedData = new(() => ResolveData.Invalid); + private ResolveData _resolvedData = ResolveData.Invalid; public event Action? PapRequested; - public IReadOnlyDictionary OngoingLoads - => _ongoingLoads; - - public ResourceLoader(ResourceService resources, FileReadService fileReadService, RsfService rsfService, Configuration config, PeSigScanner sigScanner, - ResourceHandleDestructor destructor) + public ResourceLoader(ResourceService resources, FileReadService fileReadService, RsfService rsfService, Configuration config) { _resources = resources; _fileReadService = fileReadService; - _rsfService = rsfService; + _rsfService = rsfService; _config = config; - _destructor = destructor; ResetResolvePath(); - _resources.ResourceRequested += ResourceHandler; - _resources.ResourceStateUpdating += ResourceStateUpdatingHandler; - _resources.ResourceStateUpdated += ResourceStateUpdatedHandler; - _resources.ResourceHandleIncRef += IncRefProtection; - _resources.ResourceHandleDecRef += DecRefProtection; - _fileReadService.ReadSqPack += ReadSqPackDetour; - _destructor.Subscribe(ResourceDestructorHandler, ResourceHandleDestructor.Priority.ResourceLoader); + _resources.ResourceRequested += ResourceHandler; + _resources.ResourceHandleIncRef += IncRefProtection; + _resources.ResourceHandleDecRef += DecRefProtection; + _fileReadService.ReadSqPack += ReadSqPackDetour; - _papHandler = new PapHandler(sigScanner, PapResourceHandler); + _papHandler = new PapHandler(PapResourceHandler); _papHandler.Enable(); } @@ -56,11 +44,10 @@ public unsafe class ResourceLoader : IDisposable, IService if (!_config.EnableMods || !Utf8GamePath.FromPointer(path, MetaDataComputation.CiCrc32, out var gamePath)) return length; - var resolvedData = _resolvedData.Value; var (resolvedPath, data) = _incMode.Value ? (null, ResolveData.Invalid) - : resolvedData.Valid - ? (resolvedData.ModCollection.ResolvePath(gamePath), resolvedData) + : _resolvedData.Valid + ? (_resolvedData.ModCollection.ResolvePath(gamePath), _resolvedData) : ResolvePath(gamePath, ResourceCategory.Chara, ResourceType.Pap); @@ -79,31 +66,19 @@ public unsafe class ResourceLoader : IDisposable, IService /// Load a resource for a given path and a specific collection. public ResourceHandle* LoadResolvedResource(ResourceCategory category, ResourceType type, CiByteString path, ResolveData resolveData) { - var previous = _resolvedData.Value; - _resolvedData.Value = resolveData; - try - { - return _resources.GetResource(category, type, path); - } - finally - { - _resolvedData.Value = previous; - } + _resolvedData = resolveData; + var ret = _resources.GetResource(category, type, path); + _resolvedData = ResolveData.Invalid; + return ret; } /// Load a resource for a given path and a specific collection. public SafeResourceHandle LoadResolvedSafeResource(ResourceCategory category, ResourceType type, CiByteString path, ResolveData resolveData) { - var previous = _resolvedData.Value; - _resolvedData.Value = resolveData; - try - { - return _resources.GetSafeResource(category, type, path); - } - finally - { - _resolvedData.Value = previous; - } + _resolvedData = resolveData; + var ret = _resources.GetSafeResource(category, type, path); + _resolvedData = ResolveData.Invalid; + return ret; } /// The function to use to resolve a given path. @@ -134,32 +109,12 @@ public unsafe class ResourceLoader : IDisposable, IService /// public event FileLoadedDelegate? FileLoaded; - public delegate void ResourceCompleteDelegate(ResourceHandle* resource, CiByteString path, Utf8GamePath originalPath, - ReadOnlySpan additionalData, bool isAsync); - - /// - /// Event fired just before a resource finishes loading. - /// must be checked to know whether the load was successful or not. - /// AdditionalData is either empty or the part of the path inside the leading pipes. - /// - public event ResourceCompleteDelegate? BeforeResourceComplete; - - /// - /// Event fired when a resource has finished loading. - /// must be checked to know whether the load was successful or not. - /// AdditionalData is either empty or the part of the path inside the leading pipes. - /// - public event ResourceCompleteDelegate? ResourceComplete; - public void Dispose() { - _resources.ResourceRequested -= ResourceHandler; - _resources.ResourceStateUpdating -= ResourceStateUpdatingHandler; - _resources.ResourceStateUpdated -= ResourceStateUpdatedHandler; - _resources.ResourceHandleIncRef -= IncRefProtection; - _resources.ResourceHandleDecRef -= DecRefProtection; - _fileReadService.ReadSqPack -= ReadSqPackDetour; - _destructor.Unsubscribe(ResourceDestructorHandler); + _resources.ResourceRequested -= ResourceHandler; + _resources.ResourceHandleIncRef -= IncRefProtection; + _resources.ResourceHandleDecRef -= DecRefProtection; + _fileReadService.ReadSqPack -= ReadSqPackDetour; _papHandler.Dispose(); } @@ -172,17 +127,15 @@ public unsafe class ResourceLoader : IDisposable, IService CompareHash(ComputeHash(path.Path, parameters), hash, path); // If no replacements are being made, we still want to be able to trigger the event. - var resolvedData = _resolvedData.Value; var (resolvedPath, data) = _incMode.Value ? (null, ResolveData.Invalid) - : resolvedData.Valid - ? (resolvedData.ModCollection.ResolvePath(path), resolvedData) + : _resolvedData.Valid + ? (_resolvedData.ModCollection.ResolvePath(path), _resolvedData) : ResolvePath(path, category, type); if (resolvedPath == null || !Utf8GamePath.FromByteString(resolvedPath.Value.InternalName, out var p)) { - returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, original, parameters); - TrackResourceLoad(returnValue, original); + returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters); ResourceLoaded?.Invoke(returnValue, path, resolvedPath, data); return; } @@ -192,57 +145,10 @@ public unsafe class ResourceLoader : IDisposable, IService hash = ComputeHash(resolvedPath.Value.InternalName, parameters); var oldPath = path; path = p; - returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, original, parameters); - TrackResourceLoad(returnValue, original); + returnValue = _resources.GetOriginalResource(sync, category, type, hash, path.Path, parameters); ResourceLoaded?.Invoke(returnValue, oldPath, resolvedPath.Value, data); } - private void TrackResourceLoad(ResourceHandle* handle, Utf8GamePath original) - { - if (handle->UnkState == 2 && handle->LoadState >= LoadState.Success) - return; - - _ongoingLoads.TryAdd((nint)handle, original.Clone()); - } - - private void ResourceStateUpdatedHandler(ResourceHandle* handle, Utf8GamePath syncOriginal, (byte, LoadState) previousState, ref uint returnValue) - { - if (handle->UnkState != 2 || handle->LoadState < LoadState.Success || previousState is { Item1: 2, Item2: >= LoadState.Success }) - return; - - if (!_ongoingLoads.TryRemove((nint)handle, out var asyncOriginal)) - asyncOriginal = Utf8GamePath.Empty; - - var path = handle->CsHandle.FileName; - if (!syncOriginal.IsEmpty && !asyncOriginal.IsEmpty && !syncOriginal.Equals(asyncOriginal)) - Penumbra.Log.Warning($"[ResourceLoader] Resource original paths inconsistency: 0x{(nint)handle:X}, of path {path}, sync original {syncOriginal}, async original {asyncOriginal}."); - var original = !asyncOriginal.IsEmpty ? asyncOriginal : syncOriginal; - - Penumbra.Log.Excessive($"[ResourceLoader] Resource is complete: 0x{(nint)handle:X}, of path {path}, original {original}, state {previousState.Item1}:{previousState.Item2} -> {handle->UnkState}:{handle->LoadState}, sync: {asyncOriginal.IsEmpty}"); - if (PathDataHandler.Split(path.AsSpan(), out var actualPath, out var additionalData)) - ResourceComplete?.Invoke(handle, new CiByteString(actualPath), original, additionalData, !asyncOriginal.IsEmpty); - else - ResourceComplete?.Invoke(handle, path.AsByteString(), original, [], !asyncOriginal.IsEmpty); - } - - private void ResourceStateUpdatingHandler(ResourceHandle* handle, Utf8GamePath syncOriginal) - { - if (handle->UnkState != 1 || handle->LoadState != LoadState.Success) - return; - - if (!_ongoingLoads.TryGetValue((nint)handle, out var asyncOriginal)) - asyncOriginal = Utf8GamePath.Empty; - - var path = handle->CsHandle.FileName; - var original = asyncOriginal.IsEmpty ? syncOriginal : asyncOriginal; - - Penumbra.Log.Excessive($"[ResourceLoader] Resource is about to be complete: 0x{(nint)handle:X}, of path {path}, original {original}"); - if (PathDataHandler.Split(path.AsSpan(), out var actualPath, out var additionalData)) - BeforeResourceComplete?.Invoke(handle, new CiByteString(actualPath), original, additionalData, !asyncOriginal.IsEmpty); - else - BeforeResourceComplete?.Invoke(handle, path.AsByteString(), original, [], !asyncOriginal.IsEmpty); - } - private void ReadSqPackDetour(SeFileDescriptor* fileDescriptor, ref int priority, ref bool isSync, ref byte? returnValue) { if (fileDescriptor->ResourceHandle == null) @@ -270,6 +176,7 @@ public unsafe class ResourceLoader : IDisposable, IService gamePath.Path.IsAscii); fileDescriptor->ResourceHandle->FileNameData = path.Path; fileDescriptor->ResourceHandle->FileNameLength = path.Length; + MtrlForceSync(fileDescriptor, ref isSync); returnValue = DefaultLoadResource(path, fileDescriptor, priority, isSync, data); // Return original resource handle path so that they can be loaded separately. fileDescriptor->ResourceHandle->FileNameData = gamePath.Path.Path; @@ -308,6 +215,16 @@ public unsafe class ResourceLoader : IDisposable, IService } } + /// Special handling for materials. + private static void MtrlForceSync(SeFileDescriptor* fileDescriptor, ref bool isSync) + { + // Force isSync = true for Materials. I don't really understand why, + // or where the difference even comes from. + // Was called with True on my client and with false on other peoples clients, + // which caused problems. + isSync |= fileDescriptor->ResourceHandle->FileType is ResourceType.Mtrl; + } + /// /// A resource with ref count 0 that gets incremented goes through GetResourceAsync again. /// This means, that if the path determined from that is different than the resources path, @@ -348,11 +265,6 @@ public unsafe class ResourceLoader : IDisposable, IService returnValue = 1; } - private void ResourceDestructorHandler(ResourceHandle* handle) - { - _ongoingLoads.TryRemove((nint)handle, out _); - } - /// Compute the CRC32 hash for a given path together with potential resource parameters. private static int ComputeHash(CiByteString path, GetResourceParameters* pGetResParams) { diff --git a/Penumbra/Interop/Hooks/ResourceLoading/ResourceService.cs b/Penumbra/Interop/Hooks/ResourceLoading/ResourceService.cs index 1a40accc..126505d1 100644 --- a/Penumbra/Interop/Hooks/ResourceLoading/ResourceService.cs +++ b/Penumbra/Interop/Hooks/ResourceLoading/ResourceService.cs @@ -1,5 +1,4 @@ using Dalamud.Hooking; -using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using FFXIVClientStructs.FFXIV.Client.System.Resource; @@ -20,8 +19,6 @@ public unsafe class ResourceService : IDisposable, IRequiredService private readonly PerformanceTracker _performance; private readonly ResourceManagerService _resourceManager; - private readonly ThreadLocal _currentGetResourcePath = new(() => Utf8GamePath.Empty); - public ResourceService(PerformanceTracker performance, ResourceManagerService resourceManager, IGameInteropProvider interop) { _performance = performance; @@ -37,8 +34,6 @@ public unsafe class ResourceService : IDisposable, IRequiredService _getResourceSyncHook.Enable(); if (!HookOverrides.Instance.ResourceLoading.GetResourceAsync) _getResourceAsyncHook.Enable(); - if (!HookOverrides.Instance.ResourceLoading.UpdateResourceState) - _updateResourceStateHook.Enable(); if (!HookOverrides.Instance.ResourceLoading.IncRef) _incRefHook.Enable(); if (!HookOverrides.Instance.ResourceLoading.DecRef) @@ -59,10 +54,8 @@ public unsafe class ResourceService : IDisposable, IRequiredService { _getResourceSyncHook.Dispose(); _getResourceAsyncHook.Dispose(); - _updateResourceStateHook.Dispose(); _incRefHook.Dispose(); _decRefHook.Dispose(); - _currentGetResourcePath.Dispose(); } #region GetResource @@ -86,8 +79,7 @@ public unsafe class ResourceService : IDisposable, IRequiredService ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams, nint unk7, uint unk8); private delegate ResourceHandle* GetResourceAsyncPrototype(ResourceManager* resourceManager, ResourceCategory* pCategoryId, - ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams, byte isUnknown, nint unk8, - uint unk9); + ResourceType* pResourceType, int* pResourceHash, byte* pPath, GetResourceParameters* pGetResParams, byte isUnknown, nint unk8, uint unk9); [Signature(Sigs.GetResourceSync, DetourName = nameof(GetResourceSyncDetour))] private readonly Hook _getResourceSyncHook = null!; @@ -120,92 +112,28 @@ public unsafe class ResourceService : IDisposable, IRequiredService unk9); } - if (gamePath.IsEmpty) - { - Penumbra.Log.Error($"[ResourceService] Empty resource path requested with category {*categoryId}, type {*resourceType}, hash {*resourceHash}."); - return null; - } - - var original = gamePath; ResourceHandle* returnValue = null; - ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, original, pGetResParams, ref isSync, + ResourceRequested?.Invoke(ref *categoryId, ref *resourceType, ref *resourceHash, ref gamePath, gamePath, pGetResParams, ref isSync, ref returnValue); if (returnValue != null) return returnValue; - return GetOriginalResource(isSync, *categoryId, *resourceType, *resourceHash, gamePath.Path, original, pGetResParams, isUnk, unk8, - unk9); + return GetOriginalResource(isSync, *categoryId, *resourceType, *resourceHash, gamePath.Path, pGetResParams, isUnk, unk8, unk9); } /// Call the original GetResource function. public ResourceHandle* GetOriginalResource(bool sync, ResourceCategory categoryId, ResourceType type, int hash, CiByteString path, - Utf8GamePath original, GetResourceParameters* resourceParameters = null, byte unk = 0, nint unk8 = 0, uint unk9 = 0) - { - var previous = _currentGetResourcePath.Value; - try - { - _currentGetResourcePath.Value = original; - return sync - ? _getResourceSyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path, - resourceParameters, unk8, unk9) - : _getResourceAsyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path, - resourceParameters, unk, unk8, unk9); - } - finally - { - _currentGetResourcePath.Value = previous; - } - } + => sync + ? _getResourceSyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path, + resourceParameters, unk8, unk9) + : _getResourceAsyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path, + resourceParameters, unk, unk8, unk9); #endregion private delegate nint ResourceHandlePrototype(ResourceHandle* handle); - #region UpdateResourceState - - /// Invoked before a resource state is updated. - /// The resource handle. - /// The original game path of the resource, if loaded synchronously. - public delegate void ResourceStateUpdatingDelegate(ResourceHandle* handle, Utf8GamePath syncOriginal); - - /// Invoked after a resource state is updated. - /// The resource handle. - /// The original game path of the resource, if loaded synchronously. - /// The previous state of the resource. - /// The return value to use. - public delegate void ResourceStateUpdatedDelegate(ResourceHandle* handle, Utf8GamePath syncOriginal, - (byte UnkState, LoadState LoadState) previousState, ref uint returnValue); - - /// - /// - /// Subscribers should be exception-safe. - /// - public event ResourceStateUpdatingDelegate? ResourceStateUpdating; - - /// - /// - /// Subscribers should be exception-safe. - /// - public event ResourceStateUpdatedDelegate? ResourceStateUpdated; - - private delegate uint UpdateResourceStatePrototype(ResourceHandle* handle, byte offFileThread); - - [Signature(Sigs.UpdateResourceState, DetourName = nameof(UpdateResourceStateDetour))] - private readonly Hook _updateResourceStateHook = null!; - - private uint UpdateResourceStateDetour(ResourceHandle* handle, byte offFileThread) - { - var previousState = (handle->UnkState, handle->LoadState); - var syncOriginal = _currentGetResourcePath.IsValueCreated ? _currentGetResourcePath.Value : Utf8GamePath.Empty; - ResourceStateUpdating?.Invoke(handle, syncOriginal); - var ret = _updateResourceStateHook.OriginalDisposeSafe(handle, offFileThread); - ResourceStateUpdated?.Invoke(handle, syncOriginal, previousState, ref ret); - return ret; - } - - #endregion - #region IncRef /// Invoked before a resource handle reference count is incremented. diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index db39889e..54066782 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -6,7 +6,6 @@ using Penumbra.Collections; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; -using Penumbra.Interop.Processing; using static FFXIVClientStructs.FFXIV.Client.Game.Character.ActionEffectHandler; namespace Penumbra.Interop.Hooks.Resources; @@ -36,7 +35,6 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable private readonly Hook _resolveMPapPathHook; private readonly Hook _resolveMdlPathHook; private readonly Hook _resolveMtrlPathHook; - private readonly Hook _resolveSkinMtrlPathHook; private readonly Hook _resolvePapPathHook; private readonly Hook _resolveKdbPathHook; private readonly Hook _resolvePhybPathHook; @@ -54,23 +52,22 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable { _parent = parent; // @formatter:off - _resolveSklbPathHook = Create($"{name}.{nameof(ResolveSklb)}", hooks, vTable[76], type, ResolveSklb, ResolveSklbHuman); - _resolveMdlPathHook = Create($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman); - _resolveSkpPathHook = Create($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman); - _resolvePhybPathHook = Create($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman); - _resolveKdbPathHook = Create($"{name}.{nameof(ResolveKdb)}", hooks, vTable[80], type, ResolveKdb, ResolveKdbHuman); - _vFunc81Hook = Create( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81); - _resolveBnmbPathHook = Create($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman); - _vFunc83Hook = Create( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83); - _resolvePapPathHook = Create( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman); - _resolveTmbPathHook = Create( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb); - _resolveMPapPathHook = Create( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap); - _resolveImcPathHook = Create($"{name}.{nameof(ResolveImc)}", hooks, vTable[89], ResolveImc); - _resolveMtrlPathHook = Create( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[90], ResolveMtrl); - _resolveSkinMtrlPathHook = Create($"{name}.{nameof(ResolveSkinMtrl)}", hooks, vTable[91], ResolveSkinMtrl); - _resolveDecalPathHook = Create($"{name}.{nameof(ResolveDecal)}", hooks, vTable[92], ResolveDecal); - _resolveVfxPathHook = Create( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman); - _resolveEidPathHook = Create( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid); + _resolveSklbPathHook = Create($"{name}.{nameof(ResolveSklb)}", hooks, vTable[76], type, ResolveSklb, ResolveSklbHuman); + _resolveMdlPathHook = Create($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman); + _resolveSkpPathHook = Create($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman); + _resolvePhybPathHook = Create($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman); + _resolveKdbPathHook = Create($"{name}.{nameof(ResolveKdb)}", hooks, vTable[80], type, ResolveKdb, ResolveKdbHuman); + _vFunc81Hook = Create( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81); + _resolveBnmbPathHook = Create($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman); + _vFunc83Hook = Create( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83); + _resolvePapPathHook = Create( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman); + _resolveTmbPathHook = Create( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb); + _resolveMPapPathHook = Create( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap); + _resolveImcPathHook = Create($"{name}.{nameof(ResolveImc)}", hooks, vTable[89], ResolveImc); + _resolveMtrlPathHook = Create( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[90], ResolveMtrl); + _resolveDecalPathHook = Create($"{name}.{nameof(ResolveDecal)}", hooks, vTable[92], ResolveDecal); + _resolveVfxPathHook = Create( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman); + _resolveEidPathHook = Create( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid); // @formatter:on @@ -86,7 +83,6 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Enable(); _resolveMdlPathHook.Enable(); _resolveMtrlPathHook.Enable(); - _resolveSkinMtrlPathHook.Enable(); _resolvePapPathHook.Enable(); _resolveKdbPathHook.Enable(); _resolvePhybPathHook.Enable(); @@ -107,7 +103,6 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Disable(); _resolveMdlPathHook.Disable(); _resolveMtrlPathHook.Disable(); - _resolveSkinMtrlPathHook.Disable(); _resolvePapPathHook.Disable(); _resolveKdbPathHook.Disable(); _resolvePhybPathHook.Disable(); @@ -128,7 +123,6 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Dispose(); _resolveMdlPathHook.Dispose(); _resolveMtrlPathHook.Dispose(); - _resolveSkinMtrlPathHook.Dispose(); _resolvePapPathHook.Dispose(); _resolveKdbPathHook.Dispose(); _resolvePhybPathHook.Dispose(); @@ -159,15 +153,6 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable private nint ResolveMtrl(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint mtrlFileName) => ResolvePath(drawObject, _resolveMtrlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex, mtrlFileName)); - private nint ResolveSkinMtrl(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex) - { - var finalPathBuffer = _resolveSkinMtrlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex); - if (DebugConfiguration.UseSkinMaterialProcessing && finalPathBuffer != nint.Zero && finalPathBuffer == pathBuffer) - SkinMtrlPathEarlyProcessing.Process(new Span((void*)pathBuffer, (int)pathBufferSize), (CharacterBase*)drawObject, slotIndex); - - return ResolvePath(drawObject, finalPathBuffer); - } - private nint ResolvePap(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName) => ResolvePath(drawObject, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); @@ -261,6 +246,28 @@ 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(8)] + public PrimaryId BonusModel; + + [FieldOffset(10)] + public Variant BonusVariant; + + [FieldOffset(20)] + public ushort VfxId; + + [FieldOffset(22)] + public GenderRace GenderRace; + } + private nint ResolveVfxHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint unkOutParam) { switch (slotIndex) diff --git a/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs b/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs index 0e04029b..bdb11752 100644 --- a/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs +++ b/Penumbra/Interop/Hooks/Resources/ResourceHandleDestructor.cs @@ -14,12 +14,9 @@ public sealed unsafe class ResourceHandleDestructor : EventWrapperPtr SubfileHelper, - /// + /// ShaderReplacementFixer, - /// - ResourceLoader, - /// ResourceWatcher, } diff --git a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs index 0415fc9d..c459a67a 100644 --- a/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs +++ b/Penumbra/Interop/MaterialPreview/LiveColorTablePreviewer.cs @@ -84,9 +84,7 @@ public sealed unsafe class LiveColorTablePreviewer : LiveMaterialPreviewerBase textureSize[1] = Height; using var texture = - new SafeTextureHandle( - Device.Instance()->CreateTexture2D(textureSize, 1, TextureFormat.R16G16B16A16_FLOAT, - TextureFlags.TextureNoSwizzle | TextureFlags.Immutable | TextureFlags.Managed, 7), false); + new SafeTextureHandle(Device.Instance()->CreateTexture2D(textureSize, 1, 0x2460, 0x80000804, 7), false); if (texture.IsInvalid) return; diff --git a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs index a9fb46ff..f2ea2d6c 100644 --- a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs +++ b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs @@ -85,7 +85,7 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy if (mtrlHandle == null) continue; - PathDataHandler.Split(mtrlHandle->FileName.AsSpan(), out var path, out _); + PathDataHandler.Split(mtrlHandle->ResourceHandle.FileName.AsSpan(), out var path, out _); var fileName = CiByteString.FromSpanUnsafe(path, true); if (fileName == needle) result.Add(new MaterialInfo(index, type, i, j)); diff --git a/Penumbra/Interop/PathResolving/CollectionResolver.cs b/Penumbra/Interop/PathResolving/CollectionResolver.cs index 136393d4..576b61bb 100644 --- a/Penumbra/Interop/PathResolving/CollectionResolver.cs +++ b/Penumbra/Interop/PathResolving/CollectionResolver.cs @@ -1,7 +1,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using OtterGui.Extensions; +using OtterGui; using OtterGui.Services; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -89,19 +89,12 @@ public sealed unsafe class CollectionResolver( /// Identify the correct collection for a draw object. public ResolveData IdentifyCollection(DrawObject* drawObject, bool useCache) { - if (drawObject is null) - return DefaultCollection; - - Actor obj = drawObjectState.TryGetValue(drawObject, out var gameObject) + var obj = (GameObject*)(drawObjectState.TryGetValue((nint)drawObject, out var gameObject) ? gameObject.Item1 - : drawObjectState.LastGameObject; - return IdentifyCollection(obj.AsObject, useCache); + : drawObjectState.LastGameObject); + return IdentifyCollection(obj, useCache); } - /// Get the default collection. - public ResolveData DefaultCollection - => collectionManager.Active.Default.ToResolveData(); - /// Return whether the given ModelChara id refers to a human-type model. public bool IsModelHuman(uint modelCharaId) => humanModels.IsHuman(modelCharaId); @@ -137,7 +130,7 @@ public sealed unsafe class CollectionResolver( { var item = charaEntry.Value; var identifier = actors.CreatePlayer(new ByteString(item->Name), item->HomeWorldId); - Penumbra.Log.Excessive( + Penumbra.Log.Verbose( $"Identified {identifier.Incognito(null)} in cutscene for actor {idx + 200} at 0x{(ulong)gameObject:X} of race {(gameObject->IsCharacter() ? ((Character*)gameObject)->DrawData.CustomizeData.Race.ToString() : "Unknown")}."); if (identifier.IsValid && CollectionByIdentifier(identifier) is { } coll) { diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index 97e64f84..8e32dd76 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -15,11 +15,10 @@ public sealed class CutsceneService : IRequiredService, IDisposable public const int CutsceneEndIdx = (int)ScreenActor.CutsceneEnd; public const int CutsceneSlots = CutsceneEndIdx - CutsceneStartIdx; - private readonly ObjectManager _objects; - private readonly CopyCharacter _copyCharacter; - private readonly CharacterDestructor _characterDestructor; - private readonly ConstructCutsceneCharacter _constructCutsceneCharacter; - private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray(); + private readonly ObjectManager _objects; + private readonly CopyCharacter _copyCharacter; + private readonly CharacterDestructor _characterDestructor; + private readonly short[] _copiedCharacters = Enumerable.Repeat((short)-1, CutsceneSlots).ToArray(); public IEnumerable> Actors => Enumerable.Range(CutsceneStartIdx, CutsceneSlots) @@ -27,15 +26,13 @@ public sealed class CutsceneService : IRequiredService, IDisposable .Select(i => KeyValuePair.Create(i, this[i] ?? _objects.GetDalamudObject(i)!)); public unsafe CutsceneService(ObjectManager objects, CopyCharacter copyCharacter, CharacterDestructor characterDestructor, - ConstructCutsceneCharacter constructCutsceneCharacter, IClientState clientState) + IClientState clientState) { - _objects = objects; - _copyCharacter = copyCharacter; - _characterDestructor = characterDestructor; - _constructCutsceneCharacter = constructCutsceneCharacter; + _objects = objects; + _copyCharacter = copyCharacter; + _characterDestructor = characterDestructor; _copyCharacter.Subscribe(OnCharacterCopy, CopyCharacter.Priority.CutsceneService); _characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.CutsceneService); - _constructCutsceneCharacter.Subscribe(OnSetupPlayerNpc, ConstructCutsceneCharacter.Priority.CutsceneService); if (clientState.IsGPosing) RecoverGPoseActors(); } @@ -75,7 +72,6 @@ public sealed class CutsceneService : IRequiredService, IDisposable return false; _copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx; - _objects.InvokeRequiredUpdates(); return true; } @@ -91,7 +87,6 @@ public sealed class CutsceneService : IRequiredService, IDisposable { _copyCharacter.Unsubscribe(OnCharacterCopy); _characterDestructor.Unsubscribe(OnCharacterDestructor); - _constructCutsceneCharacter.Unsubscribe(OnSetupPlayerNpc); } private unsafe void OnCharacterDestructor(Character* character) @@ -129,15 +124,6 @@ public sealed class CutsceneService : IRequiredService, IDisposable _copiedCharacters[idx] = (short)(source != null ? source->GameObject.ObjectIndex : -1); } - private unsafe void OnSetupPlayerNpc(Character* npc) - { - if (npc == null || npc->ObjectIndex is < CutsceneStartIdx or >= CutsceneEndIdx) - return; - - var idx = npc->GameObject.ObjectIndex - CutsceneStartIdx; - _copiedCharacters[idx] = 0; - } - /// Try to recover GPose actors on reloads into a running game. /// This is not 100% accurate due to world IDs, minions etc., but will be mostly sane. private void RecoverGPoseActors() diff --git a/Penumbra/Interop/PathResolving/DrawObjectState.cs b/Penumbra/Interop/PathResolving/DrawObjectState.cs index 6f3e457c..5e413fe2 100644 --- a/Penumbra/Interop/PathResolving/DrawObjectState.cs +++ b/Penumbra/Interop/PathResolving/DrawObjectState.cs @@ -9,42 +9,39 @@ using Penumbra.Interop.Hooks.Objects; namespace Penumbra.Interop.PathResolving; -public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary, IService +public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary, IService { private readonly ObjectManager _objects; private readonly CreateCharacterBase _createCharacterBase; private readonly WeaponReload _weaponReload; private readonly CharacterBaseDestructor _characterBaseDestructor; - private readonly CharacterDestructor _characterDestructor; private readonly GameState _gameState; - private readonly Dictionary _drawObjectToGameObject = []; + private readonly Dictionary _drawObjectToGameObject = []; public nint LastGameObject => _gameState.LastGameObject; public unsafe DrawObjectState(ObjectManager objects, CreateCharacterBase createCharacterBase, WeaponReload weaponReload, - CharacterBaseDestructor characterBaseDestructor, GameState gameState, IFramework framework, CharacterDestructor characterDestructor) + CharacterBaseDestructor characterBaseDestructor, GameState gameState, IFramework framework) { _objects = objects; _createCharacterBase = createCharacterBase; _weaponReload = weaponReload; _characterBaseDestructor = characterBaseDestructor; _gameState = gameState; - _characterDestructor = characterDestructor; framework.RunOnFrameworkThread(InitializeDrawObjects); _weaponReload.Subscribe(OnWeaponReloading, WeaponReload.Priority.DrawObjectState); _weaponReload.Subscribe(OnWeaponReloaded, WeaponReload.PostEvent.Priority.DrawObjectState); _createCharacterBase.Subscribe(OnCharacterBaseCreated, CreateCharacterBase.PostEvent.Priority.DrawObjectState); _characterBaseDestructor.Subscribe(OnCharacterBaseDestructor, CharacterBaseDestructor.Priority.DrawObjectState); - _characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.DrawObjectState); } - public bool ContainsKey(Model key) + public bool ContainsKey(nint key) => _drawObjectToGameObject.ContainsKey(key); - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() => _drawObjectToGameObject.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() @@ -53,28 +50,16 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary _drawObjectToGameObject.Count; - public bool TryGetValue(Model drawObject, out (Actor, ObjectIndex, bool) gameObject) - { - if (!_drawObjectToGameObject.TryGetValue(drawObject, out gameObject)) - return false; + public bool TryGetValue(nint drawObject, out (nint, bool) gameObject) + => _drawObjectToGameObject.TryGetValue(drawObject, out gameObject); - var currentObject = _objects[gameObject.Item2]; - if (currentObject != gameObject.Item1) - { - Penumbra.Log.Warning($"[DrawObjectState] Stored association {drawObject} -> {gameObject.Item1} has index {gameObject.Item2}, which resolves to {currentObject}."); - return false; - } - - return true; - } - - public (Actor, ObjectIndex, bool) this[Model key] + public (nint, bool) this[nint key] => _drawObjectToGameObject[key]; - public IEnumerable Keys + public IEnumerable Keys => _drawObjectToGameObject.Keys; - public IEnumerable<(Actor, ObjectIndex, bool)> Values + public IEnumerable<(nint, bool)> Values => _drawObjectToGameObject.Values; public unsafe void Dispose() @@ -83,37 +68,6 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary - /// Seems like sometimes the draw object of a game object is destroyed in frames after the original game object is already destroyed. - /// So protect against outdated game object pointers in the dictionary. - /// - private unsafe void OnCharacterDestructor(Character* a) - { - if (a is null) - return; - - var character = (nint)a; - var delete = stackalloc nint[5]; - var current = 0; - foreach (var (drawObject, (gameObject, _, _)) in _drawObjectToGameObject) - { - if (gameObject != character) - continue; - - delete[current++] = drawObject; - if (current is 4) - break; - } - - for (var ptr = delete; *ptr != nint.Zero; ++ptr) - { - _drawObjectToGameObject.Remove(*ptr, out var pair); - Penumbra.Log.Excessive( - $"[DrawObjectState] Removed draw object 0x{*ptr:X} -> 0x{(nint)a:X} (actual: 0x{pair.GameObject.Address:X}, {pair.IsChild})."); - } } private unsafe void OnWeaponReloading(DrawDataContainer* _, Character* character, CharacterWeapon* _2) @@ -131,9 +85,9 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary @@ -149,12 +103,12 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionaryChildObject, gameObject, true, true); if (!iterate) return; diff --git a/Penumbra/Interop/PathResolving/MetaState.cs b/Penumbra/Interop/PathResolving/MetaState.cs index eeae77cc..e7fc3176 100644 --- a/Penumbra/Interop/PathResolving/MetaState.cs +++ b/Penumbra/Interop/PathResolving/MetaState.cs @@ -98,7 +98,7 @@ public sealed unsafe class MetaState : IDisposable, IService _lastCreatedCollection = _collectionResolver.IdentifyLastGameObjectCollection(true); if (_lastCreatedCollection.Valid && _lastCreatedCollection.AssociatedGameObject != nint.Zero) _communicator.CreatingCharacterBase.Invoke(_lastCreatedCollection.AssociatedGameObject, - _lastCreatedCollection.ModCollection.Identity.Id, (nint)modelCharaId, (nint)customize, (nint)equipData); + _lastCreatedCollection.ModCollection.Id, (nint)modelCharaId, (nint)customize, (nint)equipData); var decal = new DecalReverter(Config, _characterUtility, _resources, _lastCreatedCollection, UsesDecal(*(uint*)modelCharaId, (nint)customize)); diff --git a/Penumbra/Interop/PathResolving/PathDataHandler.cs b/Penumbra/Interop/PathResolving/PathDataHandler.cs index e0c235a2..5439151f 100644 --- a/Penumbra/Interop/PathResolving/PathDataHandler.cs +++ b/Penumbra/Interop/PathResolving/PathDataHandler.cs @@ -32,7 +32,7 @@ public static class PathDataHandler /// Create the encoding path for an IMC file. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FullPath CreateImc(CiByteString path, ModCollection collection) - => new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Imc}_{DiscriminatorString}|{path}"); + => new($"|{collection.LocalId.Id}_{collection.ImcChangeCounter}_{DiscriminatorString}|{path}"); /// Create the encoding path for a TMB file. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -47,17 +47,17 @@ public static class PathDataHandler /// Create the encoding path for an ATCH file. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FullPath CreateAtch(CiByteString path, ModCollection collection) - => new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Atch}_{DiscriminatorString}|{path}"); + => new($"|{collection.LocalId.Id}_{collection.AtchChangeCounter}_{DiscriminatorString}|{path}"); /// Create the encoding path for a MTRL file. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FullPath CreateMtrl(CiByteString path, ModCollection collection, Utf8GamePath originalPath) - => new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Change}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}"); + => new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}"); /// The base function shared by most file types. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static FullPath CreateBase(CiByteString path, ModCollection collection) - => new($"|{collection.Identity.LocalId.Id}_{collection.Counters.Change}_{DiscriminatorString}|{path}"); + => new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{DiscriminatorString}|{path}"); /// Read an additional data blurb and parse it into usable data for all file types but Materials. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Penumbra/Interop/PathResolving/PathResolver.cs b/Penumbra/Interop/PathResolving/PathResolver.cs index ec421304..0b6c8340 100644 --- a/Penumbra/Interop/PathResolving/PathResolver.cs +++ b/Penumbra/Interop/PathResolving/PathResolver.cs @@ -1,3 +1,4 @@ +using System.Linq; using FFXIVClientStructs.FFXIV.Client.System.Resource; using OtterGui.Services; using Penumbra.Api.Enums; @@ -48,40 +49,42 @@ public class PathResolver : IDisposable, IService if (!_config.EnableMods) return (null, ResolveData.Invalid); - return resourceType switch - { - // Do not allow manipulating layers to prevent very obvious cheating and softlocks. - ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb => (null, ResolveData.Invalid), - // Prevent .atch loading to prevent crashes on outdated .atch files. - ResourceType.Atch => ResolveAtch(path), - // These are manipulated through Meta Edits instead. - ResourceType.Eqp or ResourceType.Eqdp or ResourceType.Est or ResourceType.Gmp or ResourceType.Cmp => (null, ResolveData.Invalid), + // Do not allow manipulating layers to prevent very obvious cheating and softlocks. + if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb) + return (null, ResolveData.Invalid); - _ => category switch - { - // Only Interface collection. - ResourceCategory.Ui => ResolveUi(path), - // Never allow changing scripts. - ResourceCategory.UiScript => (null, ResolveData.Invalid), - ResourceCategory.GameScript => (null, ResolveData.Invalid), - // Use actual resolving. - ResourceCategory.Chara => Resolve(path, resourceType), - ResourceCategory.Shader => ResolveShader(path, resourceType), - ResourceCategory.Vfx => Resolve(path, resourceType), - ResourceCategory.Sound => Resolve(path, resourceType), - // EXD Modding in general should probably be prohibited but is currently used for fan translations. - // We prevent WebURL specifically because it technically allows launching arbitrary programs / to execute arbitrary code. - ResourceCategory.Exd => path.Path.StartsWith("exd/weburl"u8) ? (null, ResolveData.Invalid) : DefaultResolver(path), - // None of these files are ever associated with specific characters, - // always use the default resolver for now, - // except that common/font is conceptually more UI. - ResourceCategory.Common => path.Path.StartsWith("common/font"u8) ? ResolveUi(path) : DefaultResolver(path), - ResourceCategory.BgCommon => DefaultResolver(path), - ResourceCategory.Bg => DefaultResolver(path), - ResourceCategory.Cut => DefaultResolver(path), - ResourceCategory.Music => DefaultResolver(path), - _ => DefaultResolver(path), - } + // Prevent .atch loading to prevent crashes on outdated .atch files. TODO: handle atch modding differently. + if (resourceType is ResourceType.Atch) + return ResolveAtch(path); + + return category switch + { + // Only Interface collection. + ResourceCategory.Ui => ResolveUi(path), + // Never allow changing scripts. + ResourceCategory.UiScript => (null, ResolveData.Invalid), + ResourceCategory.GameScript => (null, ResolveData.Invalid), + // Use actual resolving. + ResourceCategory.Chara => Resolve(path, resourceType), + ResourceCategory.Shader => ResolveShader(path, resourceType), + ResourceCategory.Vfx => Resolve(path, resourceType), + ResourceCategory.Sound => Resolve(path, resourceType), + // EXD Modding in general should probably be prohibited but is currently used for fan translations. + // We prevent WebURL specifically because it technically allows launching arbitrary programs / to execute arbitrary code. + ResourceCategory.Exd => path.Path.StartsWith("exd/weburl"u8) + ? (null, ResolveData.Invalid) + : DefaultResolver(path), + // None of these files are ever associated with specific characters, + // always use the default resolver for now, + // except that common/font is conceptually more UI. + ResourceCategory.Common => path.Path.StartsWith("common/font"u8) + ? ResolveUi(path) + : DefaultResolver(path), + ResourceCategory.BgCommon => DefaultResolver(path), + ResourceCategory.Bg => DefaultResolver(path), + ResourceCategory.Cut => DefaultResolver(path), + ResourceCategory.Music => DefaultResolver(path), + _ => DefaultResolver(path), }; } diff --git a/Penumbra/Interop/ProcessThreadApi.cs b/Penumbra/Interop/ProcessThreadApi.cs deleted file mode 100644 index 5ee213d9..00000000 --- a/Penumbra/Interop/ProcessThreadApi.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Penumbra.Interop; - -public static partial class ProcessThreadApi -{ - [LibraryImport("kernel32.dll")] - public static partial uint GetCurrentThreadId(); -} diff --git a/Penumbra/Interop/Processing/AtchPathPreProcessor.cs b/Penumbra/Interop/Processing/AtchPathPreProcessor.cs index 428826bc..9a9096f3 100644 --- a/Penumbra/Interop/Processing/AtchPathPreProcessor.cs +++ b/Penumbra/Interop/Processing/AtchPathPreProcessor.cs @@ -20,7 +20,7 @@ public sealed class AtchPathPreProcessor : IPathPreProcessor if (!TryGetAtchGenderRace(path, out var gr)) return resolved; - Penumbra.Log.Excessive($"Pre-Processed {path} with {resolveData.ModCollection} for {gr.ToName()}."); + Penumbra.Log.Information($"Pre-Processed {path} with {resolveData.ModCollection} for {gr.ToName()}."); if (resolveData.ModCollection.MetaCache?.Atch.GetFile(gr, out var file) == true) return PathDataHandler.CreateAtch(path, resolveData.ModCollection); diff --git a/Penumbra/Interop/Processing/FilePostProcessService.cs b/Penumbra/Interop/Processing/FilePostProcessService.cs index 71340178..ecf78c69 100644 --- a/Penumbra/Interop/Processing/FilePostProcessService.cs +++ b/Penumbra/Interop/Processing/FilePostProcessService.cs @@ -4,7 +4,6 @@ using Penumbra.Api.Enums; using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Structs; using Penumbra.String; -using Penumbra.String.Classes; namespace Penumbra.Interop.Processing; @@ -21,20 +20,20 @@ public unsafe class FilePostProcessService : IRequiredService, IDisposable public FilePostProcessService(ResourceLoader resourceLoader, ServiceManager services) { - _resourceLoader = resourceLoader; - _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); - _resourceLoader.BeforeResourceComplete += OnBeforeResourceComplete; + _resourceLoader = resourceLoader; + _processors = services.GetServicesImplementing().ToFrozenDictionary(s => s.Type, s => s); + _resourceLoader.FileLoaded += OnFileLoaded; } public void Dispose() { - _resourceLoader.BeforeResourceComplete -= OnBeforeResourceComplete; + _resourceLoader.FileLoaded -= OnFileLoaded; } - private void OnBeforeResourceComplete(ResourceHandle* resource, CiByteString path, Utf8GamePath original, - ReadOnlySpan additionalData, bool isAsync) + private void OnFileLoaded(ResourceHandle* resource, CiByteString path, bool returnValue, bool custom, + ReadOnlySpan additionalData) { if (_processors.TryGetValue(resource->FileType, out var processor)) - processor.PostProcess(resource, original.Path, additionalData); + processor.PostProcess(resource, path, additionalData); } } diff --git a/Penumbra/Interop/Processing/ImcFilePostProcessor.cs b/Penumbra/Interop/Processing/ImcFilePostProcessor.cs index 949baaa3..a3233cfb 100644 --- a/Penumbra/Interop/Processing/ImcFilePostProcessor.cs +++ b/Penumbra/Interop/Processing/ImcFilePostProcessor.cs @@ -1,3 +1,4 @@ +using Dalamud.Game.ClientState.JobGauge.Types; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; @@ -25,6 +26,6 @@ public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFileP file.Replace(resource); Penumbra.Log.Verbose( - $"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.Identity.AnonymizedName}."); + $"[ResourceLoader] Loaded {originalGamePath} from file and replaced with IMC from collection {collection.AnonymizedName}."); } } diff --git a/Penumbra/Interop/Processing/PbdFilePostProcessor.cs b/Penumbra/Interop/Processing/PbdFilePostProcessor.cs deleted file mode 100644 index 674500cd..00000000 --- a/Penumbra/Interop/Processing/PbdFilePostProcessor.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Dalamud.Game; -using Dalamud.Plugin.Services; -using Penumbra.Api.Enums; -using Penumbra.GameData; -using Penumbra.GameData.Data; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; -using Penumbra.String; - -namespace Penumbra.Interop.Processing; - -public sealed class PbdFilePostProcessor : IFilePostProcessor -{ - private readonly IFileAllocator _allocator; - private byte[] _epbdData; - private unsafe delegate* unmanaged _loadEpbdData; - - public ResourceType Type - => ResourceType.Pbd; - - public unsafe PbdFilePostProcessor(IDataManager dataManager, XivFileAllocator allocator, ISigScanner scanner) - { - _allocator = allocator; - _epbdData = SetEpbdData(dataManager); - _loadEpbdData = (delegate* unmanaged)scanner.ScanText(Sigs.LoadEpbdData); - } - - public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan additionalData) - { - if (_epbdData.Length is 0) - return; - - if (resource->LoadState is not LoadState.Success) - { - Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {resource->FileName()} failed load ({resource->LoadState})."); - return; - } - - var (data, length) = resource->GetData(); - if (length is 0 || data == nint.Zero) - { - Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {resource->FileName()} succeeded load but has no data."); - return; - } - - var span = new ReadOnlySpan((void*)data, (int)resource->FileSize); - var reader = new PackReader(span); - if (reader.HasData) - { - Penumbra.Log.Excessive($"[ResourceLoader] Successfully loaded PBD at {resource->FileName()} with EPBD data."); - return; - } - - var newData = AppendData(span); - fixed (byte* ptr = newData) - { - // Set the appended data and the actual file size, then re-load the EPBD data via game function call. - if (resource->SetData((nint)ptr, newData.Length)) - { - resource->FileSize = (uint)newData.Length; - resource->CsHandle.FileSize2 = (uint)newData.Length; - resource->CsHandle.FileSize3 = (uint)newData.Length; - _loadEpbdData(resource); - // Free original data. - _allocator.Release((void*)data, length); - Penumbra.Log.Debug($"[ResourceLoader] Loaded {resource->FileName()} from file and appended default EPBD data."); - } - else - { - Penumbra.Log.Warning( - $"[ResourceLoader] Failed to append EPBD data to custom PBD at {resource->FileName()}."); - } - } - } - - /// Combine the given data with the default PBD data using the game's file allocator. - private unsafe ReadOnlySpan AppendData(ReadOnlySpan data) - { - // offset has to be set, otherwise not called. - var newLength = data.Length + _epbdData.Length; - var memory = _allocator.Allocate(newLength); - var span = new Span(memory, newLength); - data.CopyTo(span); - _epbdData.CopyTo(span[data.Length..]); - return span; - } - - /// Fetch the default EPBD data from the .pbd file of the game's installation. - private static byte[] SetEpbdData(IDataManager dataManager) - { - try - { - var file = dataManager.GetFile(GamePaths.Pbd.Path); - if (file is null || file.Data.Length is 0) - { - Penumbra.Log.Warning("Default PBD file has no data."); - return []; - } - - ReadOnlySpan span = file.Data; - var reader = new PackReader(span); - if (!reader.HasData) - { - Penumbra.Log.Warning("Default PBD file has no EPBD section."); - return []; - } - - var offset = span.Length - (int)reader.PackLength; - var ret = span[offset..]; - Penumbra.Log.Verbose($"Default PBD file has EPBD section of length {ret.Length} at offset {offset}."); - return ret.ToArray(); - } - catch (Exception ex) - { - Penumbra.Log.Error($"Unknown error getting default EPBD data:\n{ex}"); - return []; - } - } -} diff --git a/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs b/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs index ddd59121..2fb35ae0 100644 --- a/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs +++ b/Penumbra/Interop/Processing/ShpkPathPreProcessor.cs @@ -49,15 +49,15 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager, return null; } - internal static SanityCheckResult SanityCheck(string path) + private static SanityCheckResult SanityCheck(string path) { try { using var file = MmioMemoryManager.CreateFromFile(path, access: MemoryMappedFileAccess.Read); var bytes = file.GetSpan(); - return ShpkFile.FastIsObsolete(bytes) - ? SanityCheckResult.Obsolete + return ShpkFile.FastIsLegacy(bytes) + ? SanityCheckResult.Legacy : SanityCheckResult.Success; } catch (FileNotFoundException) @@ -75,15 +75,15 @@ public sealed class ShpkPathPreProcessor(ResourceManagerService resourceManager, { SanityCheckResult.IoError => "Cannot read the modded file.", SanityCheckResult.NotFound => "The modded file does not exist.", - SanityCheckResult.Obsolete => "This mod is not compatible with Dawntrail post patch 7.2. Get an updated version, if possible, or disable it.", + SanityCheckResult.Legacy => "This mod is not compatible with Dawntrail. Get an updated version, if possible, or disable it.", _ => string.Empty, }; - internal enum SanityCheckResult + private enum SanityCheckResult { Success, IoError, NotFound, - Obsolete, + Legacy, } } diff --git a/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs b/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs deleted file mode 100644 index bd066d83..00000000 --- a/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs +++ /dev/null @@ -1,63 +0,0 @@ -using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; - -namespace Penumbra.Interop.Processing; - -public static unsafe class SkinMtrlPathEarlyProcessing -{ - public static void Process(Span path, CharacterBase* character, uint slotIndex) - { - var end = path.IndexOf(MaterialExtension()); - if (end < 0) - return; - - var suffixPos = path[..end].LastIndexOf((byte)'_'); - if (suffixPos < 0) - return; - - var handle = GetModelResourceHandle(character, slotIndex); - if (handle == null) - return; - - var skinSuffix = GetSkinSuffix(handle); - if (skinSuffix.IsEmpty || skinSuffix.Length > path.Length - suffixPos - 7) - return; - - ++suffixPos; - skinSuffix.CopyTo(path[suffixPos..]); - suffixPos += skinSuffix.Length; - MaterialExtension().CopyTo(path[suffixPos..]); - return; - - static ReadOnlySpan MaterialExtension() - => ".mtrl\0"u8; - } - - private static ModelResourceHandle* GetModelResourceHandle(CharacterBase* character, uint slotIndex) - { - if (character is null) - return null; - - if (character->PerSlotStagingArea is not null) - { - var handle = character->PerSlotStagingArea[slotIndex].ModelResourceHandle; - if (handle != null) - return handle; - } - - var model = character->Models[slotIndex]; - return model is null ? null : model->ModelResourceHandle; - } - - private static ReadOnlySpan GetSkinSuffix(ModelResourceHandle* handle) - { - foreach (var (attribute, _) in handle->Attributes) - { - var attributeSpan = attribute.AsSpan(); - if (attributeSpan.Length > 12 && attributeSpan[..11].SequenceEqual("skin_suffix"u8) && attributeSpan[11] is (byte)'=' or (byte)'_') - return attributeSpan[12..]; - } - - return []; - } -} diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index c204f141..0c36b745 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -22,13 +22,6 @@ internal partial record ResolveContext private static bool IsEquipmentSlot(uint slotIndex) => slotIndex is < 5 or 16 or 17; - private unsafe Variant Variant - => ModelType switch - { - ModelType.Monster => (byte)((Monster*)CharacterBase)->Variant, - _ => Equipment.Variant, - }; - private Utf8GamePath ResolveModelPath() { // Correctness: @@ -43,8 +36,8 @@ internal partial record ResolveContext private Utf8GamePath ResolveEquipmentModelPath() { var path = IsEquipmentSlot(SlotIndex) - ? GamePaths.Mdl.Equipment(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot()) - : GamePaths.Mdl.Accessory(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot()); + ? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot()) + : GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot()); return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } @@ -99,7 +92,7 @@ internal partial record ResolveContext => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imc, mtrlFileName), - ModelType.Monster => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), + ModelType.Monster => ResolveMonsterMaterialPath(modelPath, imc, mtrlFileName), _ => ResolveMaterialPathNative(mtrlFileName), }; } @@ -107,7 +100,7 @@ internal partial record ResolveContext [SkipLocalsInit] private unsafe Utf8GamePath ResolveEquipmentMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName) { - var variant = ResolveImcData(imc).MaterialId; + var variant = ResolveMaterialVariant(imc, Equipment.Variant); var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); Span pathBuffer = stackalloc byte[CharaBase.PathBufferSize]; @@ -122,12 +115,12 @@ internal partial record ResolveContext var setIdHigh = Equipment.Set.Id / 100; // All MCH (20??) weapons' materials C are one and the same if (setIdHigh is 20 && mtrlFileName[14] == (byte)'c') - return Utf8GamePath.FromString(GamePaths.Mtrl.Weapon(2001, 1, 1, "c"), out var path) ? path : Utf8GamePath.Empty; + return Utf8GamePath.FromString(GamePaths.Weapon.Mtrl.Path(2001, 1, 1, "c"), out var path) ? path : Utf8GamePath.Empty; // Some offhands share materials with the corresponding mainhand - if (ItemData.AdaptOffhandImc(Equipment.Set, out var mirroredSetId)) + if (ItemData.AdaptOffhandImc(Equipment.Set.Id, out var mirroredSetId)) { - var variant = ResolveImcData(imc).MaterialId; + var variant = ResolveMaterialVariant(imc, Equipment.Variant); var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); Span mirroredFileName = stackalloc byte[32]; @@ -148,16 +141,31 @@ internal partial record ResolveContext return ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName); } - private unsafe ImcEntry ResolveImcData(ResourceHandle* imc) + private unsafe Utf8GamePath ResolveMonsterMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName) + { + var variant = ResolveMaterialVariant(imc, (byte)((Monster*)CharacterBase)->Variant); + var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); + + Span pathBuffer = stackalloc byte[CharaBase.PathBufferSize]; + pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName); + + return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty; + } + + private unsafe byte ResolveMaterialVariant(ResourceHandle* imc, Variant variant) { var imcFileData = imc->GetDataSpan(); if (imcFileData.IsEmpty) { Penumbra.Log.Warning($"IMC resource handle with path {imc->FileName.AsByteString()} doesn't have a valid data span"); - return default; + return variant.Id; } - return ImcFile.GetEntry(imcFileData, SlotIndex.ToEquipSlot(), Variant, out _); + var entry = ImcFile.GetEntry(imcFileData, Slot.ToSlot(), variant, out var exists); + if (!exists) + return variant.Id; + + return entry.MaterialId; } private static Span AssembleMaterialPath(Span materialPathBuffer, ReadOnlySpan modelPath, byte variant, @@ -230,7 +238,7 @@ internal partial record ResolveContext if (set == 0) return Utf8GamePath.Empty; - var path = GamePaths.Sklb.Customization(raceCode, slot, set); + var path = GamePaths.Skeleton.Sklb.Path(raceCode, slot, set); return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } @@ -248,7 +256,7 @@ internal partial record ResolveContext if (faceId < 201) faceId -= tribe switch { - 0xB when modelType is 4 => 100, + 0xB when modelType == 4 => 100, 0xE | 0xF => 100, _ => 0, }; @@ -297,10 +305,10 @@ internal partial record ResolveContext private Utf8GamePath ResolveHumanSkeletonParameterPath(uint partialSkeletonIndex) { var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); - if (set.Id is 0) + if (set == 0) return Utf8GamePath.Empty; - var path = GamePaths.Skp.Customization(raceCode, slot, set); + var path = GamePaths.Skeleton.Skp.Path(raceCode, slot, set); return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } @@ -309,80 +317,4 @@ internal partial record ResolveContext var path = CharacterBase->ResolveSkpPathAsByteString(partialSkeletonIndex); return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } - - private Utf8GamePath ResolvePhysicsModulePath(uint partialSkeletonIndex) - { - // Correctness and Safety: - // Resolving a physics module path through the game's code can use EST metadata for human skeletons. - // Additionally, it can dereference null pointers for human equipment skeletons. - return ModelType switch - { - ModelType.Human => ResolveHumanPhysicsModulePath(partialSkeletonIndex), - _ => ResolvePhysicsModulePathNative(partialSkeletonIndex), - }; - } - - private Utf8GamePath ResolveHumanPhysicsModulePath(uint partialSkeletonIndex) - { - var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); - if (set.Id is 0) - return Utf8GamePath.Empty; - - var path = GamePaths.Phyb.Customization(raceCode, slot, set); - return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; - } - - private unsafe Utf8GamePath ResolvePhysicsModulePathNative(uint partialSkeletonIndex) - { - var path = CharacterBase->ResolvePhybPathAsByteString(partialSkeletonIndex); - return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; - } - - private Utf8GamePath ResolveKineDriverModulePath(uint partialSkeletonIndex) - { - // Correctness and Safety: - // Resolving a KineDriver module path through the game's code can use EST metadata for human skeletons. - // Additionally, it can dereference null pointers for human equipment skeletons. - return ModelType switch - { - ModelType.Human => ResolveHumanKineDriverModulePath(partialSkeletonIndex), - _ => ResolveKineDriverModulePathNative(partialSkeletonIndex), - }; - } - - private Utf8GamePath ResolveHumanKineDriverModulePath(uint partialSkeletonIndex) - { - var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); - if (set.Id is 0) - return Utf8GamePath.Empty; - - var path = GamePaths.Kdb.Customization(raceCode, slot, set); - return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; - } - - private unsafe Utf8GamePath ResolveKineDriverModulePathNative(uint partialSkeletonIndex) - { - var path = CharacterBase->ResolveKdbPathAsByteString(partialSkeletonIndex); - return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; - } - - private unsafe Utf8GamePath ResolveMaterialAnimationPath(ResourceHandle* imc) - { - var animation = ResolveImcData(imc).MaterialAnimationId; - if (animation is 0) - return Utf8GamePath.Empty; - - var path = CharacterBase->ResolveMaterialPapPathAsByteString(SlotIndex, animation); - return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; - } - - private unsafe Utf8GamePath ResolveDecalPath(ResourceHandle* imc) - { - var decal = ResolveImcData(imc).DecalId; - if (decal is 0) - return Utf8GamePath.Empty; - - var path = GamePaths.Tex.EquipDecal(decal); - 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 501bbc56..54612070 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -2,7 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; -using OtterGui.Extensions; +using OtterGui; using OtterGui.Text.HelperObjects; using Penumbra.Api.Enums; using Penumbra.Collections; @@ -52,20 +52,20 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, CiByteString gamePath) { - if (resourceHandle is null) + if (resourceHandle == null) return null; if (gamePath.IsEmpty) return null; if (!Utf8GamePath.FromByteString(CiByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path)) return null; - return GetOrCreateNode(ResourceType.Shpk, (nint)resourceHandle->ShaderPackage, (ResourceHandle*)resourceHandle, path); + return GetOrCreateNode(ResourceType.Shpk, (nint)resourceHandle->ShaderPackage, &resourceHandle->ResourceHandle, path); } [SkipLocalsInit] private ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, CiByteString gamePath, bool dx11) { - if (resourceHandle is null) + if (resourceHandle == null) return null; Utf8GamePath path; @@ -105,7 +105,7 @@ internal unsafe partial record ResolveContext( private ResourceNode GetOrCreateNode(ResourceType type, nint objectAddress, ResourceHandle* resourceHandle, Utf8GamePath gamePath) { - if (resourceHandle is null) + if (resourceHandle == null) throw new ArgumentNullException(nameof(resourceHandle)); if (Global.Nodes.TryGetValue((gamePath, (nint)resourceHandle), out var cached)) @@ -117,7 +117,7 @@ internal unsafe partial record ResolveContext( private ResourceNode CreateNode(ResourceType type, nint objectAddress, ResourceHandle* resourceHandle, Utf8GamePath gamePath, bool autoAdd = true) { - if (resourceHandle is null) + if (resourceHandle == null) throw new ArgumentNullException(nameof(resourceHandle)); var fileName = (ReadOnlySpan)resourceHandle->FileName.AsSpan(); @@ -141,7 +141,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromEid(ResourceHandle* eid) { - if (eid is null) + if (eid == null) return null; if (!Utf8GamePath.FromByteString(CharacterBase->ResolveEidPathAsByteString(), out var path)) @@ -152,7 +152,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromImc(ResourceHandle* imc) { - if (imc is null) + if (imc == null) return null; if (!Utf8GamePath.FromByteString(CharacterBase->ResolveImcPathAsByteString(SlotIndex), out var path)) @@ -163,7 +163,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromPbd(ResourceHandle* pbd) { - if (pbd is null) + if (pbd == null) return null; return GetOrCreateNode(ResourceType.Pbd, 0, pbd, PreBoneDeformerReplacer.PreBoneDeformerPath); @@ -171,7 +171,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, string gamePath) { - if (tex is null) + if (tex == null) return null; if (!Utf8GamePath.FromString(gamePath, out var path)) @@ -180,18 +180,9 @@ internal unsafe partial record ResolveContext( return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, path); } - public ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, Utf8GamePath gamePath) + public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc) { - if (tex is null) - return null; - - return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, gamePath); - } - - public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc, TextureResourceHandle* decalHandle, - MaterialResourceHandle* skinMtrlHandle, ResourceHandle* mpapHandle) - { - if (mdl is null || mdl->ModelResourceHandle is null) + if (mdl == null || mdl->ModelResourceHandle == null) return null; var mdlResource = mdl->ModelResourceHandle; @@ -206,12 +197,12 @@ internal unsafe partial record ResolveContext( for (var i = 0; i < mdl->MaterialCount; i++) { var mtrl = mdl->Materials[i]; - if (mtrl is null) + if (mtrl == null) continue; var mtrlFileName = mdlResource->GetMaterialFileNameBySlot((uint)i); var mtrlNode = CreateNodeFromMaterial(mtrl, ResolveMaterialPath(path, imc, mtrlFileName)); - if (mtrlNode is not null) + if (mtrlNode != null) { if (Global.WithUiData) mtrlNode.FallbackName = $"Material #{i}"; @@ -219,18 +210,6 @@ internal unsafe partial record ResolveContext( } } - if (skinMtrlHandle is not null - && Utf8GamePath.FromByteString(CharacterBase->ResolveSkinMtrlPathAsByteString(SlotIndex), out var skinMtrlPath) - && CreateNodeFromMaterial(skinMtrlHandle->Material, skinMtrlPath) is - { } skinMaaterialNode) - node.Children.Add(skinMaaterialNode); - - if (CreateNodeFromDecal(decalHandle, imc) is { } decalNode) - node.Children.Add(decalNode); - - if (CreateNodeFromMaterialPap(mpapHandle, imc) is { } mpapNode) - node.Children.Add(mpapNode); - Global.Nodes.Add((path, (nint)mdl->ModelResourceHandle), node); return node; @@ -238,29 +217,29 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateNodeFromMaterial(Material* mtrl, Utf8GamePath path) { - if (mtrl is null || mtrl->MaterialResourceHandle is null) + if (mtrl == null || mtrl->MaterialResourceHandle == null) return null; var resource = mtrl->MaterialResourceHandle; if (Global.Nodes.TryGetValue((path, (nint)resource), out var cached)) return cached; - var node = CreateNode(ResourceType.Mtrl, (nint)mtrl, (ResourceHandle*)resource, path, false); - var shpkNode = CreateNodeFromShpk(resource->ShaderPackageResourceHandle, new CiByteString(resource->ShpkName.Value)); - if (shpkNode is not null) + var node = CreateNode(ResourceType.Mtrl, (nint)mtrl, &resource->ResourceHandle, path, false); + var shpkNode = CreateNodeFromShpk(resource->ShaderPackageResourceHandle, new CiByteString(resource->ShpkName)); + if (shpkNode != null) { if (Global.WithUiData) shpkNode.Name = "Shader Package"; node.Children.Add(shpkNode); } - var shpkNames = Global.WithUiData && shpkNode is not null ? Global.TreeBuildCache.ReadShaderPackageNames(shpkNode.FullPath) : null; - var shpk = Global.WithUiData && shpkNode is not null ? (ShaderPackage*)shpkNode.ObjectAddress : null; + var shpkNames = Global.WithUiData && shpkNode != null ? Global.TreeBuildCache.ReadShaderPackageNames(shpkNode.FullPath) : null; + var shpk = Global.WithUiData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null; var alreadyProcessedSamplerIds = new HashSet(); for (var i = 0; i < resource->TextureCount; i++) { - var texNode = CreateNodeFromTex(resource->Textures[i].TextureResourceHandle, new CiByteString(resource->TexturePath(i).Value), + var texNode = CreateNodeFromTex(resource->Textures[i].TextureResourceHandle, new CiByteString(resource->TexturePath(i)), resource->Textures[i].IsDX11); if (texNode == null) continue; @@ -268,7 +247,7 @@ internal unsafe partial record ResolveContext( if (Global.WithUiData) { string? name = null; - if (shpk is not null) + if (shpk != null) { var index = GetTextureIndex(mtrl, resource->Textures[i].Flags, alreadyProcessedSamplerIds); var samplerId = index != 0x001F @@ -277,13 +256,13 @@ internal unsafe partial record ResolveContext( if (samplerId.HasValue) { alreadyProcessedSamplerIds.Add(samplerId.Value); - var textureCrc = GetTextureCrcById(shpk, samplerId.Value); - if (textureCrc.HasValue) + var samplerCrc = GetSamplerCrcById(shpk, samplerId.Value); + if (samplerCrc.HasValue) { - if (shpkNames != null && shpkNames.TryGetValue(textureCrc.Value, out var samplerName)) + if (shpkNames != null && shpkNames.TryGetValue(samplerCrc.Value, out var samplerName)) name = samplerName.Value; else - name = $"Texture 0x{textureCrc.Value:X8}"; + name = $"Texture 0x{samplerCrc.Value:X8}"; } } } @@ -299,9 +278,9 @@ internal unsafe partial record ResolveContext( return node; - static uint? GetTextureCrcById(ShaderPackage* shpk, uint id) - => shpk->TexturesSpan.FindFirst(t => t.Id == id, out var t) - ? t.CRC + static uint? GetSamplerCrcById(ShaderPackage* shpk, uint id) + => shpk->SamplersSpan.FindFirst(s => s.Id == id, out var s) + ? s.CRC : null; static uint? GetTextureSamplerId(Material* mtrl, TextureResourceHandle* handle, HashSet alreadyVisitedSamplerIds) @@ -322,59 +301,9 @@ internal unsafe partial record ResolveContext( } } - public ResourceNode? CreateNodeFromDecal(TextureResourceHandle* decalHandle, ResourceHandle* imc) + public ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) { - if (decalHandle is null) - return null; - - var path = ResolveDecalPath(imc); - if (path.IsEmpty) - return null; - - var node = CreateNodeFromTex(decalHandle, path)!; - if (Global.WithUiData) - node.FallbackName = "Decal"; - - return node; - } - - public ResourceNode? CreateNodeFromMaterialPap(ResourceHandle* mpapHandle, ResourceHandle* imc) - { - if (mpapHandle is null) - return null; - - var path = ResolveMaterialAnimationPath(imc); - if (path.IsEmpty) - return null; - - if (Global.Nodes.TryGetValue((path, (nint)mpapHandle), out var cached)) - return cached; - - var node = CreateNode(ResourceType.Pap, 0, mpapHandle, path); - if (Global.WithUiData) - node.FallbackName = "Material Animation"; - - return node; - } - - public ResourceNode? CreateNodeFromMaterialSklb(SkeletonResourceHandle* sklbHandle) - { - if (sklbHandle is null) - return null; - - if (Global.Nodes.TryGetValue((GamePaths.Sklb.MaterialAnimationSkeletonUtf8, (nint)sklbHandle), out var cached)) - return cached; - - var node = CreateNode(ResourceType.Sklb, 0, (ResourceHandle*)sklbHandle, GamePaths.Sklb.MaterialAnimationSkeletonUtf8); - node.ForceInternal = true; - - return node; - } - - public ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, ResourceHandle* phybHandle, ResourceHandle* kdbHandle, - uint partialSkeletonIndex) - { - if (sklb is null || sklb->SkeletonResourceHandle is null) + if (sklb == null || sklb->SkeletonResourceHandle == null) return null; var path = ResolveSkeletonPath(partialSkeletonIndex); @@ -382,13 +311,10 @@ internal unsafe partial record ResolveContext( if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonResourceHandle), out var cached)) return cached; - var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false); - if (CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex) is { } skpNode) + var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false); + var skpNode = CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex); + if (skpNode != null) node.Children.Add(skpNode); - if (CreateNodeFromPhyb(phybHandle, partialSkeletonIndex) is { } phybNode) - node.Children.Add(phybNode); - if (CreateNodeFromKdb(kdbHandle, partialSkeletonIndex) is { } kdbNode) - node.Children.Add(kdbNode); Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node); return node; @@ -396,7 +322,7 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) { - if (sklb is null || sklb->SkeletonParameterResourceHandle is null) + if (sklb == null || sklb->SkeletonParameterResourceHandle == null) return null; var path = ResolveSkeletonParameterPath(partialSkeletonIndex); @@ -412,49 +338,11 @@ internal unsafe partial record ResolveContext( return node; } - private ResourceNode? CreateNodeFromPhyb(ResourceHandle* phybHandle, uint partialSkeletonIndex) - { - if (phybHandle is null) - return null; - - var path = ResolvePhysicsModulePath(partialSkeletonIndex); - - if (Global.Nodes.TryGetValue((path, (nint)phybHandle), out var cached)) - return cached; - - var node = CreateNode(ResourceType.Phyb, 0, phybHandle, path, false); - if (Global.WithUiData) - node.FallbackName = "Physics Module"; - Global.Nodes.Add((path, (nint)phybHandle), node); - - return node; - } - - private ResourceNode? CreateNodeFromKdb(ResourceHandle* kdbHandle, uint partialSkeletonIndex) - { - if (kdbHandle is null) - return null; - - var path = ResolveKineDriverModulePath(partialSkeletonIndex); - - if (Global.Nodes.TryGetValue((path, (nint)kdbHandle), out var cached)) - return cached; - - var node = CreateNode(ResourceType.Kdb, 0, kdbHandle, path, false); - if (Global.WithUiData) - node.FallbackName = "KineDriver Module"; - Global.Nodes.Add((path, (nint)kdbHandle), node); - - return node; - } - internal ResourceNode.UiData GuessModelUiData(Utf8GamePath gamePath) { var path = gamePath.Path.Split((byte)'/'); // Weapons intentionally left out. - var isEquipment = path.Count >= 2 - && path[0].Span.SequenceEqual("chara"u8) - && (path[1].Span.SequenceEqual("accessory"u8) || path[1].Span.SequenceEqual("equipment"u8)); + 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())) { @@ -470,7 +358,7 @@ internal unsafe partial record ResolveContext( } var dataFromPath = GuessUiDataFromPath(gamePath); - if (dataFromPath.Name is not null) + if (dataFromPath.Name != null) return dataFromPath; return isEquipment @@ -480,19 +368,29 @@ internal unsafe partial record ResolveContext( internal ResourceNode.UiData GuessUiDataFromPath(Utf8GamePath gamePath) { - const string customization = "Customization: "; foreach (var obj in Global.Identifier.Identify(gamePath.ToString())) { var name = obj.Key; - if (name.StartsWith(customization)) - name = name.AsSpan(14).Trim().ToString(); - if (name is not "Unknown") + if (obj.Value is IdentifiedCustomization) + name = name[14..].Trim(); + if (name != "Unknown") return new ResourceNode.UiData(name, obj.Value.GetIcon().ToFlag()); } return new ResourceNode.UiData(null, ChangedItemIconFlag.Unknown); } + private static string? SafeGet(ReadOnlySpan array, Index index) + { + var i = index.GetOffset(array.Length); + return i >= 0 && i < array.Length ? array[i] : null; + } + private static ulong GetResourceHandleLength(ResourceHandle* handle) - => handle is null ? 0ul : handle->GetLength(); + { + if (handle == null) + return 0; + + return handle->GetLength(); + } } diff --git a/Penumbra/Interop/ResourceTree/ResourceNode.cs b/Penumbra/Interop/ResourceTree/ResourceNode.cs index 08dee818..6c3e1ebe 100644 --- a/Penumbra/Interop/ResourceTree/ResourceNode.cs +++ b/Penumbra/Interop/ResourceTree/ResourceNode.cs @@ -1,5 +1,4 @@ using Penumbra.Api.Enums; -using Penumbra.Mods; using Penumbra.String; using Penumbra.String.Classes; using Penumbra.UI; @@ -16,11 +15,7 @@ public class ResourceNode : ICloneable public readonly nint ResourceHandle; public Utf8GamePath[] PossibleGamePaths; public FullPath FullPath; - public PathStatus FullPathStatus; - public bool ForceInternal; - public bool ForceProtected; public string? ModName; - public readonly WeakReference Mod = new(null!); public string? ModRelativePath; public CiByteString AdditionalData; public readonly ulong Length; @@ -39,15 +34,8 @@ public class ResourceNode : ICloneable } } - /// Whether to treat the file as internal (hide from user unless debug mode is on). public bool Internal - => ForceInternal || Type is ResourceType.Eid or ResourceType.Imc; - - /// Whether to treat the file as protected (require holding the Mod Deletion Modifier to make a quick import). - public bool Protected - => ForceProtected - || Internal - || Type is ResourceType.Shpk or ResourceType.Sklb or ResourceType.Skp or ResourceType.Phyb or ResourceType.Kdb or ResourceType.Pbd; + => Type is ResourceType.Eid or ResourceType.Imc; internal ResourceNode(ResourceType type, nint objectAddress, nint resourceHandle, ulong length, ResolveContext? resolveContext) { @@ -71,13 +59,9 @@ public class ResourceNode : ICloneable ResourceHandle = other.ResourceHandle; PossibleGamePaths = other.PossibleGamePaths; FullPath = other.FullPath; - FullPathStatus = other.FullPathStatus; ModName = other.ModName; - Mod = other.Mod; ModRelativePath = other.ModRelativePath; AdditionalData = other.AdditionalData; - ForceInternal = other.ForceInternal; - ForceProtected = other.ForceProtected; Length = other.Length; Children = other.Children; ResolveContext = other.ResolveContext; @@ -111,13 +95,6 @@ public class ResourceNode : ICloneable public readonly record struct UiData(string? Name, ChangedItemIconFlag IconFlag) { public UiData PrependName(string prefix) - => Name == null ? this : this with { Name = prefix + Name }; - } - - public enum PathStatus : byte - { - Valid, - NonExistent, - External, + => Name == null ? this : new UiData(prefix + Name, IconFlag); } } diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 1ebfe53d..b50fc695 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -1,9 +1,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; -using FFXIVClientStructs.FFXIV.Client.Graphics.Physics; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; -using FFXIVClientStructs.Interop; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -12,39 +10,45 @@ using Penumbra.UI; using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData; using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType; -using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; namespace Penumbra.Interop.ResourceTree; -public class ResourceTree( - string name, - string anonymizedName, - int gameObjectIndex, - nint gameObjectAddress, - nint drawObjectAddress, - bool localPlayerRelated, - bool playerRelated, - bool networked, - string collectionName, - string anonymizedCollectionName) +public class ResourceTree { - public readonly string Name = name; - public readonly string AnonymizedName = anonymizedName; - public readonly int GameObjectIndex = gameObjectIndex; - public readonly nint GameObjectAddress = gameObjectAddress; - public readonly nint DrawObjectAddress = drawObjectAddress; - public readonly bool LocalPlayerRelated = localPlayerRelated; - public readonly bool PlayerRelated = playerRelated; - public readonly bool Networked = networked; - public readonly string CollectionName = collectionName; - public readonly string AnonymizedCollectionName = anonymizedCollectionName; - public readonly List Nodes = []; - public readonly HashSet FlatNodes = []; + public readonly string Name; + public readonly string AnonymizedName; + public readonly int GameObjectIndex; + public readonly nint GameObjectAddress; + public readonly nint DrawObjectAddress; + public readonly bool LocalPlayerRelated; + public readonly bool PlayerRelated; + public readonly bool Networked; + public readonly string CollectionName; + public readonly string AnonymizedCollectionName; + public readonly List Nodes; + public readonly HashSet FlatNodes; public int ModelId; public CustomizeData CustomizeData; public GenderRace RaceCode; + public ResourceTree(string name, string anonymizedName, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, + bool localPlayerRelated, bool playerRelated, bool networked, string collectionName, string anonymizedCollectionName) + { + Name = name; + AnonymizedName = anonymizedName; + GameObjectIndex = gameObjectIndex; + GameObjectAddress = gameObjectAddress; + DrawObjectAddress = drawObjectAddress; + LocalPlayerRelated = localPlayerRelated; + Networked = networked; + PlayerRelated = playerRelated; + CollectionName = collectionName; + AnonymizedCollectionName = anonymizedCollectionName; + Nodes = []; + FlatNodes = []; + } + public void ProcessPostfix(Action action) { foreach (var node in Nodes) @@ -66,26 +70,10 @@ public class ResourceTree( }; ModelId = character->ModelContainer.ModelCharaId; CustomizeData = character->DrawData.CustomizeData; - RaceCode = human is not null ? (GenderRace)human->RaceSexId : GenderRace.Unknown; + RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown; var genericContext = globalContext.CreateContext(model); - var mpapArrayPtr = model->MaterialAnimationPacks; - var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; - var skinMtrlArray = modelType switch - { - ModelType.Human => ((Human*) model)->SlotSkinMaterials, - _ => [], - }; - var decalArray = modelType switch - { - ModelType.Human => human->SlotDecals, - ModelType.DemiHuman => ((Demihuman*)model)->SlotDecals, - ModelType.Weapon => [((Weapon*)model)->Decal], - ModelType.Monster => [((Monster*)model)->Decal], - _ => [], - }; - for (var i = 0u; i < model->SlotCount; ++i) { var slotContext = modelType switch @@ -102,18 +90,18 @@ public class ResourceTree( : globalContext.CreateContext(model, i), }; - var imc = (ResourceHandle*)model->IMCArray[i]; - if (slotContext.CreateNodeFromImc(imc) is { } imcNode) + var imc = (ResourceHandle*)model->IMCArray[i]; + var imcNode = slotContext.CreateNodeFromImc(imc); + if (imcNode != null) { if (globalContext.WithUiData) imcNode.FallbackName = $"IMC #{i}"; Nodes.Add(imcNode); } - var mdl = model->Models[i]; - if (slotContext.CreateNodeFromModel(mdl, imc, i < decalArray.Length ? decalArray[(int)i].Value : null, - i < skinMtrlArray.Length ? skinMtrlArray[(int)i].Value : null, i < mpapArray.Length ? mpapArray[(int)i].Value : null) is - { } mdlNode) + var mdl = model->Models[i]; + var mdlNode = slotContext.CreateNodeFromModel(mdl, imc); + if (mdlNode != null) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Model #{i}"; @@ -121,12 +109,11 @@ public class ResourceTree( } } - AddSkeleton(Nodes, genericContext, model); - AddMaterialAnimationSkeleton(Nodes, genericContext, model->MaterialAnimationSkeleton); + AddSkeleton(Nodes, genericContext, model->EID, model->Skeleton); AddWeapons(globalContext, model); - if (human is not null) + if (human != null) AddHumanResources(globalContext, human); } @@ -136,12 +123,12 @@ public class ResourceTree( var weaponNodes = new List(); foreach (var baseSubObject in model->DrawObject.Object.ChildObjects) { - if (baseSubObject->GetObjectType() is not FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase) + if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase) continue; var subObject = (CharacterBase*)baseSubObject; - if (subObject->GetModelType() is not ModelType.Weapon) + if (subObject->GetModelType() != ModelType.Weapon) continue; var weapon = (Weapon*)subObject; @@ -153,24 +140,22 @@ public class ResourceTree( var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType); - var mpapArrayPtr = subObject->MaterialAnimationPacks; - var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, subObject->SlotCount) : []; - for (var i = 0; i < subObject->SlotCount; ++i) { var slotContext = globalContext.CreateContext(subObject, (uint)i, slot, equipment, weaponType); - var imc = (ResourceHandle*)subObject->IMCArray[i]; - if (slotContext.CreateNodeFromImc(imc) is { } imcNode) + var imc = (ResourceHandle*)subObject->IMCArray[i]; + var imcNode = slotContext.CreateNodeFromImc(imc); + if (imcNode != null) { if (globalContext.WithUiData) imcNode.FallbackName = $"Weapon #{weaponIndex}, IMC #{i}"; weaponNodes.Add(imcNode); } - var mdl = subObject->Models[i]; - if (slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, null, i < mpapArray.Length ? mpapArray[i].Value : null) is - { } mdlNode) + var mdl = subObject->Models[i]; + var mdlNode = slotContext.CreateNodeFromModel(mdl, imc); + if (mdlNode != null) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Weapon #{weaponIndex}, Model #{i}"; @@ -178,9 +163,7 @@ public class ResourceTree( } } - AddSkeleton(weaponNodes, genericContext, subObject, $"Weapon #{weaponIndex}, "); - AddMaterialAnimationSkeleton(weaponNodes, genericContext, subObject->MaterialAnimationSkeleton, - $"Weapon #{weaponIndex}, "); + AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, $"Weapon #{weaponIndex}, "); ++weaponIndex; } @@ -193,25 +176,28 @@ public class ResourceTree( var genericContext = globalContext.CreateContext(&human->CharacterBase); var cache = globalContext.Collection._cache; - if (cache is not null - && cache.CustomResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle) - && genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle) is { } pbdNode) + if (cache != null && cache.CustomResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle)) { - if (globalContext.WithUiData) + var pbdNode = genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle); + if (pbdNode != null) { - pbdNode = pbdNode.Clone(); - pbdNode.FallbackName = "Racial Deformer"; - pbdNode.IconFlag = ChangedItemIconFlag.Customization; - } + if (globalContext.WithUiData) + { + pbdNode = pbdNode.Clone(); + pbdNode.FallbackName = "Racial Deformer"; + pbdNode.IconFlag = ChangedItemIconFlag.Customization; + } - Nodes.Add(pbdNode); + Nodes.Add(pbdNode); + } } var decalId = (byte)(human->Customize[(int)CustomizeIndex.Facepaint] & 0x7F); - var decalPath = decalId is not 0 - ? GamePaths.Tex.FaceDecal(decalId) - : GamePaths.Tex.Transparent; - if (genericContext.CreateNodeFromTex(human->Decal, decalPath) is { } decalNode) + var decalPath = decalId != 0 + ? GamePaths.Human.Decal.FaceDecalPath(decalId) + : GamePaths.Tex.TransparentPath; + var decalNode = genericContext.CreateNodeFromTex(human->Decal, decalPath); + if (decalNode != null) { if (globalContext.WithUiData) { @@ -225,11 +211,11 @@ public class ResourceTree( var hasLegacyDecal = (human->Customize[(int)CustomizeIndex.FaceFeatures] & 0x80) != 0; var legacyDecalPath = hasLegacyDecal - ? GamePaths.Tex.LegacyDecal - : GamePaths.Tex.Transparent; - if (genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath) is { } legacyDecalNode) + ? GamePaths.Human.Decal.LegacyDecalPath + : GamePaths.Tex.TransparentPath; + var legacyDecalNode = genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath); + if (legacyDecalNode != null) { - legacyDecalNode.ForceProtected = !hasLegacyDecal; if (globalContext.WithUiData) { legacyDecalNode = legacyDecalNode.Clone(); @@ -241,11 +227,7 @@ public class ResourceTree( } } - private unsafe void AddSkeleton(List nodes, ResolveContext context, CharacterBase* model, string prefix = "") - => AddSkeleton(nodes, context, model->EID, model->Skeleton, model->BonePhysicsModule, model->BoneKineDriverModule, prefix); - - private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, BonePhysicsModule* physics, - BoneKineDriverModule* kineDriver, string prefix = "") + private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, string prefix = "") { var eidNode = context.CreateNodeFromEid((ResourceHandle*)eid); if (eidNode != null) @@ -260,9 +242,8 @@ public class ResourceTree( for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) { - var phybHandle = physics != null ? physics->BonePhysicsResourceHandles[i] : null; - var kdbHandle = kineDriver != null ? kineDriver->PartialSkeletonEntries[i].KineDriverResourceHandle : null; - if (context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, kdbHandle, (uint)i) is { } sklbNode) + var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], (uint)i); + if (sklbNode != null) { if (context.Global.WithUiData) sklbNode.FallbackName = $"{prefix}Skeleton #{i}"; @@ -270,16 +251,4 @@ public class ResourceTree( } } } - - private unsafe void AddMaterialAnimationSkeleton(List nodes, ResolveContext context, SkeletonResourceHandle* sklbHandle, - string prefix = "") - { - var sklbNode = context.CreateNodeFromMaterialSklb(sklbHandle); - if (sklbNode is null) - return; - - if (context.Global.WithUiData) - sklbNode.FallbackName = $"{prefix}Material Animation Skeleton"; - nodes.Add(sklbNode); - } } diff --git a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs index 49194c3a..9738148f 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs @@ -23,35 +23,22 @@ public class ResourceTreeFactory( Configuration config, ActorManager actors, PathState pathState, - IFramework framework, ModManager modManager) : IService { - private static readonly string ParentDirectoryPrefix = $"..{Path.DirectorySeparatorChar}"; - private TreeBuildCache CreateTreeBuildCache() - => new(framework.IsInFrameworkUpdateThread ? objects : null, gameData, actors); - - private TreeBuildCache CreateTreeBuildCache(Flags flags) - => !framework.IsInFrameworkUpdateThread && flags.HasFlag(Flags.PopulateObjectTableData) - ? framework.RunOnFrameworkThread(CreateTreeBuildCache).Result - : CreateTreeBuildCache(); + => new(objects, gameData, actors); public IEnumerable GetLocalPlayerRelatedCharacters() - => framework.RunOnFrameworkThread(() => - { - var cache = CreateTreeBuildCache(); - return cache.GetLocalPlayerRelatedCharacters(); - }).Result; + { + var cache = CreateTreeBuildCache(); + return cache.GetLocalPlayerRelatedCharacters(); + } public IEnumerable<(ICharacter Character, ResourceTree ResourceTree)> FromObjectTable( Flags flags) { - var (cache, characters) = framework.RunOnFrameworkThread(() => - { - var cache = CreateTreeBuildCache(); - var characters = ((flags & Flags.LocalPlayerRelatedOnly) != 0 ? cache.GetLocalPlayerRelatedCharacters() : cache.GetCharacters()).ToArray(); - return (cache, characters); - }).Result; + var cache = CreateTreeBuildCache(); + var characters = (flags & Flags.LocalPlayerRelatedOnly) != 0 ? cache.GetLocalPlayerRelatedCharacters() : cache.GetCharacters(); foreach (var character in characters) { @@ -64,7 +51,7 @@ public class ResourceTreeFactory( public IEnumerable<(ICharacter Character, ResourceTree ResourceTree)> FromCharacters( IEnumerable characters, Flags flags) { - var cache = CreateTreeBuildCache(flags); + var cache = CreateTreeBuildCache(); foreach (var character in characters) { var tree = FromCharacter(character, cache, flags); @@ -74,7 +61,7 @@ public class ResourceTreeFactory( } public ResourceTree? FromCharacter(ICharacter character, Flags flags) - => FromCharacter(character, CreateTreeBuildCache(flags), flags); + => FromCharacter(character, CreateTreeBuildCache(), flags); private unsafe ResourceTree? FromCharacter(ICharacter character, TreeBuildCache cache, Flags flags) { @@ -91,10 +78,10 @@ public class ResourceTreeFactory( return null; var localPlayerRelated = cache.IsLocalPlayerRelated(character); - var (name, anonymizedName, related) = GetCharacterName((GameObject*)character.Address); + var (name, anonymizedName, related) = GetCharacterName(character); var networked = character.EntityId != 0xE0000000; var tree = new ResourceTree(name, anonymizedName, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related, - networked, collectionResolveData.ModCollection.Identity.Name, collectionResolveData.ModCollection.Identity.AnonymizedName); + networked, collectionResolveData.ModCollection.Name, collectionResolveData.ModCollection.AnonymizedName); var globalContext = new GlobalResolveContext(metaFileManager, objectIdentifier, collectionResolveData.ModCollection, cache, (flags & Flags.WithUiData) != 0); using (var _ = pathState.EnterInternalResolve()) @@ -148,7 +135,6 @@ public class ResourceTreeFactory( if (node.FullPath.IsRooted && modManager.TryIdentifyPath(node.FullPath.FullName, out var mod, out var relativePath)) { node.ModName = mod.Name; - node.Mod.SetTarget(mod); node.ModRelativePath = relativePath; } } @@ -158,28 +144,25 @@ public class ResourceTreeFactory( { foreach (var node in tree.FlatNodes) { - node.FullPathStatus = GetPathStatus(node.FullPath, onlyWithinPath); - if (node.FullPathStatus != ResourceNode.PathStatus.Valid) + if (!ShallKeepPath(node.FullPath, onlyWithinPath)) node.FullPath = FullPath.Empty; } return; - static ResourceNode.PathStatus GetPathStatus(FullPath fullPath, string? onlyWithinPath) + static bool ShallKeepPath(FullPath fullPath, string? onlyWithinPath) { if (!fullPath.IsRooted) - return ResourceNode.PathStatus.Valid; + return true; if (onlyWithinPath != null) { var relPath = Path.GetRelativePath(onlyWithinPath, fullPath.FullName); - if (relPath == ".." || relPath.StartsWith(ParentDirectoryPrefix) || Path.IsPathRooted(relPath)) - return ResourceNode.PathStatus.External; + if (relPath != "." && (relPath.StartsWith('.') || Path.IsPathRooted(relPath))) + return false; } - return fullPath.Exists - ? ResourceNode.PathStatus.Valid - : ResourceNode.PathStatus.NonExistent; + return fullPath.Exists; } } @@ -194,37 +177,36 @@ public class ResourceTreeFactory( } } - private unsafe (string Name, string AnonymizedName, bool PlayerRelated) GetCharacterName(GameObject* character) + private unsafe (string Name, string AnonymizedName, bool PlayerRelated) GetCharacterName(ICharacter character) { - var identifier = actors.FromObject(character, out var owner, true, false, false); + var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false); var identifierStr = identifier.ToString(); return (identifierStr, identifier.Incognito(identifierStr), IsPlayerRelated(identifier, owner)); } - private unsafe bool IsPlayerRelated(GameObject* character) + private unsafe bool IsPlayerRelated(ICharacter? character) { - if (character is null) + if (character == null) return false; - var identifier = actors.FromObject(character, out var owner, true, false, false); + var identifier = actors.FromObject((GameObject*)character.Address, out var owner, true, false, false); return IsPlayerRelated(identifier, owner); } - private unsafe bool IsPlayerRelated(ActorIdentifier identifier, Actor owner) + private bool IsPlayerRelated(ActorIdentifier identifier, Actor owner) => identifier.Type switch { IdentifierType.Player => true, - IdentifierType.Owned => IsPlayerRelated(owner.AsObject), + IdentifierType.Owned => IsPlayerRelated(objects.Objects.CreateObjectReference(owner) as ICharacter), _ => false, }; [Flags] public enum Flags { - RedactExternalPaths = 1, - WithUiData = 2, - LocalPlayerRelatedOnly = 4, - WithOwnership = 8, - PopulateObjectTableData = 16, + RedactExternalPaths = 1, + WithUiData = 2, + LocalPlayerRelatedOnly = 4, + WithOwnership = 8, } } diff --git a/Penumbra/Interop/ResourceTree/TreeBuildCache.cs b/Penumbra/Interop/ResourceTree/TreeBuildCache.cs index c0114412..49e00547 100644 --- a/Penumbra/Interop/ResourceTree/TreeBuildCache.cs +++ b/Penumbra/Interop/ResourceTree/TreeBuildCache.cs @@ -12,15 +12,14 @@ using Penumbra.String.Classes; namespace Penumbra.Interop.ResourceTree; -internal readonly struct TreeBuildCache(ObjectManager? objects, IDataManager dataManager, ActorManager actors) +internal readonly struct TreeBuildCache(ObjectManager objects, IDataManager dataManager, ActorManager actors) { private readonly Dictionary?> _shaderPackageNames = []; - private readonly IGameObject? _player = objects?.GetDalamudObject(0); - public unsafe bool IsLocalPlayerRelated(ICharacter character) { - if (_player is null) + var player = objects.GetDalamudObject(0); + if (player == null) return false; var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)character.Address; @@ -29,26 +28,27 @@ internal readonly struct TreeBuildCache(ObjectManager? objects, IDataManager dat return actualIndex switch { < 2 => true, - < (int)ScreenActor.CutsceneStart => gameObject->OwnerId == _player.EntityId, + < (int)ScreenActor.CutsceneStart => gameObject->OwnerId == player.EntityId, _ => false, }; } public IEnumerable GetCharacters() - => objects is not null ? objects.Objects.OfType() : []; + => objects.Objects.OfType(); public IEnumerable GetLocalPlayerRelatedCharacters() { - if (_player is null) + var player = objects.GetDalamudObject(0); + if (player == null) yield break; - yield return (ICharacter)_player; + yield return (ICharacter)player; - var minion = objects!.GetDalamudObject(1); - if (minion is not null) + var minion = objects.GetDalamudObject(1); + if (minion != null) yield return (ICharacter)minion; - var playerId = _player.EntityId; + var playerId = player.EntityId; for (var i = 2; i < ObjectIndex.CutsceneStart.Index; i += 2) { if (objects.GetDalamudObject(i) is ICharacter owned && owned.OwnerId == playerId) diff --git a/Penumbra/Interop/Services/CharacterUtility.cs b/Penumbra/Interop/Services/CharacterUtility.cs index 0add9d46..1641e42d 100644 --- a/Penumbra/Interop/Services/CharacterUtility.cs +++ b/Penumbra/Interop/Services/CharacterUtility.cs @@ -1,7 +1,6 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using OtterGui.Services; -using Penumbra.Communication; using Penumbra.GameData; using Penumbra.Interop.Structs; @@ -27,16 +26,14 @@ public unsafe class CharacterUtility : IDisposable, IRequiredService public CharacterUtilityData* Address => *_characterUtilityAddress; - public bool Ready { get; private set; } - - public readonly CharacterUtilityFinished LoadingFinished = new(); - - public nint DefaultHumanPbdResource { get; private set; } - public nint DefaultTransparentResource { get; private set; } - public nint DefaultDecalResource { get; private set; } - public nint DefaultSkinShpkResource { get; private set; } - public nint DefaultCharacterStockingsShpkResource { get; private set; } - public nint DefaultCharacterLegacyShpkResource { get; private set; } + public bool Ready { get; private set; } + public event Action LoadingFinished; + public nint DefaultHumanPbdResource { get; private set; } + public nint DefaultTransparentResource { get; private set; } + public nint DefaultDecalResource { get; private set; } + public nint DefaultSkinShpkResource { get; private set; } + public nint DefaultCharacterStockingsShpkResource { get; private set; } + public nint DefaultCharacterLegacyShpkResource { get; private set; } /// /// The relevant indices depend on which meta manipulations we allow for. @@ -64,7 +61,7 @@ public unsafe class CharacterUtility : IDisposable, IRequiredService .Select(idx => new MetaList(new InternalIndex(idx))) .ToArray(); _framework = framework; - LoadingFinished.Subscribe(() => Penumbra.Log.Debug("Loading of CharacterUtility finished."), CharacterUtilityFinished.Priority.OnFinishedLoading); + LoadingFinished += () => Penumbra.Log.Debug("Loading of CharacterUtility finished."); LoadDefaultResources(null!); if (!Ready) _framework.Update += LoadDefaultResources; diff --git a/Penumbra/Interop/Services/RedrawService.cs b/Penumbra/Interop/Services/RedrawService.cs index 2d741277..8f20ca5e 100644 --- a/Penumbra/Interop/Services/RedrawService.cs +++ b/Penumbra/Interop/Services/RedrawService.cs @@ -10,6 +10,7 @@ using OtterGui.Services; using Penumbra.Api; using Penumbra.Api.Enums; using Penumbra.Communication; +using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.Interop.Structs; @@ -353,14 +354,21 @@ public sealed unsafe partial class RedrawService : IDisposable { switch (settings) { - case RedrawType.Redraw: ReloadActor(actor); break; - case RedrawType.AfterGPose: ReloadActorAfterGPose(actor); break; - default: throw new ArgumentOutOfRangeException(nameof(settings), settings, null); + case RedrawType.Redraw: + ReloadActor(actor); + break; + case RedrawType.AfterGPose: + ReloadActorAfterGPose(actor); + break; + default: throw new ArgumentOutOfRangeException(nameof(settings), settings, null); } } private IGameObject? GetLocalPlayer() - => InGPose ? _objects.GetDalamudObject(GPosePlayerIdx) ?? _objects.GetDalamudObject(0) : _objects.GetDalamudObject(0); + { + var gPosePlayer = _objects.GetDalamudObject(GPosePlayerIdx); + return gPosePlayer ?? _objects.GetDalamudObject(0); + } public bool GetName(string lowerName, out IGameObject? actor) { @@ -421,9 +429,9 @@ public sealed unsafe partial class RedrawService : IDisposable return; - foreach (ref var f in currentTerritory->FurnitureManager.FurnitureMemory) + foreach (ref var f in currentTerritory->Furniture) { - var gameObject = f.Index >= 0 ? currentTerritory->FurnitureManager.ObjectManager.ObjectArray.Objects[f.Index].Value : null; + var gameObject = f.Index >= 0 ? currentTerritory->HousingObjectManager.Objects[f.Index].Value : null; if (gameObject == null) continue; diff --git a/Penumbra/Interop/Services/SchedulerResourceManagementService.cs b/Penumbra/Interop/Services/SchedulerResourceManagementService.cs deleted file mode 100644 index b7f57a44..00000000 --- a/Penumbra/Interop/Services/SchedulerResourceManagementService.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Frozen; -using Dalamud.Plugin.Services; -using Dalamud.Utility.Signatures; -using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Resource; -using Lumina.Excel.Sheets; -using OtterGui.Services; -using Penumbra.Collections; -using Penumbra.Communication; -using Penumbra.GameData; -using Penumbra.Mods.Editor; -using Penumbra.Services; -using Penumbra.String; -using Penumbra.String.Classes; - -namespace Penumbra.Interop.Services; - -public unsafe class SchedulerResourceManagementService : IService, IDisposable -{ - private static readonly CiByteString TmbExtension = new(".tmb"u8, MetaDataComputation.All); - private static readonly CiByteString FolderPrefix = new("chara/action/"u8, MetaDataComputation.All); - - private readonly CommunicatorService _communicator; - private readonly FrozenDictionary _actionTmbs; - - private readonly ConcurrentDictionary _listedTmbIds = []; - - public bool Contains(uint tmbId) - => _listedTmbIds.ContainsKey(tmbId); - - public IReadOnlyDictionary ListedTmbs - => _listedTmbIds; - - public IReadOnlyDictionary ActionTmbs - => _actionTmbs; - - public SchedulerResourceManagementService(IGameInteropProvider interop, CommunicatorService communicator, IDataManager dataManager) - { - _communicator = communicator; - _actionTmbs = CreateActionTmbs(dataManager); - _communicator.ResolvedFileChanged.Subscribe(OnResolvedFileChange, ResolvedFileChanged.Priority.SchedulerResourceManagementService); - interop.InitializeFromAttributes(this); - } - - private void OnResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath gamePath, FullPath oldPath, - FullPath newPath, IMod? mod) - { - switch (type) - { - case ResolvedFileChanged.Type.Added: - CheckFile(gamePath); - return; - case ResolvedFileChanged.Type.FullRecomputeFinished: - foreach (var path in collection.ResolvedFiles.Keys) - CheckFile(path); - return; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private void CheckFile(Utf8GamePath gamePath) - { - if (!gamePath.Extension().Equals(TmbExtension)) - return; - - if (!gamePath.Path.StartsWith(FolderPrefix)) - return; - - var tmb = gamePath.Path.Substring(FolderPrefix.Length, gamePath.Length - FolderPrefix.Length - TmbExtension.Length).Clone(); - if (_actionTmbs.TryGetValue(tmb, out var rowId)) - _listedTmbIds[rowId] = tmb; - else - Penumbra.Log.Verbose($"Action TMB {gamePath} encountered with no corresponding row ID."); - } - - [Signature(Sigs.SchedulerResourceManagementInstance, ScanType = ScanType.StaticAddress)] - public readonly SchedulerResourceManagement** Address = null; - - public SchedulerResourceManagement* Scheduler - => *Address; - - public void Dispose() - { - _listedTmbIds.Clear(); - _communicator.ResolvedFileChanged.Unsubscribe(OnResolvedFileChange); - } - - private static FrozenDictionary CreateActionTmbs(IDataManager dataManager) - { - var sheet = dataManager.GetExcelSheet(); - return sheet.Where(row => !row.Key.IsEmpty).DistinctBy(row => row.Key).ToFrozenDictionary(row => new CiByteString(row.Key, MetaDataComputation.All).Clone(), row => row.RowId); - } -} diff --git a/Penumbra/Interop/Services/TextureArraySlicer.cs b/Penumbra/Interop/Services/TextureArraySlicer.cs index 11498878..c934ac2b 100644 --- a/Penumbra/Interop/Services/TextureArraySlicer.cs +++ b/Penumbra/Interop/Services/TextureArraySlicer.cs @@ -1,4 +1,3 @@ -using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using OtterGui.Services; using SharpDX.Direct3D; @@ -17,7 +16,7 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable private readonly HashSet<(nint XivTexture, byte SliceIndex)> _expiredKeys = []; /// Caching this across frames will cause a crash to desktop. - public ImTextureID GetImGuiHandle(Texture* texture, byte sliceIndex) + public nint GetImGuiHandle(Texture* texture, byte sliceIndex) { if (texture == null) throw new ArgumentNullException(nameof(texture)); @@ -26,7 +25,7 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable if (_activeSlices.TryGetValue(((nint)texture, sliceIndex), out var state)) { state.Refresh(); - return new ImTextureID((nint)state.ShaderResourceView); + return (nint)state.ShaderResourceView; } var srv = (ShaderResourceView)(nint)texture->D3D11ShaderResourceView; var description = srv.Description; @@ -61,7 +60,7 @@ public sealed unsafe class TextureArraySlicer : IUiService, IDisposable } state = new SliceState(new ShaderResourceView(srv.Device, srv.Resource, description)); _activeSlices.Add(((nint)texture, sliceIndex), state); - return new ImTextureID((nint)state.ShaderResourceView); + return (nint)state.ShaderResourceView; } public void Tick() diff --git a/Penumbra/Interop/Structs/ResourceHandle.cs b/Penumbra/Interop/Structs/ResourceHandle.cs index 1558c035..65550563 100644 --- a/Penumbra/Interop/Structs/ResourceHandle.cs +++ b/Penumbra/Interop/Structs/ResourceHandle.cs @@ -24,20 +24,10 @@ public unsafe struct TextureResourceHandle public enum LoadState : byte { - Constructing = 0x00, - Constructed = 0x01, - Async2 = 0x02, - AsyncRequested = 0x03, - Async4 = 0x04, - AsyncLoading = 0x05, - Async6 = 0x06, Success = 0x07, - Unknown8 = 0x08, + Async = 0x03, Failure = 0x09, FailedSubResource = 0x0A, - FailureB = 0x0B, - FailureC = 0x0C, - FailureD = 0x0D, None = 0xFF, } @@ -84,9 +74,6 @@ public unsafe struct ResourceHandle [FieldOffset(0x58)] public int FileNameLength; - [FieldOffset(0xA8)] - public byte UnkState; - [FieldOffset(0xA9)] public LoadState LoadState; diff --git a/Penumbra/Interop/Structs/StructExtensions.cs b/Penumbra/Interop/Structs/StructExtensions.cs index 7349f6cc..9dd9a96d 100644 --- a/Penumbra/Interop/Structs/StructExtensions.cs +++ b/Penumbra/Interop/Structs/StructExtensions.cs @@ -1,6 +1,5 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.STD; -using InteropGenerator.Runtime; using Penumbra.String; namespace Penumbra.Interop.Structs; @@ -10,68 +9,44 @@ internal static class StructExtensions public static CiByteString AsByteString(in this StdString str) => CiByteString.FromSpanUnsafe(str.AsSpan(), true); - public static CiByteString ResolveEidPathAsByteString(ref this CharacterBase character) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); + public static CiByteString ResolveEidPathAsByteString(ref this CharacterBase character) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); + } + + public static CiByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); } - public static CiByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); + public static CiByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); } - public static CiByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); + public static unsafe CiByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName) + { + var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); } - public static unsafe CiByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName) - { - var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); + public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); } - public static unsafe CiByteString ResolveSkinMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex) - { - var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveSkinMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex)); + public static CiByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); } - public static CiByteString ResolveMaterialPapPathAsByteString(ref this CharacterBase character, uint slotIndex, uint unkSId) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveMaterialPapPath(pathBuffer, slotIndex, unkSId)); - } - - public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); - } - - public static CiByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); - } - - public static CiByteString ResolvePhybPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolvePhybPath(pathBuffer, partialSkeletonIndex)); - } - - public static unsafe CiByteString ResolveKdbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveKdbPath(pathBuffer, CharacterBase.PathBufferSize, partialSkeletonIndex)); - } - - private static unsafe CiByteString ToOwnedByteString(CStringPointer str) - => str.HasValue ? new CiByteString(str.Value).Clone() : CiByteString.Empty; + private static unsafe CiByteString ToOwnedByteString(byte* str) + => str == null ? CiByteString.Empty : new CiByteString(str).Clone(); private static CiByteString ToOwnedByteString(ReadOnlySpan str) => str.Length == 0 ? CiByteString.Empty : CiByteString.FromSpanUnsafe(str, true).Clone(); diff --git a/Penumbra/Interop/VolatileOffsets.cs b/Penumbra/Interop/VolatileOffsets.cs index 85008aae..2c6e3180 100644 --- a/Penumbra/Interop/VolatileOffsets.cs +++ b/Penumbra/Interop/VolatileOffsets.cs @@ -6,7 +6,7 @@ public static class VolatileOffsets { public const int PlayTimeOffset = 0x254; public const int SomeIntermediate = 0x1F8; - public const int Flags = 0x4A8; + public const int Flags = 0x4A4; public const int IInstanceListenner = 0x270; public const int BitShift = 13; public const int CasterVFunc = 1; @@ -19,7 +19,7 @@ public static class VolatileOffsets public static class UpdateModel { - public const int ShortCircuit = 0xA3C; + public const int ShortCircuit = 0xA2C; } public static class FontReloader diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs index b8db66dd..01ef3f16 100644 --- a/Penumbra/Meta/Files/ImcFile.cs +++ b/Penumbra/Meta/Files/ImcFile.cs @@ -1,5 +1,3 @@ -using OtterGui.Extensions; -using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.Interop.Structs; @@ -194,52 +192,22 @@ public unsafe class ImcFile : MetaBaseFile public void Replace(ResourceHandle* resource) { var (data, length) = resource->GetData(); - var actualLength = ActualLength; - - if (DebugConfiguration.WriteImcBytesToLog) + if (length == ActualLength) { - Penumbra.Log.Information($"Default IMC file -> Modified IMC File for {Path}, current handle state {resource->LoadState}:"); - Penumbra.Log.Information(new Span((void*)data, length).WriteHexBytes()); - Penumbra.Log.Information(new Span(Data, actualLength).WriteHexBytes()); - Penumbra.Log.Information(new Span(Data, actualLength).WriteHexByteDiff(new Span((void*)data, length))); - } - - if (length >= actualLength) - { - MemoryUtility.MemCpyUnchecked((byte*)data, Data, actualLength); - if (length > actualLength) - MemoryUtility.MemSet((byte*)(data + actualLength), 0, length - actualLength); - if (DebugConfiguration.WriteImcBytesToLog) - { - Penumbra.Log.Information( - $"Copied {actualLength} bytes from local IMC file into {length} available bytes.{(length > actualLength ? $" Filled remaining {length - actualLength} bytes with 0." : string.Empty)}"); - Penumbra.Log.Information("Result IMC Resource Data:"); - Penumbra.Log.Information(new Span((void*)data, length).WriteHexBytes()); - } - + MemoryUtility.MemCpyUnchecked((byte*)data, Data, ActualLength); return; } - var paddedLength = actualLength.PadToMultiple(128); - var newData = Manager.XivFileAllocator.Allocate(paddedLength, 8); + var newData = Manager.XivAllocator.Allocate(ActualLength, 8); if (newData == null) { Penumbra.Log.Error($"Could not replace loaded IMC data at 0x{(ulong)resource:X}, allocation failed."); return; } - MemoryUtility.MemCpyUnchecked(newData, Data, actualLength); - if (paddedLength > actualLength) - MemoryUtility.MemSet(newData + actualLength, 0, paddedLength - actualLength); - if (DebugConfiguration.WriteImcBytesToLog) - { - Penumbra.Log.Information( - $"Allocated {paddedLength} bytes for IMC file, copied {actualLength} bytes from local IMC file. {(length > actualLength ? $" Filled remaining {length - actualLength} bytes with 0." : string.Empty)}"); - Penumbra.Log.Information("Result IMC Resource Data:"); - Penumbra.Log.Information(new Span(newData, paddedLength).WriteHexBytes()); - } + MemoryUtility.MemCpyUnchecked(newData, Data, ActualLength); - Manager.XivFileAllocator.Release((void*)data, length); - resource->SetData((nint)newData, paddedLength); + Manager.XivAllocator.Release((void*)data, length); + resource->SetData((nint)newData, ActualLength); } } diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs index d04e1bdf..5bc36068 100644 --- a/Penumbra/Meta/Files/MetaBaseFile.cs +++ b/Penumbra/Meta/Files/MetaBaseFile.cs @@ -28,16 +28,11 @@ public unsafe interface IFileAllocator public sealed class MarshalAllocator : IFileAllocator { public unsafe T* Allocate(int length, int alignment = 1) where T : unmanaged - { - var ret = (T*)Marshal.AllocHGlobal(length * sizeof(T)); - Penumbra.Log.Verbose($"Allocating {length * sizeof(T)} bytes via Marshal Allocator to 0x{(nint)ret:X}."); - return ret; - } + => (T*)Marshal.AllocHGlobal(length * sizeof(T)); public unsafe void Release(ref T* pointer, int length) where T : unmanaged { Marshal.FreeHGlobal((nint)pointer); - Penumbra.Log.Verbose($"Freeing {length * sizeof(T)} bytes from 0x{(nint)pointer:X} via Marshal Allocator."); pointer = null; } } @@ -58,35 +53,11 @@ public sealed unsafe class XivFileAllocator : IFileAllocator, IService => ((delegate* unmanaged)_getFileSpaceAddress)(); public T* Allocate(int length, int alignment = 1) where T : unmanaged - { - var ret = (T*)GetFileSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment); - Penumbra.Log.Verbose($"Allocating {length * sizeof(T)} bytes via FFXIV File Allocator to 0x{(nint)ret:X}."); - return ret; - } + => (T*)GetFileSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment); public void Release(ref T* pointer, int length) where T : unmanaged { - IMemorySpace.Free(pointer, (ulong)(length * sizeof(T))); - Penumbra.Log.Verbose($"Freeing {length * sizeof(T)} bytes from 0x{(nint)pointer:X} via FFXIV File Allocator."); - pointer = null; - } -} - -public sealed unsafe class XivDefaultAllocator : IFileAllocator, IService -{ - public T* Allocate(int length, int alignment = 1) where T : unmanaged - { - var ret = (T*)IMemorySpace.GetDefaultSpace()->Malloc((ulong)(length * sizeof(T)), (ulong)alignment); - Penumbra.Log.Verbose($"Allocating {length * sizeof(T)} bytes via FFXIV Default Allocator to 0x{(nint)ret:X}."); - return ret; - } - - public void Release(ref T* pointer, int length) where T : unmanaged - { - - IMemorySpace.Free(pointer, (ulong)(length * sizeof(T))); - Penumbra.Log.Verbose($"Freeing {length * sizeof(T)} bytes from 0x{(nint)pointer:X} via FFXIV Default Allocator."); pointer = null; } } diff --git a/Penumbra/Meta/Manipulations/AtchIdentifier.cs b/Penumbra/Meta/Manipulations/AtchIdentifier.cs index c248c48b..bce37620 100644 --- a/Penumbra/Meta/Manipulations/AtchIdentifier.cs +++ b/Penumbra/Meta/Manipulations/AtchIdentifier.cs @@ -31,7 +31,7 @@ public readonly record struct AtchIdentifier(AtchType Type, GenderRace GenderRac public override string ToString() => $"Atch - {Type.ToAbbreviation()} - {GenderRace.ToName()} - {EntryIndex}"; - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) { // Nothing specific } diff --git a/Penumbra/Meta/Manipulations/AtrIdentifier.cs b/Penumbra/Meta/Manipulations/AtrIdentifier.cs deleted file mode 100644 index ca65f6aa..00000000 --- a/Penumbra/Meta/Manipulations/AtrIdentifier.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -public readonly record struct AtrIdentifier(HumanSlot Slot, PrimaryId? Id, ShapeAttributeString Attribute, GenderRace GenderRaceCondition) - : IComparable, IMetaIdentifier -{ - public int CompareTo(AtrIdentifier other) - { - var slotComparison = Slot.CompareTo(other.Slot); - if (slotComparison is not 0) - return slotComparison; - - if (Id.HasValue) - { - if (other.Id.HasValue) - { - var idComparison = Id.Value.Id.CompareTo(other.Id.Value.Id); - if (idComparison is not 0) - return idComparison; - } - else - { - return -1; - } - } - else if (other.Id.HasValue) - { - return 1; - } - - var genderRaceComparison = GenderRaceCondition.CompareTo(other.GenderRaceCondition); - if (genderRaceComparison is not 0) - return genderRaceComparison; - - return Attribute.CompareTo(other.Attribute); - } - - - public override string ToString() - { - var sb = new StringBuilder(64); - sb.Append("Shp - ") - .Append(Attribute); - if (Slot is HumanSlot.Unknown) - { - sb.Append(" - All Slots & IDs"); - } - else - { - sb.Append(" - ") - .Append(Slot.ToName()) - .Append(" - "); - if (Id.HasValue) - sb.Append(Id.Value.Id); - else - sb.Append("All IDs"); - } - - if (GenderRaceCondition is not GenderRace.Unknown) - sb.Append(" - ").Append(GenderRaceCondition.ToRaceCode()); - - return sb.ToString(); - } - - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - { - // Nothing for now since it depends entirely on the shape key. - } - - public MetaIndex FileIndex() - => (MetaIndex)(-1); - - public bool Validate() - { - if (!Enum.IsDefined(Slot) || Slot is HumanSlot.UnkBonus) - return false; - - if (!ShapeAttributeHashSet.GenderRaceIndices.ContainsKey(GenderRaceCondition)) - return false; - - if (Slot is HumanSlot.Unknown && Id is not null) - return false; - - if (Slot.ToSpecificEnum() is BodySlot && Id is { Id: > byte.MaxValue }) - return false; - - if (Id is { Id: > ExpandedEqpGmpBase.Count - 1 }) - return false; - - return Attribute.ValidateCustomAttributeString(); - } - - public JObject AddToJson(JObject jObj) - { - if (Slot is not HumanSlot.Unknown) - jObj["Slot"] = Slot.ToString(); - if (Id.HasValue) - jObj["Id"] = Id.Value.Id.ToString(); - jObj["Attribute"] = Attribute.ToString(); - if (GenderRaceCondition is not GenderRace.Unknown) - jObj["GenderRaceCondition"] = (uint)GenderRaceCondition; - return jObj; - } - - public static AtrIdentifier? FromJson(JObject jObj) - { - var attribute = jObj["Attribute"]?.ToObject(); - if (attribute is null || !ShapeAttributeString.TryRead(attribute, out var attributeString)) - return null; - - var slot = jObj["Slot"]?.ToObject() ?? HumanSlot.Unknown; - var id = jObj["Id"]?.ToObject(); - var genderRaceCondition = jObj["GenderRaceCondition"]?.ToObject() ?? 0; - var identifier = new AtrIdentifier(slot, id, attributeString, genderRaceCondition); - return identifier.Validate() ? identifier : null; - } - - public MetaManipulationType Type - => MetaManipulationType.Atr; -} - -[JsonConverter(typeof(Converter))] -public readonly record struct AtrEntry(bool Value) -{ - public static readonly AtrEntry True = new(true); - public static readonly AtrEntry False = new(false); - - private class Converter : JsonConverter - { - public override void WriteJson(JsonWriter writer, AtrEntry value, JsonSerializer serializer) - => serializer.Serialize(writer, value.Value); - - public override AtrEntry ReadJson(JsonReader reader, Type objectType, AtrEntry existingValue, bool hasExistingValue, - JsonSerializer serializer) - => new(serializer.Deserialize(reader)); - } -} diff --git a/Penumbra/Meta/Manipulations/Eqdp.cs b/Penumbra/Meta/Manipulations/Eqdp.cs index c8423b92..3a804d0c 100644 --- a/Penumbra/Meta/Manipulations/Eqdp.cs +++ b/Penumbra/Meta/Manipulations/Eqdp.cs @@ -15,8 +15,8 @@ public readonly record struct EqdpIdentifier(PrimaryId SetId, EquipSlot Slot, Ge public Gender Gender => GenderRace.Split().Item1; - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - => identifier.Identify(changedItems, GamePaths.Mdl.Equipment(SetId, GenderRace, Slot)); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + => identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, Slot)); public MetaIndex FileIndex() => CharacterUtilityData.EqdpIdx(GenderRace, Slot.IsAccessory()); diff --git a/Penumbra/Meta/Manipulations/Eqp.cs b/Penumbra/Meta/Manipulations/Eqp.cs index 154aca40..f758126c 100644 --- a/Penumbra/Meta/Manipulations/Eqp.cs +++ b/Penumbra/Meta/Manipulations/Eqp.cs @@ -8,8 +8,8 @@ namespace Penumbra.Meta.Manipulations; public readonly record struct EqpIdentifier(PrimaryId SetId, EquipSlot Slot) : IMetaIdentifier, IComparable { - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - => identifier.Identify(changedItems, GamePaths.Mdl.Equipment(SetId, GenderRace.MidlanderMale, Slot)); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + => identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, Slot)); public MetaIndex FileIndex() => MetaIndex.Eqp; diff --git a/Penumbra/Meta/Manipulations/Est.cs b/Penumbra/Meta/Manipulations/Est.cs index 46a275a5..cfe9b7d4 100644 --- a/Penumbra/Meta/Manipulations/Est.cs +++ b/Penumbra/Meta/Manipulations/Est.cs @@ -24,29 +24,24 @@ public readonly record struct EstIdentifier(PrimaryId SetId, EstType Slot, Gende public Gender Gender => GenderRace.Split().Item1; - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) { switch (Slot) { case EstType.Hair: - { - var (gender, race) = GenderRace.Split(); - var id = (CustomizeValue)SetId.Id; - changedItems.UpdateCountOrSet( - $"Customization: {race.ToName()} {gender.ToName()} Hair {SetId}", () => IdentifiedCustomization.Hair(race, gender, id)); + changedItems.TryAdd( + $"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Hair (Hair) {SetId}", null); break; - } case EstType.Face: - { - var (gender, race) = GenderRace.Split(); - var id = (CustomizeValue)SetId.Id; - changedItems.UpdateCountOrSet( - $"Customization: {race.ToName()} {gender.ToName()} Face {SetId}", - () => IdentifiedCustomization.Face(race, gender, id)); + changedItems.TryAdd( + $"Customization: {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()} Face (Face) {SetId}", null); + break; + case EstType.Body: + identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Body)); + break; + case EstType.Head: + identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace, EquipSlot.Head)); break; - } - case EstType.Body: identifier.Identify(changedItems, GamePaths.Mdl.Equipment(SetId, GenderRace, EquipSlot.Body)); break; - case EstType.Head: identifier.Identify(changedItems, GamePaths.Mdl.Equipment(SetId, GenderRace, EquipSlot.Head)); break; } } diff --git a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs index 33399a36..ec59762b 100644 --- a/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs +++ b/Penumbra/Meta/Manipulations/GlobalEqpManipulation.cs @@ -16,10 +16,10 @@ public readonly struct GlobalEqpManipulation : IMetaIdentifier if (!Enum.IsDefined(Type)) return false; - if (Type.HasCondition()) - return Condition.Id is not 0; + if (Type is GlobalEqpType.DoNotHideVieraHats or GlobalEqpType.DoNotHideHrothgarHats) + return Condition == 0; - return Condition.Id is 0; + return Condition != 0; } public JObject AddToJson(JObject jObj) @@ -70,15 +70,15 @@ public readonly struct GlobalEqpManipulation : IMetaIdentifier public override string ToString() => $"Global EQP - {Type}{(Condition != 0 ? $" - {Condition.Id}" : string.Empty)}"; - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) { var path = Type switch { - GlobalEqpType.DoNotHideEarrings => GamePaths.Mdl.Accessory(Condition, GenderRace.MidlanderMale, EquipSlot.Ears), - GlobalEqpType.DoNotHideNecklace => GamePaths.Mdl.Accessory(Condition, GenderRace.MidlanderMale, EquipSlot.Neck), - GlobalEqpType.DoNotHideBracelets => GamePaths.Mdl.Accessory(Condition, GenderRace.MidlanderMale, EquipSlot.Wrists), - GlobalEqpType.DoNotHideRingR => GamePaths.Mdl.Accessory(Condition, GenderRace.MidlanderMale, EquipSlot.RFinger), - GlobalEqpType.DoNotHideRingL => GamePaths.Mdl.Accessory(Condition, GenderRace.MidlanderMale, EquipSlot.LFinger), + GlobalEqpType.DoNotHideEarrings => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Ears), + GlobalEqpType.DoNotHideNecklace => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Neck), + GlobalEqpType.DoNotHideBracelets => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.Wrists), + GlobalEqpType.DoNotHideRingR => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.RFinger), + GlobalEqpType.DoNotHideRingL => GamePaths.Accessory.Mdl.Path(Condition, GenderRace.MidlanderMale, EquipSlot.LFinger), GlobalEqpType.DoNotHideHrothgarHats => string.Empty, GlobalEqpType.DoNotHideVieraHats => string.Empty, _ => string.Empty, @@ -86,15 +86,9 @@ public readonly struct GlobalEqpManipulation : IMetaIdentifier if (path.Length > 0) identifier.Identify(changedItems, path); else if (Type is GlobalEqpType.DoNotHideVieraHats) - changedItems.UpdateCountOrSet("All Hats for Viera", () => new IdentifiedName()); + changedItems["All Hats for Viera"] = null; else if (Type is GlobalEqpType.DoNotHideHrothgarHats) - changedItems.UpdateCountOrSet("All Hats for Hrothgar", () => new IdentifiedName()); - else if (Type is GlobalEqpType.HideHorns) - changedItems.UpdateCountOrSet("All Au Ra Horns", () => new IdentifiedName()); - else if (Type is GlobalEqpType.HideVieraEars) - changedItems.UpdateCountOrSet("All Viera Ears", () => new IdentifiedName()); - else if (Type is GlobalEqpType.HideMiqoteEars) - changedItems.UpdateCountOrSet("All Miqo'te Ears", () => new IdentifiedName()); + changedItems["All Hats for Hrothgar"] = null; } public MetaIndex FileIndex() diff --git a/Penumbra/Meta/Manipulations/GlobalEqpType.cs b/Penumbra/Meta/Manipulations/GlobalEqpType.cs index 29bfe825..1a7396f9 100644 --- a/Penumbra/Meta/Manipulations/GlobalEqpType.cs +++ b/Penumbra/Meta/Manipulations/GlobalEqpType.cs @@ -13,9 +13,6 @@ public enum GlobalEqpType DoNotHideRingL, DoNotHideHrothgarHats, DoNotHideVieraHats, - HideHorns, - HideVieraEars, - HideMiqoteEars, } public static class GlobalEqpExtensions @@ -30,9 +27,6 @@ public static class GlobalEqpExtensions GlobalEqpType.DoNotHideRingL => true, GlobalEqpType.DoNotHideHrothgarHats => false, GlobalEqpType.DoNotHideVieraHats => false, - GlobalEqpType.HideHorns => false, - GlobalEqpType.HideVieraEars => false, - GlobalEqpType.HideMiqoteEars => false, _ => false, }; @@ -47,9 +41,6 @@ public static class GlobalEqpExtensions GlobalEqpType.DoNotHideRingL => "Always Show Rings (Left Finger)"u8, GlobalEqpType.DoNotHideHrothgarHats => "Always Show Hats for Hrothgar"u8, GlobalEqpType.DoNotHideVieraHats => "Always Show Hats for Viera"u8, - GlobalEqpType.HideHorns => "Always Hide Horns (Au Ra)"u8, - GlobalEqpType.HideVieraEars => "Always Hide Ears (Viera)"u8, - GlobalEqpType.HideMiqoteEars => "Always Hide Ears (Miqo'te)"u8, _ => "\0"u8, }; @@ -69,9 +60,6 @@ public static class GlobalEqpExtensions "Prevents the game from hiding any hats for Hrothgar that are normally flagged to not display on them."u8, GlobalEqpType.DoNotHideVieraHats => "Prevents the game from hiding any hats for Viera that are normally flagged to not display on them."u8, - GlobalEqpType.HideHorns => "Forces the game to hide Au Ra horns regardless of headwear."u8, - GlobalEqpType.HideVieraEars => "Forces the game to hide Viera ears regardless of headwear."u8, - GlobalEqpType.HideMiqoteEars => "Forces the game to hide Miqo'te ears regardless of headwear."u8, - _ => "\0"u8, + _ => "\0"u8, }; } diff --git a/Penumbra/Meta/Manipulations/Gmp.cs b/Penumbra/Meta/Manipulations/Gmp.cs index 5bc81f26..1f41adfb 100644 --- a/Penumbra/Meta/Manipulations/Gmp.cs +++ b/Penumbra/Meta/Manipulations/Gmp.cs @@ -8,8 +8,8 @@ namespace Penumbra.Meta.Manipulations; public readonly record struct GmpIdentifier(PrimaryId SetId) : IMetaIdentifier, IComparable { - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - => identifier.Identify(changedItems, GamePaths.Mdl.Equipment(SetId, GenderRace.MidlanderMale, EquipSlot.Head)); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + => identifier.Identify(changedItems, GamePaths.Equipment.Mdl.Path(SetId, GenderRace.MidlanderMale, EquipSlot.Head)); public MetaIndex FileIndex() => MetaIndex.Gmp; diff --git a/Penumbra/Meta/Manipulations/IMetaIdentifier.cs b/Penumbra/Meta/Manipulations/IMetaIdentifier.cs index 922825c3..999fd906 100644 --- a/Penumbra/Meta/Manipulations/IMetaIdentifier.cs +++ b/Penumbra/Meta/Manipulations/IMetaIdentifier.cs @@ -15,13 +15,11 @@ public enum MetaManipulationType : byte Rsp = 6, GlobalEqp = 7, Atch = 8, - Shp = 9, - Atr = 10, } public interface IMetaIdentifier { - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); public MetaIndex FileIndex(); diff --git a/Penumbra/Meta/Manipulations/Imc.cs b/Penumbra/Meta/Manipulations/Imc.cs index fa726708..cba6c379 100644 --- a/Penumbra/Meta/Manipulations/Imc.cs +++ b/Penumbra/Meta/Manipulations/Imc.cs @@ -27,21 +27,21 @@ public readonly record struct ImcIdentifier( : this(primaryId, variant, slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment, 0, slot, BodySlot.Unknown) { } - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) => AddChangedItems(identifier, changedItems, false); - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems, bool allVariants) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems, bool allVariants) { var path = ObjectType switch { - ObjectType.Equipment when allVariants => GamePaths.Mdl.Equipment(PrimaryId, GenderRace.MidlanderMale, EquipSlot), - ObjectType.Equipment => GamePaths.Mtrl.Equipment(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"), - ObjectType.Accessory when allVariants => GamePaths.Mdl.Accessory(PrimaryId, GenderRace.MidlanderMale, EquipSlot), - ObjectType.Accessory => GamePaths.Mtrl.Accessory(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"), - ObjectType.Weapon => GamePaths.Mtrl.Weapon(PrimaryId, SecondaryId.Id, Variant, "a"), - ObjectType.DemiHuman => GamePaths.Mtrl.DemiHuman(PrimaryId, SecondaryId.Id, EquipSlot, Variant, + ObjectType.Equipment when allVariants => GamePaths.Equipment.Mdl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot), + ObjectType.Equipment => GamePaths.Equipment.Mtrl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"), + ObjectType.Accessory when allVariants => GamePaths.Accessory.Mdl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot), + ObjectType.Accessory => GamePaths.Accessory.Mtrl.Path(PrimaryId, GenderRace.MidlanderMale, EquipSlot, Variant, "a"), + ObjectType.Weapon => GamePaths.Weapon.Mtrl.Path(PrimaryId, SecondaryId.Id, Variant, "a"), + ObjectType.DemiHuman => GamePaths.DemiHuman.Mtrl.Path(PrimaryId, SecondaryId.Id, EquipSlot, Variant, "a"), - ObjectType.Monster => GamePaths.Mtrl.Monster(PrimaryId, SecondaryId.Id, Variant, "a"), + ObjectType.Monster => GamePaths.Monster.Mtrl.Path(PrimaryId, SecondaryId.Id, Variant, "a"), _ => string.Empty, }; if (path.Length == 0) @@ -51,7 +51,15 @@ public readonly record struct ImcIdentifier( } public string GamePathString() - => GamePaths.Imc.Path(ObjectType, PrimaryId, SecondaryId); + => ObjectType switch + { + ObjectType.Accessory => GamePaths.Accessory.Imc.Path(PrimaryId), + ObjectType.Equipment => GamePaths.Equipment.Imc.Path(PrimaryId), + ObjectType.DemiHuman => GamePaths.DemiHuman.Imc.Path(PrimaryId, SecondaryId.Id), + ObjectType.Monster => GamePaths.Monster.Imc.Path(PrimaryId, SecondaryId.Id), + ObjectType.Weapon => GamePaths.Weapon.Imc.Path(PrimaryId, SecondaryId.Id), + _ => string.Empty, + }; public Utf8GamePath GamePath() => Utf8GamePath.FromString(GamePathString(), out var p) ? p : Utf8GamePath.Empty; diff --git a/Penumbra/Meta/Manipulations/MetaDictionary.cs b/Penumbra/Meta/Manipulations/MetaDictionary.cs index 8b448ec6..ca45c777 100644 --- a/Penumbra/Meta/Manipulations/MetaDictionary.cs +++ b/Penumbra/Meta/Manipulations/MetaDictionary.cs @@ -1,10 +1,7 @@ -using System.Collections.Frozen; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Penumbra.Collections.Cache; -using Penumbra.GameData.Enums; using Penumbra.GameData.Files.AtchStructs; -using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; using Penumbra.Util; using ImcEntry = Penumbra.GameData.Structs.ImcEntry; @@ -14,333 +11,120 @@ namespace Penumbra.Meta.Manipulations; [JsonConverter(typeof(Converter))] public class MetaDictionary { - private class Wrapper : HashSet - { - public readonly Dictionary Imc = []; - public readonly Dictionary Eqp = []; - public readonly Dictionary Eqdp = []; - public readonly Dictionary Est = []; - public readonly Dictionary Rsp = []; - public readonly Dictionary Gmp = []; - public readonly Dictionary Atch = []; - public readonly Dictionary Shp = []; - public readonly Dictionary Atr = []; - - public Wrapper() - { } - - public Wrapper(MetaCache cache) - { - Imc = cache.Imc.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Eqp = cache.Eqp.ToDictionary(kvp => kvp.Key, kvp => new EqpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); - Eqdp = cache.Eqdp.ToDictionary(kvp => kvp.Key, kvp => new EqdpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); - Est = cache.Est.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Gmp = cache.Gmp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Rsp = cache.Rsp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Atch = cache.Atch.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Shp = cache.Shp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - Atr = cache.Atr.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); - foreach (var geqp in cache.GlobalEqp.Keys) - Add(geqp); - } - - public static unsafe Wrapper Filtered(MetaCache cache, Actor actor) - { - if (!actor.IsCharacter) - return new Wrapper(cache); - - var model = actor.Model; - if (!model.IsHuman) - return new Wrapper(cache); - - var headId = model.GetModelId(HumanSlot.Head); - var bodyId = model.GetModelId(HumanSlot.Body); - var equipIdSet = ((IEnumerable) - [ - headId, - bodyId, - model.GetModelId(HumanSlot.Hands), - model.GetModelId(HumanSlot.Legs), - model.GetModelId(HumanSlot.Feet), - ]).ToFrozenSet(); - var earsId = model.GetModelId(HumanSlot.Ears); - var neckId = model.GetModelId(HumanSlot.Neck); - var wristId = model.GetModelId(HumanSlot.Wrists); - var rFingerId = model.GetModelId(HumanSlot.RFinger); - var lFingerId = model.GetModelId(HumanSlot.LFinger); - - var wrapper = new Wrapper(); - // Check for all relevant primary IDs due to slot overlap. - foreach (var (eqp, value) in cache.Eqp) - { - if (eqp.Slot.IsEquipment()) - { - if (equipIdSet.Contains(eqp.SetId)) - wrapper.Eqp.Add(eqp, new EqpEntryInternal(value.Entry, eqp.Slot)); - } - else - { - switch (eqp.Slot) - { - case EquipSlot.Ears when eqp.SetId == earsId: - case EquipSlot.Neck when eqp.SetId == neckId: - case EquipSlot.Wrists when eqp.SetId == wristId: - case EquipSlot.RFinger when eqp.SetId == rFingerId: - case EquipSlot.LFinger when eqp.SetId == lFingerId: - wrapper.Eqp.Add(eqp, new EqpEntryInternal(value.Entry, eqp.Slot)); - break; - } - } - } - - // Check also for body IDs due to body occupying head. - foreach (var (gmp, value) in cache.Gmp) - { - if (gmp.SetId == headId || gmp.SetId == bodyId) - wrapper.Gmp.Add(gmp, value.Entry); - } - - // Check for all races due to inheritance and all slots due to overlap. - foreach (var (eqdp, value) in cache.Eqdp) - { - if (eqdp.Slot.IsEquipment()) - { - if (equipIdSet.Contains(eqdp.SetId)) - wrapper.Eqdp.Add(eqdp, new EqdpEntryInternal(value.Entry, eqdp.Slot)); - } - else - { - switch (eqdp.Slot) - { - case EquipSlot.Ears when eqdp.SetId == earsId: - case EquipSlot.Neck when eqdp.SetId == neckId: - case EquipSlot.Wrists when eqdp.SetId == wristId: - case EquipSlot.RFinger when eqdp.SetId == rFingerId: - case EquipSlot.LFinger when eqdp.SetId == lFingerId: - wrapper.Eqdp.Add(eqdp, new EqdpEntryInternal(value.Entry, eqdp.Slot)); - break; - } - } - } - - var genderRace = (GenderRace)model.AsHuman->RaceSexId; - var hairId = model.GetModelId(HumanSlot.Hair); - var faceId = model.GetModelId(HumanSlot.Face); - // We do not need to care for racial inheritance for ESTs. - foreach (var (est, value) in cache.Est) - { - switch (est.Slot) - { - case EstType.Hair when est.SetId == hairId && est.GenderRace == genderRace: - case EstType.Face when est.SetId == faceId && est.GenderRace == genderRace: - case EstType.Body when est.SetId == bodyId && est.GenderRace == genderRace: - case EstType.Head when (est.SetId == headId || est.SetId == bodyId) && est.GenderRace == genderRace: - wrapper.Est.Add(est, value.Entry); - break; - } - } - - foreach (var (geqp, _) in cache.GlobalEqp) - { - switch (geqp.Type) - { - case GlobalEqpType.DoNotHideEarrings when geqp.Condition != earsId: - case GlobalEqpType.DoNotHideNecklace when geqp.Condition != neckId: - case GlobalEqpType.DoNotHideBracelets when geqp.Condition != wristId: - case GlobalEqpType.DoNotHideRingR when geqp.Condition != rFingerId: - case GlobalEqpType.DoNotHideRingL when geqp.Condition != lFingerId: - continue; - default: wrapper.Add(geqp); break; - } - } - - var (_, _, main, off) = model.GetWeapons(actor); - foreach (var (imc, value) in cache.Imc) - { - switch (imc.ObjectType) - { - case ObjectType.Equipment when equipIdSet.Contains(imc.PrimaryId): wrapper.Imc.Add(imc, value.Entry); break; - - case ObjectType.Weapon: - if (imc.PrimaryId == main.Skeleton && imc.SecondaryId == main.Weapon) - wrapper.Imc.Add(imc, value.Entry); - else if (imc.PrimaryId == off.Skeleton && imc.SecondaryId == off.Weapon) - wrapper.Imc.Add(imc, value.Entry); - break; - case ObjectType.Accessory: - switch (imc.EquipSlot) - { - case EquipSlot.Ears when imc.PrimaryId == earsId: - case EquipSlot.Neck when imc.PrimaryId == neckId: - case EquipSlot.Wrists when imc.PrimaryId == wristId: - case EquipSlot.RFinger when imc.PrimaryId == rFingerId: - case EquipSlot.LFinger when imc.PrimaryId == lFingerId: - wrapper.Imc.Add(imc, value.Entry); - break; - } - - break; - } - } - - var subRace = (SubRace)model.AsHuman->Customize[4]; - foreach (var (rsp, value) in cache.Rsp) - { - if (rsp.SubRace == subRace) - wrapper.Rsp.Add(rsp, value.Entry); - } - - // Keep all atch, atr and shp. - wrapper.Atch.EnsureCapacity(cache.Atch.Count); - wrapper.Shp.EnsureCapacity(cache.Shp.Count); - wrapper.Atr.EnsureCapacity(cache.Atr.Count); - foreach (var (atch, value) in cache.Atch) - wrapper.Atch.Add(atch, value.Entry); - foreach (var (shp, value) in cache.Shp) - wrapper.Shp.Add(shp, value.Entry); - foreach (var (atr, value) in cache.Atr) - wrapper.Atr.Add(atr, value.Entry); - return wrapper; - } - } - - private Wrapper? _data; + private readonly Dictionary _imc = []; + private readonly Dictionary _eqp = []; + private readonly Dictionary _eqdp = []; + private readonly Dictionary _est = []; + private readonly Dictionary _rsp = []; + private readonly Dictionary _gmp = []; + private readonly Dictionary _atch = []; + private readonly HashSet _globalEqp = []; public IReadOnlyDictionary Imc - => _data?.Imc ?? []; + => _imc; public IReadOnlyDictionary Eqp - => _data?.Eqp ?? []; + => _eqp; public IReadOnlyDictionary Eqdp - => _data?.Eqdp ?? []; + => _eqdp; public IReadOnlyDictionary Est - => _data?.Est ?? []; + => _est; public IReadOnlyDictionary Gmp - => _data?.Gmp ?? []; + => _gmp; public IReadOnlyDictionary Rsp - => _data?.Rsp ?? []; + => _rsp; public IReadOnlyDictionary Atch - => _data?.Atch ?? []; - - public IReadOnlyDictionary Shp - => _data?.Shp ?? []; - - public IReadOnlyDictionary Atr - => _data?.Atr ?? []; + => _atch; public IReadOnlySet GlobalEqp - => _data ?? []; + => _globalEqp; public int Count { get; private set; } public int GetCount(MetaManipulationType type) - => _data is null - ? 0 - : type switch - { - MetaManipulationType.Imc => _data.Imc.Count, - MetaManipulationType.Eqdp => _data.Eqdp.Count, - MetaManipulationType.Eqp => _data.Eqp.Count, - MetaManipulationType.Est => _data.Est.Count, - MetaManipulationType.Gmp => _data.Gmp.Count, - MetaManipulationType.Rsp => _data.Rsp.Count, - MetaManipulationType.Atch => _data.Atch.Count, - MetaManipulationType.Shp => _data.Shp.Count, - MetaManipulationType.Atr => _data.Atr.Count, - MetaManipulationType.GlobalEqp => _data.Count, - _ => 0, - }; + => type switch + { + MetaManipulationType.Imc => _imc.Count, + MetaManipulationType.Eqdp => _eqdp.Count, + MetaManipulationType.Eqp => _eqp.Count, + MetaManipulationType.Est => _est.Count, + MetaManipulationType.Gmp => _gmp.Count, + MetaManipulationType.Rsp => _rsp.Count, + MetaManipulationType.Atch => _atch.Count, + MetaManipulationType.GlobalEqp => _globalEqp.Count, + _ => 0, + }; public bool Contains(IMetaIdentifier identifier) - => _data is not null - && identifier switch - { - EqdpIdentifier i => _data.Eqdp.ContainsKey(i), - EqpIdentifier i => _data.Eqp.ContainsKey(i), - EstIdentifier i => _data.Est.ContainsKey(i), - GlobalEqpManipulation i => _data.Contains(i), - GmpIdentifier i => _data.Gmp.ContainsKey(i), - ImcIdentifier i => _data.Imc.ContainsKey(i), - AtchIdentifier i => _data.Atch.ContainsKey(i), - ShpIdentifier i => _data.Shp.ContainsKey(i), - AtrIdentifier i => _data.Atr.ContainsKey(i), - RspIdentifier i => _data.Rsp.ContainsKey(i), - _ => false, - }; + => identifier switch + { + EqdpIdentifier i => _eqdp.ContainsKey(i), + EqpIdentifier i => _eqp.ContainsKey(i), + EstIdentifier i => _est.ContainsKey(i), + GlobalEqpManipulation i => _globalEqp.Contains(i), + GmpIdentifier i => _gmp.ContainsKey(i), + ImcIdentifier i => _imc.ContainsKey(i), + AtchIdentifier i => _atch.ContainsKey(i), + RspIdentifier i => _rsp.ContainsKey(i), + _ => false, + }; public void Clear() { - _data = null; Count = 0; + _imc.Clear(); + _eqp.Clear(); + _eqdp.Clear(); + _est.Clear(); + _rsp.Clear(); + _gmp.Clear(); + _atch.Clear(); + _globalEqp.Clear(); } public void ClearForDefault() { - if (_data is null) - return; - - if (_data.Count is 0 && Shp.Count is 0 && Atr.Count is 0) - { - _data = null; - Count = 0; - return; - } - - Count = GlobalEqp.Count + Shp.Count + Atr.Count; - _data!.Imc.Clear(); - _data!.Eqp.Clear(); - _data!.Eqdp.Clear(); - _data!.Est.Clear(); - _data!.Rsp.Clear(); - _data!.Gmp.Clear(); - _data!.Atch.Clear(); + Count = _globalEqp.Count; + _imc.Clear(); + _eqp.Clear(); + _eqdp.Clear(); + _est.Clear(); + _rsp.Clear(); + _gmp.Clear(); + _atch.Clear(); } public bool Equals(MetaDictionary other) - { - if (Count != other.Count) - return false; - - if (_data is null) - return true; - - return _data.Imc.SetEquals(other._data!.Imc) - && _data.Eqp.SetEquals(other._data!.Eqp) - && _data.Eqdp.SetEquals(other._data!.Eqdp) - && _data.Est.SetEquals(other._data!.Est) - && _data.Rsp.SetEquals(other._data!.Rsp) - && _data.Gmp.SetEquals(other._data!.Gmp) - && _data.Atch.SetEquals(other._data!.Atch) - && _data.Shp.SetEquals(other._data!.Shp) - && _data.Atr.SetEquals(other._data!.Atr) - && _data.SetEquals(other._data!); - } + => Count == other.Count + && _imc.SetEquals(other._imc) + && _eqp.SetEquals(other._eqp) + && _eqdp.SetEquals(other._eqdp) + && _est.SetEquals(other._est) + && _rsp.SetEquals(other._rsp) + && _gmp.SetEquals(other._gmp) + && _atch.SetEquals(other._atch) + && _globalEqp.SetEquals(other._globalEqp); public IEnumerable Identifiers - => _data is null - ? [] - : _data.Imc.Keys.Cast() - .Concat(_data!.Eqdp.Keys.Cast()) - .Concat(_data!.Eqp.Keys.Cast()) - .Concat(_data!.Est.Keys.Cast()) - .Concat(_data!.Gmp.Keys.Cast()) - .Concat(_data!.Rsp.Keys.Cast()) - .Concat(_data!.Atch.Keys.Cast()) - .Concat(_data!.Shp.Keys.Cast()) - .Concat(_data!.Atr.Keys.Cast()) - .Concat(_data!.Cast()); + => _imc.Keys.Cast() + .Concat(_eqdp.Keys.Cast()) + .Concat(_eqp.Keys.Cast()) + .Concat(_est.Keys.Cast()) + .Concat(_gmp.Keys.Cast()) + .Concat(_rsp.Keys.Cast()) + .Concat(_atch.Keys.Cast()) + .Concat(_globalEqp.Cast()); #region TryAdd public bool TryAdd(ImcIdentifier identifier, ImcEntry entry) { - _data ??= []; - if (!_data!.Imc.TryAdd(identifier, entry)) + if (!_imc.TryAdd(identifier, entry)) return false; ++Count; @@ -349,8 +133,7 @@ public class MetaDictionary public bool TryAdd(EqpIdentifier identifier, EqpEntryInternal entry) { - _data ??= []; - if (!_data!.Eqp.TryAdd(identifier, entry)) + if (!_eqp.TryAdd(identifier, entry)) return false; ++Count; @@ -362,8 +145,7 @@ public class MetaDictionary public bool TryAdd(EqdpIdentifier identifier, EqdpEntryInternal entry) { - _data ??= []; - if (!_data!.Eqdp.TryAdd(identifier, entry)) + if (!_eqdp.TryAdd(identifier, entry)) return false; ++Count; @@ -375,8 +157,7 @@ public class MetaDictionary public bool TryAdd(EstIdentifier identifier, EstEntry entry) { - _data ??= []; - if (!_data!.Est.TryAdd(identifier, entry)) + if (!_est.TryAdd(identifier, entry)) return false; ++Count; @@ -385,8 +166,7 @@ public class MetaDictionary public bool TryAdd(GmpIdentifier identifier, GmpEntry entry) { - _data ??= []; - if (!_data!.Gmp.TryAdd(identifier, entry)) + if (!_gmp.TryAdd(identifier, entry)) return false; ++Count; @@ -395,8 +175,7 @@ public class MetaDictionary public bool TryAdd(RspIdentifier identifier, RspEntry entry) { - _data ??= []; - if (!_data!.Rsp.TryAdd(identifier, entry)) + if (!_rsp.TryAdd(identifier, entry)) return false; ++Count; @@ -405,28 +184,7 @@ public class MetaDictionary public bool TryAdd(AtchIdentifier identifier, in AtchEntry entry) { - _data ??= []; - if (!_data!.Atch.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } - - public bool TryAdd(ShpIdentifier identifier, in ShpEntry entry) - { - _data ??= []; - if (!_data!.Shp.TryAdd(identifier, entry)) - return false; - - ++Count; - return true; - } - - public bool TryAdd(AtrIdentifier identifier, in AtrEntry entry) - { - _data ??= []; - if (!_data!.Atr.TryAdd(identifier, entry)) + if (!_atch.TryAdd(identifier, entry)) return false; ++Count; @@ -435,8 +193,7 @@ public class MetaDictionary public bool TryAdd(GlobalEqpManipulation identifier) { - _data ??= []; - if (!_data.Add(identifier)) + if (!_globalEqp.Add(identifier)) return false; ++Count; @@ -449,19 +206,19 @@ public class MetaDictionary public bool Update(ImcIdentifier identifier, ImcEntry entry) { - if (_data is null || !_data.Imc.ContainsKey(identifier)) + if (!_imc.ContainsKey(identifier)) return false; - _data.Imc[identifier] = entry; + _imc[identifier] = entry; return true; } public bool Update(EqpIdentifier identifier, EqpEntryInternal entry) { - if (_data is null || !_data.Eqp.ContainsKey(identifier)) + if (!_eqp.ContainsKey(identifier)) return false; - _data.Eqp[identifier] = entry; + _eqp[identifier] = entry; return true; } @@ -470,10 +227,10 @@ public class MetaDictionary public bool Update(EqdpIdentifier identifier, EqdpEntryInternal entry) { - if (_data is null || !_data.Eqdp.ContainsKey(identifier)) + if (!_eqdp.ContainsKey(identifier)) return false; - _data.Eqdp[identifier] = entry; + _eqdp[identifier] = entry; return true; } @@ -482,55 +239,37 @@ public class MetaDictionary public bool Update(EstIdentifier identifier, EstEntry entry) { - if (_data is null || !_data.Est.ContainsKey(identifier)) + if (!_est.ContainsKey(identifier)) return false; - _data.Est[identifier] = entry; + _est[identifier] = entry; return true; } public bool Update(GmpIdentifier identifier, GmpEntry entry) { - if (_data is null || !_data.Gmp.ContainsKey(identifier)) + if (!_gmp.ContainsKey(identifier)) return false; - _data.Gmp[identifier] = entry; + _gmp[identifier] = entry; return true; } public bool Update(RspIdentifier identifier, RspEntry entry) { - if (_data is null || !_data.Rsp.ContainsKey(identifier)) + if (!_rsp.ContainsKey(identifier)) return false; - _data.Rsp[identifier] = entry; + _rsp[identifier] = entry; return true; } public bool Update(AtchIdentifier identifier, in AtchEntry entry) { - if (_data is null || !_data.Atch.ContainsKey(identifier)) + if (!_atch.ContainsKey(identifier)) return false; - _data.Atch[identifier] = entry; - return true; - } - - public bool Update(ShpIdentifier identifier, in ShpEntry entry) - { - if (_data is null || !_data.Shp.ContainsKey(identifier)) - return false; - - _data.Shp[identifier] = entry; - return true; - } - - public bool Update(AtrIdentifier identifier, in AtrEntry entry) - { - if (_data is null || !_data.Atr.ContainsKey(identifier)) - return false; - - _data.Atr[identifier] = entry; + _atch[identifier] = entry; return true; } @@ -539,63 +278,44 @@ public class MetaDictionary #region TryGetValue public bool TryGetValue(EstIdentifier identifier, out EstEntry value) - => _data?.Est.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _est.TryGetValue(identifier, out value); public bool TryGetValue(EqpIdentifier identifier, out EqpEntryInternal value) - => _data?.Eqp.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _eqp.TryGetValue(identifier, out value); public bool TryGetValue(EqdpIdentifier identifier, out EqdpEntryInternal value) - => _data?.Eqdp.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _eqdp.TryGetValue(identifier, out value); public bool TryGetValue(GmpIdentifier identifier, out GmpEntry value) - => _data?.Gmp.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _gmp.TryGetValue(identifier, out value); public bool TryGetValue(RspIdentifier identifier, out RspEntry value) - => _data?.Rsp.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _rsp.TryGetValue(identifier, out value); public bool TryGetValue(ImcIdentifier identifier, out ImcEntry value) - => _data?.Imc.TryGetValue(identifier, out value) ?? SetDefault(out value); + => _imc.TryGetValue(identifier, out value); public bool TryGetValue(AtchIdentifier identifier, out AtchEntry value) - => _data?.Atch.TryGetValue(identifier, out value) ?? SetDefault(out value); - - public bool TryGetValue(ShpIdentifier identifier, out ShpEntry value) - => _data?.Shp.TryGetValue(identifier, out value) ?? SetDefault(out value); - - public bool TryGetValue(AtrIdentifier identifier, out AtrEntry value) - => _data?.Atr.TryGetValue(identifier, out value) ?? SetDefault(out value); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private static bool SetDefault(out T? value) - { - value = default; - return false; - } + => _atch.TryGetValue(identifier, out value); #endregion public bool Remove(IMetaIdentifier identifier) { - if (_data is null) - return false; - var ret = identifier switch { - EqdpIdentifier i => _data.Eqdp.Remove(i), - EqpIdentifier i => _data.Eqp.Remove(i), - EstIdentifier i => _data.Est.Remove(i), - GlobalEqpManipulation i => _data.Remove(i), - GmpIdentifier i => _data.Gmp.Remove(i), - ImcIdentifier i => _data.Imc.Remove(i), - RspIdentifier i => _data.Rsp.Remove(i), - AtchIdentifier i => _data.Atch.Remove(i), - ShpIdentifier i => _data.Shp.Remove(i), - AtrIdentifier i => _data.Atr.Remove(i), + EqdpIdentifier i => _eqdp.Remove(i), + EqpIdentifier i => _eqp.Remove(i), + EstIdentifier i => _est.Remove(i), + GlobalEqpManipulation i => _globalEqp.Remove(i), + GmpIdentifier i => _gmp.Remove(i), + ImcIdentifier i => _imc.Remove(i), + RspIdentifier i => _rsp.Remove(i), + AtchIdentifier i => _atch.Remove(i), _ => false, }; - if (ret && --Count is 0) - _data = null; - + if (ret) + --Count; return ret; } @@ -603,164 +323,110 @@ public class MetaDictionary public void UnionWith(MetaDictionary manips) { - if (manips.Count is 0) - return; - - _data ??= []; - foreach (var (identifier, entry) in manips._data!.Imc) + foreach (var (identifier, entry) in manips._imc) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Eqp) + foreach (var (identifier, entry) in manips._eqp) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Eqdp) + foreach (var (identifier, entry) in manips._eqdp) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Gmp) + foreach (var (identifier, entry) in manips._gmp) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Rsp) + foreach (var (identifier, entry) in manips._rsp) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Est) + foreach (var (identifier, entry) in manips._est) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Atch) + foreach (var (identifier, entry) in manips._atch) TryAdd(identifier, entry); - foreach (var (identifier, entry) in manips._data!.Shp) - TryAdd(identifier, entry); - - foreach (var (identifier, entry) in manips._data!.Atr) - TryAdd(identifier, entry); - - foreach (var identifier in manips._data!) + foreach (var identifier in manips._globalEqp) TryAdd(identifier); } /// Try to merge all manipulations from manips into this, and return the first failure, if any. public bool MergeForced(MetaDictionary manips, out IMetaIdentifier? failedIdentifier) { - if (manips.Count is 0) - { - failedIdentifier = null; - return true; - } - - _data ??= []; - foreach (var (identifier, _) in manips._data!.Imc.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._imc.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Eqp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._eqp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Eqdp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._eqdp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Gmp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._gmp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Rsp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._rsp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Est.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._est.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Atch.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var (identifier, _) in manips._atch.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Shp.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) + foreach (var identifier in manips._globalEqp.Where(identifier => !TryAdd(identifier))) { failedIdentifier = identifier; return false; } - foreach (var (identifier, _) in manips._data!.Atr.Where(kvp => !TryAdd(kvp.Key, kvp.Value))) - { - failedIdentifier = identifier; - return false; - } - - foreach (var identifier in manips._data!.Where(identifier => !TryAdd(identifier))) - { - failedIdentifier = identifier; - return false; - } - - failedIdentifier = null; + failedIdentifier = default; return true; } public void SetTo(MetaDictionary other) { - if (other.Count is 0) - { - _data = null; - Count = 0; - return; - } - - _data ??= []; - _data!.Imc.SetTo(other._data!.Imc); - _data!.Eqp.SetTo(other._data!.Eqp); - _data!.Eqdp.SetTo(other._data!.Eqdp); - _data!.Est.SetTo(other._data!.Est); - _data!.Rsp.SetTo(other._data!.Rsp); - _data!.Gmp.SetTo(other._data!.Gmp); - _data!.Atch.SetTo(other._data!.Atch); - _data!.Shp.SetTo(other._data!.Shp); - _data!.Atr.SetTo(other._data!.Atr); - _data!.SetTo(other._data!); - Count = other.Count; + _imc.SetTo(other._imc); + _eqp.SetTo(other._eqp); + _eqdp.SetTo(other._eqdp); + _est.SetTo(other._est); + _rsp.SetTo(other._rsp); + _gmp.SetTo(other._gmp); + _atch.SetTo(other._atch); + _globalEqp.SetTo(other._globalEqp); + Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count; } public void UpdateTo(MetaDictionary other) { - if (other.Count is 0) - return; - - _data ??= []; - _data!.Imc.UpdateTo(other._data!.Imc); - _data!.Eqp.UpdateTo(other._data!.Eqp); - _data!.Eqdp.UpdateTo(other._data!.Eqdp); - _data!.Est.UpdateTo(other._data!.Est); - _data!.Rsp.UpdateTo(other._data!.Rsp); - _data!.Gmp.UpdateTo(other._data!.Gmp); - _data!.Atch.UpdateTo(other._data!.Atch); - _data!.Shp.UpdateTo(other._data!.Shp); - _data!.Atr.UpdateTo(other._data!.Atr); - _data!.UnionWith(other._data!); - Count = _data!.Imc.Count - + _data!.Eqp.Count - + _data!.Eqdp.Count - + _data!.Est.Count - + _data!.Rsp.Count - + _data!.Gmp.Count - + _data!.Atch.Count - + _data!.Shp.Count - + _data!.Atr.Count - + _data!.Count; + _imc.UpdateTo(other._imc); + _eqp.UpdateTo(other._eqp); + _eqdp.UpdateTo(other._eqdp); + _est.UpdateTo(other._est); + _rsp.UpdateTo(other._rsp); + _gmp.UpdateTo(other._gmp); + _atch.UpdateTo(other._atch); + _globalEqp.UnionWith(other._globalEqp); + Count = _imc.Count + _eqp.Count + _eqdp.Count + _est.Count + _rsp.Count + _gmp.Count + _atch.Count + _globalEqp.Count; } #endregion @@ -848,26 +514,6 @@ public class MetaDictionary }), }; - public static JObject Serialize(ShpIdentifier identifier, ShpEntry entry) - => new() - { - ["Type"] = MetaManipulationType.Shp.ToString(), - ["Manipulation"] = identifier.AddToJson(new JObject - { - ["Entry"] = entry.Value, - }), - }; - - public static JObject Serialize(AtrIdentifier identifier, AtrEntry entry) - => new() - { - ["Type"] = MetaManipulationType.Atr.ToString(), - ["Manipulation"] = identifier.AddToJson(new JObject - { - ["Entry"] = entry.Value, - }), - }; - public static JObject Serialize(GlobalEqpManipulation identifier) => new() { @@ -897,10 +543,6 @@ public class MetaDictionary return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); if (typeof(TIdentifier) == typeof(AtchIdentifier) && typeof(TEntry) == typeof(AtchEntry)) return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); - if (typeof(TIdentifier) == typeof(ShpIdentifier) && typeof(TEntry) == typeof(ShpEntry)) - return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); - if (typeof(TIdentifier) == typeof(AtrIdentifier) && typeof(TEntry) == typeof(AtrEntry)) - return Serialize(Unsafe.As(ref identifier), Unsafe.As(ref entry)); if (typeof(TIdentifier) == typeof(GlobalEqpManipulation)) return Serialize(Unsafe.As(ref identifier)); @@ -939,20 +581,14 @@ public class MetaDictionary } var array = new JArray(); - if (value._data is not null) - { - SerializeTo(array, value._data!.Imc); - SerializeTo(array, value._data!.Eqp); - SerializeTo(array, value._data!.Eqdp); - SerializeTo(array, value._data!.Est); - SerializeTo(array, value._data!.Rsp); - SerializeTo(array, value._data!.Gmp); - SerializeTo(array, value._data!.Atch); - SerializeTo(array, value._data!.Shp); - SerializeTo(array, value._data!.Atr); - SerializeTo(array, value._data!); - } - + SerializeTo(array, value._imc); + SerializeTo(array, value._eqp); + SerializeTo(array, value._eqdp); + SerializeTo(array, value._est); + SerializeTo(array, value._rsp); + SerializeTo(array, value._gmp); + SerializeTo(array, value._atch); + SerializeTo(array, value._globalEqp); array.WriteTo(writer); } @@ -1049,26 +685,6 @@ public class MetaDictionary Penumbra.Log.Warning("Invalid ATCH Manipulation encountered."); break; } - case MetaManipulationType.Shp: - { - var identifier = ShpIdentifier.FromJson(manip); - var entry = new ShpEntry(manip["Entry"]?.Value() ?? true); - if (identifier.HasValue) - dict.TryAdd(identifier.Value, entry); - else - Penumbra.Log.Warning("Invalid SHP Manipulation encountered."); - break; - } - case MetaManipulationType.Atr: - { - var identifier = AtrIdentifier.FromJson(manip); - var entry = new AtrEntry(manip["Entry"]?.Value() ?? true); - if (identifier.HasValue) - dict.TryAdd(identifier.Value, entry); - else - Penumbra.Log.Warning("Invalid ATR Manipulation encountered."); - break; - } case MetaManipulationType.GlobalEqp: { var identifier = GlobalEqpManipulation.FromJson(manip); @@ -1090,30 +706,17 @@ public class MetaDictionary public MetaDictionary(MetaCache? cache) { - if (cache is null) + if (cache == null) return; - _data = new Wrapper(cache); - Count = cache.Count; - } - - public MetaDictionary(MetaCache? cache, Actor actor) - { - if (cache is null) - return; - - _data = Wrapper.Filtered(cache, actor); - Count = _data.Count - + _data.Eqp.Count - + _data.Eqdp.Count - + _data.Est.Count - + _data.Gmp.Count - + _data.Imc.Count - + _data.Rsp.Count - + _data.Atch.Count - + _data.Atr.Count - + _data.Shp.Count; - if (Count is 0) - _data = null; + _imc = cache.Imc.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _eqp = cache.Eqp.ToDictionary(kvp => kvp.Key, kvp => new EqpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); + _eqdp = cache.Eqdp.ToDictionary(kvp => kvp.Key, kvp => new EqdpEntryInternal(kvp.Value.Entry, kvp.Key.Slot)); + _est = cache.Est.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _gmp = cache.Gmp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _rsp = cache.Rsp.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _atch = cache.Atch.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Entry); + _globalEqp = cache.GlobalEqp.Select(kvp => kvp.Key).ToHashSet(); + Count = cache.Count; } } diff --git a/Penumbra/Meta/Manipulations/Rsp.cs b/Penumbra/Meta/Manipulations/Rsp.cs index 5f91a37c..2d73ec7f 100644 --- a/Penumbra/Meta/Manipulations/Rsp.cs +++ b/Penumbra/Meta/Manipulations/Rsp.cs @@ -8,8 +8,8 @@ namespace Penumbra.Meta.Manipulations; public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attribute) : IMetaIdentifier { - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - => changedItems.UpdateCountOrSet($"{SubRace.ToName()} {Attribute.ToFullString()}", () => new IdentifiedName()); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + => changedItems.TryAdd($"{SubRace.ToName()} {Attribute.ToFullString()}", null); public MetaIndex FileIndex() => MetaIndex.HumanCmp; @@ -40,9 +40,6 @@ public readonly record struct RspIdentifier(SubRace SubRace, RspAttribute Attrib public MetaManipulationType Type => MetaManipulationType.Rsp; - - public override string ToString() - => $"RSP - {SubRace.ToName()} - {Attribute.ToFullString()}"; } [JsonConverter(typeof(Converter))] diff --git a/Penumbra/Meta/Manipulations/ShpIdentifier.cs b/Penumbra/Meta/Manipulations/ShpIdentifier.cs deleted file mode 100644 index 0a5b71b7..00000000 --- a/Penumbra/Meta/Manipulations/ShpIdentifier.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Linq; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Structs; -using Penumbra.Meta.Files; - -namespace Penumbra.Meta.Manipulations; - -[JsonConverter(typeof(StringEnumConverter))] -public enum ShapeConnectorCondition : byte -{ - None = 0, - Wrists = 1, - Waist = 2, - Ankles = 3, -} - -public readonly record struct ShpIdentifier( - HumanSlot Slot, - PrimaryId? Id, - ShapeAttributeString Shape, - ShapeConnectorCondition ConnectorCondition, - GenderRace GenderRaceCondition) - : IComparable, IMetaIdentifier -{ - public int CompareTo(ShpIdentifier other) - { - var slotComparison = Slot.CompareTo(other.Slot); - if (slotComparison is not 0) - return slotComparison; - - if (Id.HasValue) - { - if (other.Id.HasValue) - { - var idComparison = Id.Value.Id.CompareTo(other.Id.Value.Id); - if (idComparison is not 0) - return idComparison; - } - else - { - return -1; - } - } - else if (other.Id.HasValue) - { - return 1; - } - - var conditionComparison = ConnectorCondition.CompareTo(other.ConnectorCondition); - if (conditionComparison is not 0) - return conditionComparison; - - var genderRaceComparison = GenderRaceCondition.CompareTo(other.GenderRaceCondition); - if (genderRaceComparison is not 0) - return genderRaceComparison; - - return Shape.CompareTo(other.Shape); - } - - - public override string ToString() - { - var sb = new StringBuilder(64); - sb.Append("Shp - ") - .Append(Shape); - if (Slot is HumanSlot.Unknown) - { - sb.Append(" - All Slots & IDs"); - } - else - { - sb.Append(" - ") - .Append(Slot.ToName()) - .Append(" - "); - if (Id.HasValue) - sb.Append(Id.Value.Id); - else - sb.Append("All IDs"); - } - - switch (ConnectorCondition) - { - case ShapeConnectorCondition.Wrists: sb.Append(" - Wrist Connector"); break; - case ShapeConnectorCondition.Waist: sb.Append(" - Waist Connector"); break; - case ShapeConnectorCondition.Ankles: sb.Append(" - Ankle Connector"); break; - } - - if (GenderRaceCondition is not GenderRace.Unknown) - sb.Append(" - ").Append(GenderRaceCondition.ToRaceCode()); - - return sb.ToString(); - } - - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - { - // Nothing for now since it depends entirely on the shape key. - } - - public MetaIndex FileIndex() - => (MetaIndex)(-1); - - public bool Validate() - { - if (!Enum.IsDefined(Slot) || Slot is HumanSlot.UnkBonus) - return false; - - if (!ShapeAttributeHashSet.GenderRaceIndices.ContainsKey(GenderRaceCondition)) - return false; - - if (!Enum.IsDefined(ConnectorCondition)) - return false; - - if (Slot is HumanSlot.Unknown && Id is not null) - return false; - - if (Slot.ToSpecificEnum() is BodySlot && Id is { Id: > byte.MaxValue }) - return false; - - if (Id is { Id: > ExpandedEqpGmpBase.Count - 1 }) - return false; - - if (!Shape.ValidateCustomShapeString()) - return false; - - return ConnectorCondition switch - { - ShapeConnectorCondition.None => true, - ShapeConnectorCondition.Wrists => Slot is HumanSlot.Body or HumanSlot.Hands or HumanSlot.Unknown, - ShapeConnectorCondition.Waist => Slot is HumanSlot.Body or HumanSlot.Legs or HumanSlot.Unknown, - ShapeConnectorCondition.Ankles => Slot is HumanSlot.Legs or HumanSlot.Feet or HumanSlot.Unknown, - _ => false, - }; - } - - public JObject AddToJson(JObject jObj) - { - if (Slot is not HumanSlot.Unknown) - jObj["Slot"] = Slot.ToString(); - if (Id.HasValue) - jObj["Id"] = Id.Value.Id.ToString(); - jObj["Shape"] = Shape.ToString(); - if (ConnectorCondition is not ShapeConnectorCondition.None) - jObj["ConnectorCondition"] = ConnectorCondition.ToString(); - if (GenderRaceCondition is not GenderRace.Unknown) - jObj["GenderRaceCondition"] = (uint)GenderRaceCondition; - return jObj; - } - - public static ShpIdentifier? FromJson(JObject jObj) - { - var shape = jObj["Shape"]?.ToObject(); - if (shape is null || !ShapeAttributeString.TryRead(shape, out var shapeString)) - return null; - - var slot = jObj["Slot"]?.ToObject() ?? HumanSlot.Unknown; - var id = jObj["Id"]?.ToObject(); - var connectorCondition = jObj["ConnectorCondition"]?.ToObject() ?? ShapeConnectorCondition.None; - var genderRaceCondition = jObj["GenderRaceCondition"]?.ToObject() ?? 0; - var identifier = new ShpIdentifier(slot, id, shapeString, connectorCondition, genderRaceCondition); - return identifier.Validate() ? identifier : null; - } - - public MetaManipulationType Type - => MetaManipulationType.Shp; -} - -[JsonConverter(typeof(Converter))] -public readonly record struct ShpEntry(bool Value) -{ - public static readonly ShpEntry True = new(true); - public static readonly ShpEntry False = new(false); - - private class Converter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ShpEntry value, JsonSerializer serializer) - => serializer.Serialize(writer, value.Value); - - public override ShpEntry ReadJson(JsonReader reader, Type objectType, ShpEntry existingValue, bool hasExistingValue, - JsonSerializer serializer) - => new(serializer.Deserialize(reader)); - } -} diff --git a/Penumbra/Meta/MetaFileManager.cs b/Penumbra/Meta/MetaFileManager.cs index 6130a48f..5250273b 100644 --- a/Penumbra/Meta/MetaFileManager.cs +++ b/Penumbra/Meta/MetaFileManager.cs @@ -28,26 +28,24 @@ public class MetaFileManager : IService internal readonly ImcChecker ImcChecker; internal readonly AtchManager AtchManager; internal readonly IFileAllocator MarshalAllocator = new MarshalAllocator(); - internal readonly IFileAllocator XivFileAllocator; - internal readonly IFileAllocator XivDefaultAllocator; + internal readonly IFileAllocator XivAllocator; public MetaFileManager(CharacterUtility characterUtility, ResidentResourceManager residentResources, IDataManager gameData, ActiveCollectionData activeCollections, Configuration config, ValidityChecker validityChecker, ObjectIdentification identifier, FileCompactor compactor, IGameInteropProvider interop, AtchManager atchManager) { - CharacterUtility = characterUtility; - ResidentResources = residentResources; - GameData = gameData; - ActiveCollections = activeCollections; - Config = config; - ValidityChecker = validityChecker; - Identifier = identifier; - Compactor = compactor; - AtchManager = atchManager; - ImcChecker = new ImcChecker(this); - XivFileAllocator = new XivFileAllocator(interop); - XivDefaultAllocator = new XivDefaultAllocator(); + CharacterUtility = characterUtility; + ResidentResources = residentResources; + GameData = gameData; + ActiveCollections = activeCollections; + Config = config; + ValidityChecker = validityChecker; + Identifier = identifier; + Compactor = compactor; + AtchManager = atchManager; + ImcChecker = new ImcChecker(this); + XivAllocator = new XivFileAllocator(interop); interop.InitializeFromAttributes(this); } diff --git a/Penumbra/Meta/ShapeAttributeManager.cs b/Penumbra/Meta/ShapeAttributeManager.cs deleted file mode 100644 index a7f71ac7..00000000 --- a/Penumbra/Meta/ShapeAttributeManager.cs +++ /dev/null @@ -1,227 +0,0 @@ -using OtterGui.Services; -using Penumbra.Collections; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; -using Penumbra.Interop.Hooks.PostProcessing; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.Meta; - -public unsafe class ShapeAttributeManager : IRequiredService, IDisposable -{ - public const int NumSlots = 14; - public const int ModelSlotSize = 18; - private readonly AttributeHook _attributeHook; - - public static ReadOnlySpan UsedModels - => - [ - HumanSlot.Head, HumanSlot.Body, HumanSlot.Hands, HumanSlot.Legs, HumanSlot.Feet, HumanSlot.Ears, HumanSlot.Neck, HumanSlot.Wrists, - HumanSlot.RFinger, HumanSlot.LFinger, HumanSlot.Glasses, HumanSlot.Hair, HumanSlot.Face, HumanSlot.Ear, - ]; - - public ShapeAttributeManager(AttributeHook attributeHook) - { - _attributeHook = attributeHook; - _attributeHook.Subscribe(OnAttributeComputed, AttributeHook.Priority.ShapeAttributeManager); - } - - private readonly Dictionary[] _temporaryShapes = - Enumerable.Range(0, NumSlots).Select(_ => new Dictionary()).ToArray(); - - private readonly PrimaryId[] _ids = new PrimaryId[ModelSlotSize]; - - private HumanSlot _modelIndex; - private int _slotIndex; - private GenderRace _genderRace; - - private FFXIVClientStructs.FFXIV.Client.Graphics.Render.Model* _model; - - public void Dispose() - => _attributeHook.Unsubscribe(OnAttributeComputed); - - private void OnAttributeComputed(Actor actor, Model model, ModCollection collection) - { - if (!collection.HasCache) - return; - - _genderRace = (GenderRace)model.AsHuman->RaceSexId; - for (_slotIndex = 0; _slotIndex < NumSlots; ++_slotIndex) - { - _modelIndex = UsedModels[_slotIndex]; - _model = model.AsHuman->Models[_modelIndex.ToIndex()]; - if (_model is null || _model->ModelResourceHandle is null) - continue; - - _ids[(int)_modelIndex] = model.GetModelId(_modelIndex); - CheckShapes(collection.MetaCache!.Shp); - CheckAttributes(collection.MetaCache!.Atr); - if (_modelIndex is <= HumanSlot.LFinger and >= HumanSlot.Ears) - AccessoryImcCheck(model); - } - - UpdateDefaultMasks(model, collection.MetaCache!.Shp); - } - - private void AccessoryImcCheck(Model model) - { - var imcMask = (ushort)(0x03FF & *(ushort*)(model.Address + 0xAAC + 6 * (int)_modelIndex)); - - Span attr = - [ - (byte)'a', - (byte)'t', - (byte)'r', - (byte)'_', - AccessoryByte(_modelIndex), - (byte)'v', - (byte)'_', - (byte)'a', - 0, - ]; - for (var i = 1; i < 10; ++i) - { - var flag = (ushort)(1 << i); - if ((imcMask & flag) is not 0) - continue; - - attr[^2] = (byte)('a' + i); - - foreach (var (attribute, index) in _model->ModelResourceHandle->Attributes) - { - if (!EqualAttribute(attr, attribute.Value)) - continue; - - _model->EnabledAttributeIndexMask &= ~(1u << index); - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] - private static bool EqualAttribute(Span needle, byte* haystack) - { - foreach (var character in needle) - { - if (*haystack++ != character) - return false; - } - - return true; - } - - private static byte AccessoryByte(HumanSlot slot) - => slot switch - { - HumanSlot.Head => (byte)'m', - HumanSlot.Ears => (byte)'e', - HumanSlot.Neck => (byte)'n', - HumanSlot.Wrists => (byte)'w', - HumanSlot.RFinger => (byte)'r', - HumanSlot.LFinger => (byte)'r', - _ => 0, - }; - - private void CheckAttributes(AtrCache attributeCache) - { - if (attributeCache.DisabledCount is 0) - return; - - ref var attributes = ref _model->ModelResourceHandle->Attributes; - foreach (var (attribute, index) in attributes.Where(kvp => ShapeAttributeString.ValidateCustomAttributeString(kvp.Key.Value))) - { - if (ShapeAttributeString.TryRead(attribute.Value, out var attributeString)) - { - // Mask out custom attributes if they are disabled. Attributes are enabled by default. - if (attributeCache.ShouldBeDisabled(attributeString, _modelIndex, _ids[_modelIndex.ToIndex()], _genderRace)) - _model->EnabledAttributeIndexMask &= ~(1u << index); - } - else - { - Penumbra.Log.Warning($"Trying to read a attribute string that is too long: {attribute}."); - } - } - } - - private void CheckShapes(ShpCache shapeCache) - { - _temporaryShapes[_slotIndex].Clear(); - ref var shapes = ref _model->ModelResourceHandle->Shapes; - foreach (var (shape, index) in shapes.Where(kvp => ShapeAttributeString.ValidateCustomShapeString(kvp.Key.Value))) - { - if (ShapeAttributeString.TryRead(shape.Value, out var shapeString)) - { - _temporaryShapes[_slotIndex].TryAdd(shapeString, index); - // Add custom shapes if they are enabled. Shapes are disabled by default. - if (shapeCache.ShouldBeEnabled(shapeString, _modelIndex, _ids[_modelIndex.ToIndex()], _genderRace)) - _model->EnabledShapeKeyIndexMask |= 1u << index; - } - else - { - Penumbra.Log.Warning($"Trying to read a shape string that is too long: {shape}."); - } - } - } - - private void UpdateDefaultMasks(Model human, ShpCache cache) - { - var genderRace = (GenderRace)human.AsHuman->RaceSexId; - foreach (var (shape, topIndex) in _temporaryShapes[1]) - { - if (shape.IsWrist() - && _temporaryShapes[2].TryGetValue(shape, out var handIndex) - && !cache.ShouldBeDisabled(shape, HumanSlot.Body, _ids[1], genderRace) - && !cache.ShouldBeDisabled(shape, HumanSlot.Hands, _ids[2], genderRace) - && human.AsHuman->Models[1] is not null - && human.AsHuman->Models[2] is not null) - { - human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex; - human.AsHuman->Models[2]->EnabledShapeKeyIndexMask |= 1u << handIndex; - CheckCondition(cache.State(ShapeConnectorCondition.Wrists), genderRace, HumanSlot.Body, HumanSlot.Hands, 1, 2); - } - - if (shape.IsWaist() - && _temporaryShapes[3].TryGetValue(shape, out var legIndex) - && !cache.ShouldBeDisabled(shape, HumanSlot.Body, _ids[1], genderRace) - && !cache.ShouldBeDisabled(shape, HumanSlot.Legs, _ids[3], genderRace) - && human.AsHuman->Models[1] is not null - && human.AsHuman->Models[3] is not null) - { - human.AsHuman->Models[1]->EnabledShapeKeyIndexMask |= 1u << topIndex; - human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << legIndex; - CheckCondition(cache.State(ShapeConnectorCondition.Waist), genderRace, HumanSlot.Body, HumanSlot.Legs, 1, 3); - } - } - - foreach (var (shape, bottomIndex) in _temporaryShapes[3]) - { - if (shape.IsAnkle() - && _temporaryShapes[4].TryGetValue(shape, out var footIndex) - && !cache.ShouldBeDisabled(shape, HumanSlot.Legs, _ids[3], genderRace) - && !cache.ShouldBeDisabled(shape, HumanSlot.Feet, _ids[4], genderRace) - && human.AsHuman->Models[3] is not null - && human.AsHuman->Models[4] is not null) - { - human.AsHuman->Models[3]->EnabledShapeKeyIndexMask |= 1u << bottomIndex; - human.AsHuman->Models[4]->EnabledShapeKeyIndexMask |= 1u << footIndex; - CheckCondition(cache.State(ShapeConnectorCondition.Ankles), genderRace, HumanSlot.Legs, HumanSlot.Feet, 3, 4); - } - } - - return; - - void CheckCondition(IReadOnlyDictionary dict, GenderRace genderRace, HumanSlot slot1, - HumanSlot slot2, int idx1, int idx2) - { - foreach (var (shape, set) in dict) - { - if (set.CheckEntry(slot1, _ids[idx1], genderRace) is true && _temporaryShapes[idx1].TryGetValue(shape, out var index1)) - human.AsHuman->Models[idx1]->EnabledShapeKeyIndexMask |= 1u << index1; - if (set.CheckEntry(slot2, _ids[idx2], genderRace) is true && _temporaryShapes[idx2].TryGetValue(shape, out var index2)) - human.AsHuman->Models[idx2]->EnabledShapeKeyIndexMask |= 1u << index2; - } - } - } -} diff --git a/Penumbra/Meta/ShapeAttributeString.cs b/Penumbra/Meta/ShapeAttributeString.cs deleted file mode 100644 index 55e3f021..00000000 --- a/Penumbra/Meta/ShapeAttributeString.cs +++ /dev/null @@ -1,203 +0,0 @@ -using Lumina.Misc; -using Newtonsoft.Json; -using Penumbra.GameData.Files.PhybStructs; -using Penumbra.String.Functions; - -namespace Penumbra.Meta; - -[JsonConverter(typeof(Converter))] -public struct ShapeAttributeString : IEquatable, IComparable -{ - public const int MaxLength = 30; - - public static readonly ShapeAttributeString Empty = new(); - - private FixedString32 _buffer; - - public int Count - => _buffer[31]; - - public int Length - => _buffer[31]; - - public override string ToString() - => Encoding.UTF8.GetString(_buffer[..Length]); - - public byte this[int index] - => _buffer[index]; - - public unsafe ReadOnlySpan AsSpan - { - get - { - fixed (void* ptr = &this) - { - return new ReadOnlySpan(ptr, Length); - } - } - } - - public static unsafe bool ValidateCustomShapeString(byte* shape) - { - // "shpx_*" - if (shape is null) - return false; - - if (*shape++ is not (byte)'s' - || *shape++ is not (byte)'h' - || *shape++ is not (byte)'p' - || *shape++ is not (byte)'x' - || *shape++ is not (byte)'_' - || *shape is 0) - return false; - - return true; - } - - public bool ValidateCustomShapeString() - { - // "shpx_*" - if (Length < 6) - return false; - - if (_buffer[0] is not (byte)'s' - || _buffer[1] is not (byte)'h' - || _buffer[2] is not (byte)'p' - || _buffer[3] is not (byte)'x' - || _buffer[4] is not (byte)'_') - return false; - - return true; - } - - public static unsafe bool ValidateCustomAttributeString(byte* shape) - { - // "atrx_*" - if (shape is null) - return false; - - if (*shape++ is not (byte)'a' - || *shape++ is not (byte)'t' - || *shape++ is not (byte)'r' - || *shape++ is not (byte)'x' - || *shape++ is not (byte)'_' - || *shape is 0) - return false; - - return true; - } - - public bool ValidateCustomAttributeString() - { - // "atrx_*" - if (Length < 6) - return false; - - if (_buffer[0] is not (byte)'a' - || _buffer[1] is not (byte)'t' - || _buffer[2] is not (byte)'r' - || _buffer[3] is not (byte)'x' - || _buffer[4] is not (byte)'_') - return false; - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsAnkle() - => CheckCenter('a', 'n'); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsWaist() - => CheckCenter('w', 'a'); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public bool IsWrist() - => CheckCenter('w', 'r'); - - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - private bool CheckCenter(char first, char second) - => Length > 8 && _buffer[5] == first && _buffer[6] == second && _buffer[7] is (byte)'_'; - - public bool Equals(ShapeAttributeString other) - => Length == other.Length && _buffer[..Length].SequenceEqual(other._buffer[..Length]); - - public override bool Equals(object? obj) - => obj is ShapeAttributeString other && Equals(other); - - public override int GetHashCode() - => (int)Crc32.Get(_buffer[..Length]); - - public static bool operator ==(ShapeAttributeString left, ShapeAttributeString right) - => left.Equals(right); - - public static bool operator !=(ShapeAttributeString left, ShapeAttributeString right) - => !left.Equals(right); - - public static unsafe bool TryRead(byte* pointer, out ShapeAttributeString ret) - { - var span = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pointer); - return TryRead(span, out ret); - } - - public unsafe int CompareTo(ShapeAttributeString other) - { - fixed (void* lhs = &this) - { - return ByteStringFunctions.Compare((byte*)lhs, Length, (byte*)&other, other.Length); - } - } - - public static bool TryRead(ReadOnlySpan utf8, out ShapeAttributeString ret) - { - if (utf8.Length is 0 or > MaxLength) - { - ret = Empty; - return false; - } - - ret = Empty; - utf8.CopyTo(ret._buffer); - ret._buffer[utf8.Length] = 0; - ret._buffer[31] = (byte)utf8.Length; - return true; - } - - public static bool TryRead(ReadOnlySpan utf16, out ShapeAttributeString ret) - { - ret = Empty; - if (!Encoding.UTF8.TryGetBytes(utf16, ret._buffer[..MaxLength], out var written)) - return false; - - ret._buffer[written] = 0; - ret._buffer[31] = (byte)written; - return true; - } - - public void ForceLength(byte length) - { - if (length > MaxLength) - length = MaxLength; - _buffer[length] = 0; - _buffer[31] = length; - } - - private sealed class Converter : JsonConverter - { - public override void WriteJson(JsonWriter writer, ShapeAttributeString value, JsonSerializer serializer) - { - writer.WriteValue(value.ToString()); - } - - public override ShapeAttributeString ReadJson(JsonReader reader, Type objectType, ShapeAttributeString existingValue, - bool hasExistingValue, - JsonSerializer serializer) - { - var value = serializer.Deserialize(reader); - if (!TryRead(value, out existingValue)) - throw new JsonReaderException($"Could not parse {value} into ShapeAttributeString."); - - return existingValue; - } - } -} diff --git a/Penumbra/Mods/Editor/MdlMaterialEditor.cs b/Penumbra/Mods/Editor/MdlMaterialEditor.cs index da580794..2a23ffad 100644 --- a/Penumbra/Mods/Editor/MdlMaterialEditor.cs +++ b/Penumbra/Mods/Editor/MdlMaterialEditor.cs @@ -1,5 +1,5 @@ +using OtterGui; using OtterGui.Compression; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; diff --git a/Penumbra/Mods/Editor/ModFileCollection.cs b/Penumbra/Mods/Editor/ModFileCollection.cs index 15bd179e..20423493 100644 --- a/Penumbra/Mods/Editor/ModFileCollection.cs +++ b/Penumbra/Mods/Editor/ModFileCollection.cs @@ -1,5 +1,4 @@ using OtterGui; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.Mods.SubMods; using Penumbra.String.Classes; @@ -14,7 +13,6 @@ public class ModFileCollection : IDisposable, IService private readonly List _tex = []; private readonly List _shpk = []; private readonly List _pbd = []; - private readonly List _atch = []; private readonly SortedSet _missing = []; private readonly HashSet _usedPaths = []; @@ -43,24 +41,21 @@ public class ModFileCollection : IDisposable, IService public IReadOnlyList Pbd => Ready ? _pbd : []; - public IReadOnlyList Atch - => Ready ? _atch : []; - public bool Ready { get; private set; } = true; public void UpdateAll(Mod mod, IModDataContainer option) { - UpdateFiles(mod, CancellationToken.None); - UpdatePaths(mod, option, false, CancellationToken.None); + UpdateFiles(mod, new CancellationToken()); + UpdatePaths(mod, option, false, new CancellationToken()); } public void UpdatePaths(Mod mod, IModDataContainer option) - => UpdatePaths(mod, option, true, CancellationToken.None); + => UpdatePaths(mod, option, true, new CancellationToken()); public void Clear() { ClearFiles(); - ClearPaths(false, CancellationToken.None); + ClearPaths(false, new CancellationToken()); } public void Dispose() @@ -140,9 +135,6 @@ public class ModFileCollection : IDisposable, IService case ".pbd": _pbd.Add(registry); break; - case ".atch": - _atch.Add(registry); - break; } } } @@ -155,7 +147,6 @@ public class ModFileCollection : IDisposable, IService _tex.Clear(); _shpk.Clear(); _pbd.Clear(); - _atch.Clear(); } private void ClearPaths(bool clearRegistries, CancellationToken tok) diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index eb270e13..e3eb5f54 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -2,11 +2,9 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Utility; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.Api.Enums; using Penumbra.Communication; -using Penumbra.Mods.Groups; using Penumbra.Mods.Manager; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; @@ -45,13 +43,13 @@ public class ModMerger : IDisposable, IService public ModMerger(ModManager mods, ModGroupEditor editor, ModSelection selection, DuplicateManager duplicates, CommunicatorService communicator, ModCreator creator, Configuration config) { - _editor = editor; - _selection = selection; - _duplicates = duplicates; - _communicator = communicator; - _creator = creator; - _config = config; - _mods = mods; + _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); } @@ -100,117 +98,26 @@ public class ModMerger : IDisposable, IService foreach (var originalGroup in MergeFromMod!.Groups) { - switch (originalGroup.Type) + var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, originalGroup.Type, originalGroup.Name); + if (groupCreated) + _createdGroups.Add(groupIdx); + if (group == null) + throw new Exception( + $"The merged group {originalGroup.Name} already existed, but had a different type than the original group of type {originalGroup.Type}."); + + foreach (var originalOption in originalGroup.DataContainers) { - case GroupType.Single: - case GroupType.Multi: + var (option, _, optionCreated) = _editor.FindOrAddOption(group, originalOption.GetName()); + if (optionCreated) { - var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, originalGroup.Type, originalGroup.Name); - if (group is null) - throw new Exception( - $"The merged group {originalGroup.Name} already existed, but had a different type than the original group of type {originalGroup.Type}."); - - if (groupCreated) - { - _createdGroups.Add(groupIdx); - group.Description = originalGroup.Description; - group.Image = originalGroup.Image; - group.DefaultSettings = originalGroup.DefaultSettings; - group.Page = originalGroup.Page; - group.Priority = originalGroup.Priority; - } - - foreach (var originalOption in originalGroup.Options) - { - var (option, _, optionCreated) = _editor.FindOrAddOption(group, originalOption.Name); - if (optionCreated) - { - _createdOptions.Add(option!); - MergeIntoOption([(IModDataContainer)originalOption], (IModDataContainer)option!, false); - option!.Description = originalOption.Description; - if (option is MultiSubMod multiOption) - multiOption.Priority = ((MultiSubMod)originalOption).Priority; - } - else - { - throw new Exception( - $"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: The option {option!.FullName} already existed."); - } - } - - break; + _createdOptions.Add(option!); + // #TODO DataContainer <> Option. + MergeIntoOption([originalOption], (IModDataContainer)option!, false); } - - case GroupType.Imc when originalGroup is ImcModGroup imc: + else { - var group = _editor.ImcEditor.AddModGroup(MergeToMod!, imc.Name, imc.Identifier, imc.DefaultEntry); - if (group is null) - throw new Exception( - $"The merged group {originalGroup.Name} already existed, but groups of type {originalGroup.Type} can not be merged."); - - group.AllVariants = imc.AllVariants; - group.OnlyAttributes = imc.OnlyAttributes; - group.Description = imc.Description; - group.Image = imc.Image; - group.DefaultSettings = imc.DefaultSettings; - group.Page = imc.Page; - group.Priority = imc.Priority; - foreach (var originalOption in imc.OptionData) - { - if (originalOption.IsDisableSubMod) - { - _editor.ImcEditor.ChangeCanBeDisabled(group, true); - var disable = group.OptionData.First(s => s.IsDisableSubMod); - disable.Description = originalOption.Description; - disable.Name = originalOption.Name; - continue; - } - - var newOption = _editor.ImcEditor.AddOption(group, originalOption.Name); - if (newOption is null) - throw new Exception( - $"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: Unknown error when creating IMC option {originalOption.FullName}."); - - newOption.Description = originalOption.Description; - newOption.AttributeMask = originalOption.AttributeMask; - } - - break; - } - case GroupType.Combining when originalGroup is CombiningModGroup combining: - { - var group = _editor.CombiningEditor.AddModGroup(MergeToMod!, combining.Name); - if (group is null) - throw new Exception( - $"The merged group {originalGroup.Name} already existed, but groups of type {originalGroup.Type} can not be merged."); - - group.Description = combining.Description; - group.Image = combining.Image; - group.DefaultSettings = combining.DefaultSettings; - group.Page = combining.Page; - group.Priority = combining.Priority; - foreach (var originalOption in combining.OptionData) - { - var option = _editor.CombiningEditor.AddOption(group, originalOption.Name); - if (option is null) - throw new Exception( - $"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: Unknown error when creating combining option {originalOption.FullName}."); - - option.Description = originalOption.Description; - } - - if (group.Data.Count != combining.Data.Count) - throw new Exception( - $"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: Unknown error caused data container counts in combining group {originalGroup.Name} to differ."); - - foreach (var (originalContainer, container) in combining.Data.Zip(group.Data)) - { - container.Name = originalContainer.Name; - MergeIntoOption([originalContainer], container, false); - } - - - break; + throw new Exception( + $"Could not merge {MergeFromMod!.Name} into {MergeToMod!.Name}: The option {option!.FullName} already existed."); } } } @@ -243,6 +150,7 @@ public class ModMerger : IDisposable, IService if (!dir.Exists) _createdDirectories.Add(dir.FullName); CopyFiles(dir); + // #TODO DataContainer <> Option. MergeIntoOption(MergeFromMod!.AllDataContainers.Reverse(), (IModDataContainer)option!, true); } @@ -372,6 +280,7 @@ public class ModMerger : IDisposable, IService } else { + // TODO DataContainer <> Option. var (group, _, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name); var (option, _, _) = _editor.FindOrAddOption(group!, originalOption.GetName()); var folder = Path.Combine(dir.FullName, group!.Name, option!.Name); diff --git a/Penumbra/Mods/Editor/ModMetaEditor.cs b/Penumbra/Mods/Editor/ModMetaEditor.cs index b4db457d..7a5142dc 100644 --- a/Penumbra/Mods/Editor/ModMetaEditor.cs +++ b/Penumbra/Mods/Editor/ModMetaEditor.cs @@ -1,15 +1,10 @@ using System.Collections.Frozen; -using OtterGui.Classes; using OtterGui.Services; -using Penumbra.Collections.Cache; using Penumbra.Meta; using Penumbra.Meta.Files; using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Groups; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.SubMods; -using Penumbra.Services; -using static Penumbra.GameData.Files.ShpkFile; namespace Penumbra.Mods.Editor; @@ -64,8 +59,6 @@ public class ModMetaEditor( OtherData[MetaManipulationType.Est].Add(name, option.Manipulations.GetCount(MetaManipulationType.Est)); OtherData[MetaManipulationType.Rsp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Rsp)); OtherData[MetaManipulationType.Atch].Add(name, option.Manipulations.GetCount(MetaManipulationType.Atch)); - OtherData[MetaManipulationType.Shp].Add(name, option.Manipulations.GetCount(MetaManipulationType.Shp)); - OtherData[MetaManipulationType.Atr].Add(name, option.Manipulations.GetCount(MetaManipulationType.Atr)); OtherData[MetaManipulationType.GlobalEqp].Add(name, option.Manipulations.GetCount(MetaManipulationType.GlobalEqp)); } @@ -74,164 +67,12 @@ public class ModMetaEditor( Changes = false; } - public static bool DeleteDefaultValues(Mod mod, MetaFileManager metaFileManager, SaveService? saveService, bool deleteAll = false) + public static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict) { - if (deleteAll) - { - var changes = false; - foreach (var container in mod.AllDataContainers) - { - if (!DeleteDefaultValues(metaFileManager, container.Manipulations)) - continue; - - saveService?.ImmediateSaveSync(new ModSaveGroup(container, metaFileManager.Config.ReplaceNonAsciiOnImport)); - changes = true; - } - - return changes; - } - - var defaultEntries = new MultiDictionary(); - var actualEntries = new HashSet(); - if (!FilterDefaultValues(mod.AllDataContainers, metaFileManager, defaultEntries, actualEntries)) - return false; - - var groups = new HashSet(); - DefaultSubMod? defaultMod = null; - foreach (var (defaultIdentifier, containers) in defaultEntries.Grouped) - { - if (!deleteAll && actualEntries.Contains(defaultIdentifier)) - continue; - - foreach (var container in containers) - { - if (!container.Manipulations.Remove(defaultIdentifier)) - continue; - - Penumbra.Log.Verbose($"Deleted default-valued meta-entry {defaultIdentifier}."); - if (container.Group is { } group) - groups.Add(group); - else if (container is DefaultSubMod d) - defaultMod = d; - } - } - - if (saveService is not null) - { - if (defaultMod is not null) - saveService.ImmediateSaveSync(new ModSaveGroup(defaultMod, metaFileManager.Config.ReplaceNonAsciiOnImport)); - foreach (var group in groups) - saveService.ImmediateSaveSync(new ModSaveGroup(group, metaFileManager.Config.ReplaceNonAsciiOnImport)); - } - - return defaultMod is not null || groups.Count > 0; - } - - public void DeleteDefaultValues() - => Changes = DeleteDefaultValues(metaFileManager, this); - - public void Apply(IModDataContainer container) - { - if (!Changes) - return; - - groupEditor.SetManipulations(container, this); - Changes = false; - } - - private static bool FilterDefaultValues(IEnumerable containers, MetaFileManager metaFileManager, - MultiDictionary defaultEntries, HashSet actualEntries) - { - if (!metaFileManager.CharacterUtility.Ready) - { - Penumbra.Log.Warning("Trying to filter default meta values before CharacterUtility was ready, skipped."); - return false; - } - - foreach (var container in containers) - { - foreach (var (key, value) in container.Manipulations.Imc) - { - var defaultEntry = ImcChecker.GetDefaultEntry(key, false); - if (defaultEntry.Entry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Eqp) - { - var defaultEntry = new EqpEntryInternal(ExpandedEqpFile.GetDefault(metaFileManager, key.SetId), key.Slot); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Eqdp) - { - var defaultEntry = new EqdpEntryInternal(ExpandedEqdpFile.GetDefault(metaFileManager, key), key.Slot); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Est) - { - var defaultEntry = EstFile.GetDefault(metaFileManager, key); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Gmp) - { - var defaultEntry = ExpandedGmpFile.GetDefault(metaFileManager, key); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Rsp) - { - var defaultEntry = CmpFile.GetDefault(metaFileManager, key.SubRace, key.Attribute); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - - foreach (var (key, value) in container.Manipulations.Atch) - { - var defaultEntry = AtchCache.GetDefault(metaFileManager, key); - if (defaultEntry.Equals(value)) - defaultEntries.TryAdd(key, container); - else - actualEntries.Add(key); - } - } - - return true; - } - - private static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict) - { - if (!metaFileManager.CharacterUtility.Ready) - { - Penumbra.Log.Warning("Trying to delete default meta values before CharacterUtility was ready, skipped."); - return false; - } - var clone = dict.Clone(); dict.ClearForDefault(); var count = 0; - foreach (var value in clone.GlobalEqp) - dict.TryAdd(value); - foreach (var (key, value) in clone.Imc) { var defaultEntry = ImcChecker.GetDefaultEntry(key, false); @@ -316,27 +157,22 @@ public class ModMetaEditor( } } - foreach (var (key, value) in clone.Atch) - { - var defaultEntry = AtchCache.GetDefault(metaFileManager, key); - if (!defaultEntry.HasValue) - continue; - - if (!defaultEntry.Value.Equals(value)) - { - dict.TryAdd(key, value); - } - else - { - Penumbra.Log.Verbose($"Deleted default-valued meta-entry {key}."); - ++count; - } - } - 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(metaFileManager, this); + + public void Apply(IModDataContainer container) + { + if (!Changes) + return; + + groupEditor.SetManipulations(container, this); + Changes = false; + } } diff --git a/Penumbra/Mods/Editor/ModNormalizer.cs b/Penumbra/Mods/Editor/ModNormalizer.cs index df1528f6..3e367a3b 100644 --- a/Penumbra/Mods/Editor/ModNormalizer.cs +++ b/Penumbra/Mods/Editor/ModNormalizer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.ImGuiNotification; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Services; using OtterGui.Tasks; using Penumbra.Mods.Groups; @@ -76,7 +76,7 @@ public class ModNormalizer(ModManager modManager, Configuration config, SaveServ else { var groupDir = ModCreator.NewOptionDirectory(mod.ModPath, container.Group.Name, config.ReplaceNonAsciiOnImport); - var optionDir = ModCreator.NewOptionDirectory(groupDir, container.GetDirectoryName(), config.ReplaceNonAsciiOnImport); + var optionDir = ModCreator.NewOptionDirectory(groupDir, container.GetName(), config.ReplaceNonAsciiOnImport); containers[container] = optionDir.FullName; } } @@ -286,7 +286,7 @@ public class ModNormalizer(ModManager modManager, Configuration config, SaveServ void HandleSubMod(DirectoryInfo groupDir, IModDataContainer option, Dictionary newDict) { - var name = option.GetDirectoryName(); + var name = option.GetName(); var optionDir = ModCreator.CreateModFolder(groupDir, name, config.ReplaceNonAsciiOnImport, true); newDict.Clear(); diff --git a/Penumbra/Mods/Editor/ModelMaterialInfo.cs b/Penumbra/Mods/Editor/ModelMaterialInfo.cs index fe46048f..741c2388 100644 --- a/Penumbra/Mods/Editor/ModelMaterialInfo.cs +++ b/Penumbra/Mods/Editor/ModelMaterialInfo.cs @@ -1,5 +1,5 @@ +using OtterGui; using OtterGui.Compression; -using OtterGui.Extensions; using Penumbra.GameData.Files; using Penumbra.String.Classes; diff --git a/Penumbra/Mods/FeatureChecker.cs b/Penumbra/Mods/FeatureChecker.cs deleted file mode 100644 index 10874fc9..00000000 --- a/Penumbra/Mods/FeatureChecker.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Frozen; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; -using OtterGui.Text; -using Penumbra.Mods.Manager; -using Penumbra.UI.Classes; -using Notification = OtterGui.Classes.Notification; - -namespace Penumbra.Mods; - -public static class FeatureChecker -{ - /// Manually setup supported features to exclude None and Invalid and not make something supported too early. - private static readonly FrozenDictionary SupportedFlags = new[] - { - FeatureFlags.Atch, - FeatureFlags.Shp, - FeatureFlags.Atr, - }.ToFrozenDictionary(f => f.ToString(), f => f); - - public static IReadOnlyCollection SupportedFeatures - => SupportedFlags.Keys; - - public static FeatureFlags ParseFlags(string modDirectory, string modName, IEnumerable features) - { - var featureFlags = FeatureFlags.None; - HashSet missingFeatures = []; - foreach (var feature in features) - { - if (SupportedFlags.TryGetValue(feature, out var featureFlag)) - featureFlags |= featureFlag; - else - missingFeatures.Add(feature); - } - - if (missingFeatures.Count > 0) - { - Penumbra.Messager.AddMessage(new Notification( - $"Please update Penumbra to use the mod {modName}{(modDirectory != modName ? $" at {modDirectory}" : string.Empty)}!\n\nLoading failed because it requires the unsupported feature{(missingFeatures.Count > 1 ? $"s\n\n\t[{string.Join("], [", missingFeatures)}]." : $" [{missingFeatures.First()}].")}", - NotificationType.Warning)); - return FeatureFlags.Invalid; - } - - return featureFlags; - } - - public static bool Supported(string features) - => SupportedFlags.ContainsKey(features); - - public static void DrawFeatureFlagInput(ModDataEditor editor, Mod mod, float width) - { - const int numButtons = 5; - var innerSpacing = ImGui.GetStyle().ItemInnerSpacing; - var size = new Vector2((width - (numButtons - 1) * innerSpacing.X) / numButtons, 0); - var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg); - var textColor = ImGui.GetColorU32(ImGuiCol.TextDisabled); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, innerSpacing) - .Push(ImGuiStyleVar.FrameBorderSize, 0); - using (var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()) - .Push(ImGuiCol.Button, buttonColor) - .Push(ImGuiCol.Text, textColor)) - { - foreach (var flag in SupportedFlags.Values) - { - if (mod.RequiredFeatures.HasFlag(flag)) - { - style.Push(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale); - color.Pop(2); - if (ImUtf8.Button($"{flag}", size)) - editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures & ~flag); - color.Push(ImGuiCol.Button, buttonColor) - .Push(ImGuiCol.Text, textColor); - style.Pop(); - } - else if (ImUtf8.Button($"{flag}", size)) - { - editor.ChangeRequiredFeatures(mod, mod.RequiredFeatures | flag); - } - - ImGui.SameLine(); - } - } - - if (ImUtf8.ButtonEx("Compute"u8, "Compute the required features automatically from the used features."u8, size)) - editor.ChangeRequiredFeatures(mod, mod.ComputeRequiredFeatures()); - - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Clear"u8, "Clear all required features."u8, size)) - editor.ChangeRequiredFeatures(mod, FeatureFlags.None); - - ImGui.SameLine(); - ImUtf8.Text("Required Features"u8); - } -} diff --git a/Penumbra/Mods/Groups/CombiningModGroup.cs b/Penumbra/Mods/Groups/CombiningModGroup.cs deleted file mode 100644 index d3f14101..00000000 --- a/Penumbra/Mods/Groups/CombiningModGroup.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Dalamud.Interface.ImGuiNotification; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Extensions; -using Penumbra.Api.Enums; -using Penumbra.GameData.Data; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Settings; -using Penumbra.Mods.SubMods; -using Penumbra.String.Classes; -using Penumbra.UI.ModsTab.Groups; -using Penumbra.Util; - -namespace Penumbra.Mods.Groups; - -/// Groups that allow all available options to be selected at once. -public sealed class CombiningModGroup : IModGroup -{ - public GroupType Type - => GroupType.Combining; - - public GroupDrawBehaviour Behaviour - => GroupDrawBehaviour.MultiSelection; - - public Mod Mod { get; } - public string Name { get; set; } = "Group"; - 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 = []; - public List Data { get; private set; } - - /// Groups that allow all available options to be selected at once. - public CombiningModGroup(Mod mod) - { - Mod = mod; - Data = [new CombinedDataContainer(this)]; - } - - IReadOnlyList IModGroup.Options - => OptionData; - - public IReadOnlyList DataContainers - => Data; - - public bool IsOption - => OptionData.Count > 0; - - public FullPath? FindBestMatch(Utf8GamePath gamePath) - { - foreach (var path in Data.SelectWhere(o - => (o.Files.TryGetValue(gamePath, out var file) || o.FileSwaps.TryGetValue(gamePath, out file), file))) - return path; - - return null; - } - - public IModOption? AddOption(string name, string description = "") - { - var groupIdx = Mod.Groups.IndexOf(this); - if (groupIdx < 0) - return null; - - var subMod = new CombiningSubMod(this) - { - Name = name, - Description = description, - }; - return OptionData.AddNewWithPowerSet(Data, subMod, () => new CombinedDataContainer(this), IModGroup.MaxCombiningOptions) - ? subMod - : null; - } - - public static CombiningModGroup? Load(Mod mod, JObject json) - { - var ret = new CombiningModGroup(mod, true); - if (!ModSaveGroup.ReadJsonBase(json, ret)) - return null; - - var options = json["Options"]; - if (options != null) - foreach (var child in options.Children()) - { - if (ret.OptionData.Count == IModGroup.MaxCombiningOptions) - { - Penumbra.Messager.NotificationMessage( - $"Combining Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxCombiningOptions} options, ignoring excessive options.", - NotificationType.Warning); - break; - } - - var subMod = new CombiningSubMod(ret, child); - ret.OptionData.Add(subMod); - } - - var requiredContainers = 1 << ret.OptionData.Count; - var containers = json["Containers"]; - if (containers != null) - foreach (var child in containers.Children()) - { - if (requiredContainers <= ret.Data.Count) - { - Penumbra.Messager.NotificationMessage( - $"Combining Group {ret.Name} in {mod.Name} has more data containers than it can support with {ret.OptionData.Count} options, ignoring excessive containers.", - NotificationType.Warning); - break; - } - - var container = new CombinedDataContainer(ret, child); - ret.Data.Add(container); - } - - if (requiredContainers > ret.Data.Count) - { - Penumbra.Messager.NotificationMessage( - $"Combining Group {ret.Name} in {mod.Name} has not enough data containers for its {ret.OptionData.Count} options, filling with empty containers.", - NotificationType.Warning); - ret.Data.EnsureCapacity(requiredContainers); - ret.Data.AddRange(Enumerable.Repeat(0, requiredContainers - ret.Data.Count).Select(_ => new CombinedDataContainer(ret))); - } - - ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); - - return ret; - } - - public int GetIndex() - => ModGroup.GetIndex(this); - - public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) - => new CombiningModGroupEditDrawer(editDrawer, this); - - public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) - => Data[setting.AsIndex].AddDataTo(redirections, manipulations); - - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - { - foreach (var container in DataContainers) - identifier.AddChangedItems(container, changedItems); - } - - public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null) - { - ModSaveGroup.WriteJsonBase(jWriter, this); - jWriter.WritePropertyName("Options"); - jWriter.WriteStartArray(); - foreach (var option in OptionData) - { - jWriter.WriteStartObject(); - SubMod.WriteModOption(jWriter, option); - jWriter.WriteEndObject(); - } - - jWriter.WriteEndArray(); - - jWriter.WritePropertyName("Containers"); - jWriter.WriteStartArray(); - foreach (var container in Data) - { - jWriter.WriteStartObject(); - if (container.Name.Length > 0) - { - jWriter.WritePropertyName("Name"); - jWriter.WriteValue(container.Name); - } - - SubMod.WriteModContainer(jWriter, serializer, container, basePath ?? Mod.ModPath); - jWriter.WriteEndObject(); - } - - jWriter.WriteEndArray(); - } - - public (int Redirections, int Swaps, int Manips) GetCounts() - => ModGroup.GetCountsBase(this); - - public Setting FixSetting(Setting setting) - => new(Math.Min(setting.Value, (ulong)(Data.Count - 1))); - - /// Create a group without a mod only for saving it in the creator. - internal static CombiningModGroup WithoutMod(string name) - => new(null!) - { - Name = name, - }; - - /// For loading when no empty container should be created. - private CombiningModGroup(Mod mod, bool _) - { - Mod = mod; - Data = []; - } -} diff --git a/Penumbra/Mods/Groups/ComplexModGroup.cs b/Penumbra/Mods/Groups/ComplexModGroup.cs deleted file mode 100644 index 435bc253..00000000 --- a/Penumbra/Mods/Groups/ComplexModGroup.cs +++ /dev/null @@ -1,180 +0,0 @@ -using Dalamud.Interface.ImGuiNotification; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OtterGui.Classes; -using OtterGui.Extensions; -using Penumbra.Api.Enums; -using Penumbra.GameData.Data; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Settings; -using Penumbra.Mods.SubMods; -using Penumbra.String.Classes; -using Penumbra.UI.ModsTab.Groups; -using Penumbra.Util; - -namespace Penumbra.Mods.Groups; - -public sealed class ComplexModGroup(Mod mod) : IModGroup -{ - public Mod Mod { get; } = mod; - public string Name { get; set; } = "Option"; - public string Description { get; set; } = string.Empty; - public string Image { get; set; } = string.Empty; - - public GroupType Type - => GroupType.Complex; - - public GroupDrawBehaviour Behaviour - => GroupDrawBehaviour.Complex; - - public ModPriority Priority { get; set; } - public int Page { get; set; } - public Setting DefaultSettings { get; set; } - - public readonly List Options = []; - public readonly List Containers = []; - - - public FullPath? FindBestMatch(Utf8GamePath gamePath) - => throw new NotImplementedException(); - - public IModOption? AddOption(string name, string description = "") - => throw new NotImplementedException(); - - IReadOnlyList IModGroup.Options - => Options; - - IReadOnlyList IModGroup.DataContainers - => Containers; - - public bool IsOption - => Options.Count > 0; - - public int GetIndex() - => ModGroup.GetIndex(this); - - public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer) - => throw new NotImplementedException(); - - public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations) - { - foreach (var container in Containers.Where(c => c.Association.IsEnabled(setting))) - SubMod.AddContainerTo(container, redirections, manipulations); - } - - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) - { - foreach (var container in Containers) - identifier.AddChangedItems(container, changedItems); - } - - public Setting FixSetting(Setting setting) - => new(setting.Value & ((1ul << Options.Count) - 1)); - - public void WriteJson(JsonTextWriter jWriter, JsonSerializer serializer, DirectoryInfo? basePath = null) - { - ModSaveGroup.WriteJsonBase(jWriter, this); - jWriter.WritePropertyName("Options"); - jWriter.WriteStartArray(); - foreach (var option in Options) - { - jWriter.WriteStartObject(); - SubMod.WriteModOption(jWriter, option); - if (!option.Conditions.IsZero) - { - jWriter.WritePropertyName("ConditionMask"); - jWriter.WriteValue(option.Conditions.Mask.Value); - jWriter.WritePropertyName("ConditionValue"); - jWriter.WriteValue(option.Conditions.Value.Value); - } - - if (option.Indentation > 0) - { - jWriter.WritePropertyName("Indentation"); - jWriter.WriteValue(option.Indentation); - } - - if (option.SubGroupLabel.Length > 0) - { - jWriter.WritePropertyName("SubGroup"); - jWriter.WriteValue(option.SubGroupLabel); - } - - jWriter.WriteEndObject(); - } - - jWriter.WriteEndArray(); - - jWriter.WritePropertyName("Containers"); - jWriter.WriteStartArray(); - foreach (var container in Containers) - { - jWriter.WriteStartObject(); - if (container.Name.Length > 0) - { - jWriter.WritePropertyName("Name"); - jWriter.WriteValue(container.Name); - } - - if (!container.Association.IsZero) - { - jWriter.WritePropertyName("AssociationMask"); - jWriter.WriteValue(container.Association.Mask.Value); - - jWriter.WritePropertyName("AssociationValue"); - jWriter.WriteValue(container.Association.Value.Value); - } - - SubMod.WriteModContainer(jWriter, serializer, container, basePath ?? Mod.ModPath); - jWriter.WriteEndObject(); - } - - jWriter.WriteEndArray(); - } - - public (int Redirections, int Swaps, int Manips) GetCounts() - => ModGroup.GetCountsBase(this); - - public static ComplexModGroup? Load(Mod mod, JObject json) - { - var ret = new ComplexModGroup(mod); - if (!ModSaveGroup.ReadJsonBase(json, ret)) - return null; - - var options = json["Options"]; - if (options != null) - foreach (var child in options.Children()) - { - if (ret.Options.Count == IModGroup.MaxComplexOptions) - { - Penumbra.Messager.NotificationMessage( - $"Complex Group {ret.Name} in {mod.Name} has more than {IModGroup.MaxComplexOptions} options, ignoring excessive options.", - NotificationType.Warning); - break; - } - - var subMod = new ComplexSubMod(ret, child); - ret.Options.Add(subMod); - } - - // Fix up conditions: No condition on itself. - foreach (var (option, index) in ret.Options.WithIndex()) - { - option.Conditions = option.Conditions.Limit(ret.Options.Count); - option.Conditions = new MaskedSetting(option.Conditions.Mask.SetBit(index, false), option.Conditions.Value); - } - - var containers = json["Containers"]; - if (containers != null) - foreach (var child in containers.Children()) - { - var container = new ComplexDataContainer(ret, child); - container.Association = container.Association.Limit(ret.Options.Count); - ret.Containers.Add(container); - } - - ret.DefaultSettings = ret.FixSetting(ret.DefaultSettings); - - return ret; - } -} diff --git a/Penumbra/Mods/Groups/IModGroup.cs b/Penumbra/Mods/Groups/IModGroup.cs index 98f62862..a6f6e20d 100644 --- a/Penumbra/Mods/Groups/IModGroup.cs +++ b/Penumbra/Mods/Groups/IModGroup.cs @@ -18,14 +18,11 @@ public enum GroupDrawBehaviour { SingleSelection, MultiSelection, - Complex, } public interface IModGroup { - public const int MaxMultiOptions = 32; - public const int MaxComplexOptions = MaxMultiOptions; - public const int MaxCombiningOptions = 8; + public const int MaxMultiOptions = 32; public Mod Mod { get; } public string Name { get; set; } @@ -55,7 +52,7 @@ public interface IModGroup public IModGroupEditDrawer EditDrawer(ModGroupEditDrawer editDrawer); public void AddData(Setting setting, Dictionary redirections, MetaDictionary manipulations); - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems); /// Ensure that a value is valid for a group. public Setting FixSetting(Setting setting); diff --git a/Penumbra/Mods/Groups/ImcModGroup.cs b/Penumbra/Mods/Groups/ImcModGroup.cs index 34174f7f..2a1854ed 100644 --- a/Penumbra/Mods/Groups/ImcModGroup.cs +++ b/Penumbra/Mods/Groups/ImcModGroup.cs @@ -1,8 +1,8 @@ using Dalamud.Interface.ImGuiNotification; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.GameData.Structs; @@ -131,7 +131,7 @@ public class ImcModGroup(Mod mod) : IModGroup } } - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) => Identifier.AddChangedItems(identifier, changedItems, AllVariants); public Setting FixSetting(Setting setting) diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index 558ee6be..0c9aa805 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -1,8 +1,8 @@ using Dalamud.Interface.ImGuiNotification; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.Meta.Manipulations; @@ -122,7 +122,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup } } - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) { foreach (var container in DataContainers) identifier.AddChangedItems(container, changedItems); diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index f376c1c9..ab0c2d4f 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; +using OtterGui; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.Meta.Manipulations; @@ -107,7 +107,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup OptionData[setting.AsIndex].AddDataTo(redirections, manipulations); } - public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) + public void AddChangedItems(ObjectIdentification identifier, IDictionary changedItems) { foreach (var container in DataContainers) identifier.AddChangedItems(container, changedItems); diff --git a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs index c5406f66..cd36de93 100644 --- a/Penumbra/Mods/ItemSwap/CustomizationSwap.cs +++ b/Penumbra/Mods/ItemSwap/CustomizationSwap.cs @@ -17,8 +17,8 @@ public static class CustomizationSwap if (idFrom.Id > byte.MaxValue) throw new Exception($"The Customization ID {idFrom} is too large for {slot}."); - var mdlPathFrom = GamePaths.Mdl.Customization(race, slot, idFrom, slot.ToCustomizationType()); - var mdlPathTo = GamePaths.Mdl.Customization(race, slot, idTo, slot.ToCustomizationType()); + var mdlPathFrom = GamePaths.Character.Mdl.Path(race, slot, idFrom, slot.ToCustomizationType()); + var mdlPathTo = GamePaths.Character.Mdl.Path(race, slot, idTo, slot.ToCustomizationType()); var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo); var range = slot == BodySlot.Tail @@ -47,8 +47,8 @@ public static class CustomizationSwap ref string fileName, ref bool dataWasChanged) { variant = slot is BodySlot.Face or BodySlot.Ear ? Variant.None.Id : variant; - var mtrlFromPath = GamePaths.Mtrl.Customization(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant); - var mtrlToPath = GamePaths.Mtrl.Customization(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant); + var mtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, fileName, out var gameRaceFrom, out var gameSetIdFrom, variant); + var mtrlToPath = GamePaths.Character.Mtrl.Path(race, slot, idTo, fileName, out var gameRaceTo, out var gameSetIdTo, variant); var newFileName = fileName; newFileName = ItemSwap.ReplaceRace(newFileName, gameRaceTo, race, gameRaceTo != race); @@ -60,7 +60,7 @@ public static class CustomizationSwap var actualMtrlFromPath = mtrlFromPath; if (newFileName != fileName) { - actualMtrlFromPath = GamePaths.Mtrl.Customization(race, slot, idFrom, newFileName, out _, out _, variant); + actualMtrlFromPath = GamePaths.Character.Mtrl.Path(race, slot, idFrom, newFileName, out _, out _, variant); fileName = newFileName; dataWasChanged = true; } diff --git a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs index 216b5841..c7e43a26 100644 --- a/Penumbra/Mods/ItemSwap/EquipmentSwap.cs +++ b/Penumbra/Mods/ItemSwap/EquipmentSwap.cs @@ -27,31 +27,31 @@ public static class EquipmentSwap : []; } - public static HashSet CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, + public static EquipItem[] CreateTypeSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, Func redirections, MetaDictionary manips, 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; } @@ -88,40 +88,30 @@ public static class EquipmentSwap return affectedItems; } - public static HashSet CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, + public static EquipItem[] CreateItemSwap(MetaFileManager manager, ObjectIdentification identifier, List swaps, Func redirections, MetaDictionary manips, EquipItem itemFrom, EquipItem itemTo, bool rFinger = true, bool lFinger = true) { // 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(); - HashSet affectedItems = []; - var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo); + var eqp = CreateEqp(manager, manips, slotFrom, idFrom, idTo); if (eqp != null) - { swaps.Add(eqp); - // Add items affected through multi-slot EQP edits. - foreach (var child in eqp.ChildSwaps.SelectMany(c => c.WithChildren()).OfType>()) - { - affectedItems.UnionWith(identifier - .Identify(GamePaths.Mdl.Equipment(idFrom, GenderRace.MidlanderMale, child.SwapFromIdentifier.Slot)) - .Select(kvp => kvp.Value).OfType().Select(i => i.Item)); - } - } var gmp = CreateGmp(manager, manips, slotFrom, idFrom, idTo); if (gmp != null) swaps.Add(gmp); + var affectedItems = Array.Empty(); foreach (var slot in ConvertSlots(slotFrom, rFinger, lFinger)) { - var (imcFileFrom, variants, affectedItemsLocal) = GetVariants(manager, identifier, slot, idFrom, idTo, variantFrom); - affectedItems.UnionWith(affectedItemsLocal); + (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); @@ -132,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; } @@ -158,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); } @@ -186,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); @@ -196,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); @@ -223,9 +212,11 @@ public static class EquipmentSwap public static FileSwap CreateMdl(MetaFileManager manager, Func redirections, EquipSlot slotFrom, EquipSlot slotTo, GenderRace gr, PrimaryId idFrom, PrimaryId idTo, byte mtrlTo) { - var mdlPathFrom = GamePaths.Mdl.Gear(idFrom, gr, slotFrom); - var mdlPathTo = GamePaths.Mdl.Gear(idTo, gr, slotTo); - var mdl = FileSwap.CreateSwap(manager, ResourceType.Mdl, redirections, mdlPathFrom, mdlPathTo); + var mdlPathFrom = slotFrom.IsAccessory() + ? 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); foreach (ref var fileName in mdl.AsMdl()!.Materials.AsSpan()) { @@ -234,56 +225,9 @@ public static class EquipmentSwap mdl.ChildSwaps.Add(mtrl); } - FixAttributes(mdl, slotFrom, slotTo); - return mdl; } - private static void FixAttributes(FileSwap swap, EquipSlot slotFrom, EquipSlot slotTo) - { - if (slotFrom == slotTo) - return; - - var needle = slotTo switch - { - EquipSlot.Head => "atr_mv_", - EquipSlot.Ears => "atr_ev_", - EquipSlot.Neck => "atr_nv_", - EquipSlot.Wrists => "atr_wv_", - EquipSlot.RFinger or EquipSlot.LFinger => "atr_rv_", - _ => string.Empty, - }; - - var replacement = slotFrom switch - { - EquipSlot.Head => 'm', - EquipSlot.Ears => 'e', - EquipSlot.Neck => 'n', - EquipSlot.Wrists => 'w', - EquipSlot.RFinger or EquipSlot.LFinger => 'r', - _ => 'm', - }; - - var attributes = swap.AsMdl()!.Attributes; - for (var i = 0; i < attributes.Length; ++i) - { - if (FixAttribute(ref attributes[i], needle, replacement)) - swap.DataWasChanged = true; - } - } - - private static unsafe bool FixAttribute(ref string attribute, string from, char to) - { - if (!attribute.StartsWith(from) || attribute.Length != from.Length + 1 || attribute[^1] is < 'a' or > 'j') - return false; - - Span stack = stackalloc char[attribute.Length]; - attribute.CopyTo(stack); - stack[4] = to; - attribute = new string(stack); - return true; - } - private static void LookupItem(EquipItem i, out EquipSlot slot, out PrimaryId modelId, out Variant variant) { slot = i.Type.ToSlot(); @@ -294,24 +238,25 @@ public static class EquipmentSwap variant = i.Variant; } - private static (ImcFile, Variant[], HashSet) GetVariants(MetaFileManager manager, ObjectIdentification identifier, - EquipSlot slotFrom, + 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); - HashSet items; - Variant[] variants; + var ident = new ImcIdentifier(slotFrom, idFrom, variantFrom); + var imc = new ImcFile(manager, ident); + EquipItem[] items; + Variant[] variants; if (idFrom == idTo) { - items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToHashSet(); + items = identifier.Identify(idFrom, 0, variantFrom, slotFrom).ToArray(); variants = [variantFrom]; } else { - items = identifier.Identify(GamePaths.Mdl.Gear(idFrom, GenderRace.MidlanderMale, slotFrom)) + items = identifier.Identify(slotFrom.IsEquipment() + ? GamePaths.Equipment.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom) + : GamePaths.Accessory.Mdl.Path(idFrom, GenderRace.MidlanderMale, slotFrom)) .Select(kvp => kvp.Value).OfType().Select(i => i.Item) - .ToHashSet(); + .ToArray(); variants = Enumerable.Range(0, imc.Count + 1).Select(i => (Variant)i).ToArray(); } @@ -325,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); } @@ -342,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); @@ -367,7 +312,7 @@ public static class EquipmentSwap if (decalId == 0) return null; - var decalPath = GamePaths.Tex.EquipDecal(decalId); + var decalPath = GamePaths.Equipment.Decal.Path(decalId); return FileSwap.CreateSwap(manager, ResourceType.Tex, redirections, decalPath, decalPath); } @@ -380,10 +325,10 @@ public static class EquipmentSwap if (vfxId == 0) return null; - var vfxPathFrom = GamePaths.Avfx.Path(slotFrom, idFrom, vfxId); + var vfxPathFrom = GamePaths.Equipment.Avfx.Path(idFrom, vfxId); vfxPathFrom = ItemSwap.ReplaceType(vfxPathFrom, slotFrom, slotTo, idFrom); - var vfxPathTo = GamePaths.Avfx.Path(slotTo, idTo, vfxId); - var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); + var vfxPathTo = GamePaths.Equipment.Avfx.Path(idTo, vfxId); + var avfx = FileSwap.CreateSwap(manager, ResourceType.Avfx, redirections, vfxPathFrom, vfxPathTo); foreach (ref var filePath in avfx.AsAvfx()!.Textures.AsSpan()) { @@ -394,42 +339,18 @@ public static class EquipmentSwap return avfx; } - public static MetaSwap? CreateEqp(MetaFileManager manager, MetaDictionary manips, EquipSlot slot, - PrimaryId idFrom, PrimaryId idTo) + public static MetaSwap? CreateEqp(MetaFileManager manager, MetaDictionary manips, + EquipSlot slot, PrimaryId idFrom, PrimaryId idTo) { if (slot.IsAccessory()) 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 swap = new MetaSwap(i => manips.TryGetValue(i, out var e) ? e : null, manipFromIdentifier, + 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); - var entry = swap.SwapToModdedEntry.ToEntry(slot); - // Add additional EQP entries if the swapped item is a multi-slot item, - // because those take the EQP entries of their other model-set slots when used. - switch (slot) - { - case EquipSlot.Body: - if (!entry.HasFlag(EqpEntry.BodyShowLeg) - && CreateEqp(manager, manips, EquipSlot.Legs, idFrom, idTo) is { } legChild) - swap.ChildSwaps.Add(legChild); - if (!entry.HasFlag(EqpEntry.BodyShowHead) - && CreateEqp(manager, manips, EquipSlot.Head, idFrom, idTo) is { } headChild) - swap.ChildSwaps.Add(headChild); - if (!entry.HasFlag(EqpEntry.BodyShowHand) - && CreateEqp(manager, manips, EquipSlot.Hands, idFrom, idTo) is { } handChild) - swap.ChildSwaps.Add(handChild); - break; - case EquipSlot.Legs: - if (!entry.HasFlag(EqpEntry.LegsShowFoot) - && CreateEqp(manager, manips, EquipSlot.Feet, idFrom, idTo) is { } footChild) - swap.ChildSwaps.Add(footChild); - break; - } - - return swap; } public static FileSwap? CreateMtrl(MetaFileManager manager, Func redirections, EquipSlot slot, PrimaryId idFrom, @@ -445,17 +366,21 @@ public static class EquipmentSwap if (!fileName.Contains($"{prefix}{idTo.Id:D4}")) return null; - var folderTo = GamePaths.Mtrl.GearFolder(slotTo, idTo, variantTo); - var pathTo = $"{folderTo}{fileName}"; + var folderTo = slotTo.IsAccessory() + ? GamePaths.Accessory.Mtrl.FolderPath(idTo, variantTo) + : GamePaths.Equipment.Mtrl.FolderPath(idTo, variantTo); + var pathTo = $"{folderTo}{fileName}"; - var folderFrom = GamePaths.Mtrl.GearFolder(slotFrom, idFrom, variantTo); + var folderFrom = slotFrom.IsAccessory() + ? GamePaths.Accessory.Mtrl.FolderPath(idFrom, variantTo) + : GamePaths.Equipment.Mtrl.FolderPath(idFrom, variantTo); var newFileName = ItemSwap.ReplaceId(fileName, prefix, idTo, idFrom); newFileName = ItemSwap.ReplaceSlot(newFileName, slotTo, slotFrom, slotTo != slotFrom); var pathFrom = $"{folderFrom}{newFileName}"; if (newFileName != fileName) { - fileName = newFileName; + fileName = newFileName; dataWasChanged = true; } @@ -480,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; } @@ -496,7 +421,7 @@ public static class EquipmentSwap public static FileSwap CreateShader(MetaFileManager manager, Func redirections, ref string shaderName, ref bool dataWasChanged) { - var path = GamePaths.Shader(shaderName); + var path = $"shader/sm5/shpk/{shaderName}"; return FileSwap.CreateSwap(manager, ResourceType.Shpk, redirections, path, path); } @@ -504,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/Mods/ItemSwap/ItemSwap.cs b/Penumbra/Mods/ItemSwap/ItemSwap.cs index 0049fa12..03abfc45 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwap.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwap.cs @@ -132,14 +132,14 @@ public static class ItemSwap public static FileSwap CreatePhyb(MetaFileManager manager, Func redirections, EstType type, GenderRace race, EstEntry estEntry) { - var phybPath = GamePaths.Phyb.Customization(race, type.ToName(), estEntry.AsId); + var phybPath = GamePaths.Skeleton.Phyb.Path(race, type.ToName(), estEntry.AsId); return FileSwap.CreateSwap(manager, ResourceType.Phyb, redirections, phybPath, phybPath); } public static FileSwap CreateSklb(MetaFileManager manager, Func redirections, EstType type, GenderRace race, EstEntry estEntry) { - var sklbPath = GamePaths.Sklb.Customization(race, type.ToName(), estEntry.AsId); + var sklbPath = GamePaths.Skeleton.Sklb.Path(race, type.ToName(), estEntry.AsId); return FileSwap.CreateSwap(manager, ResourceType.Sklb, redirections, sklbPath, sklbPath); } diff --git a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs index a9d5e0d6..d2deb9ef 100644 --- a/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs +++ b/Penumbra/Mods/ItemSwap/ItemSwapContainer.cs @@ -127,7 +127,7 @@ public class ItemSwapContainer ? new MetaDictionary(cache) : _appliedModData.Manipulations; - public HashSet LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, + public EquipItem[] LoadEquipment(EquipItem from, EquipItem to, ModCollection? collection = null, bool useRightRing = true, bool useLeftRing = true) { Swaps.Clear(); @@ -138,7 +138,7 @@ public class ItemSwapContainer return ret; } - public HashSet LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null) + public EquipItem[] LoadTypeSwap(EquipSlot slotFrom, EquipItem from, EquipSlot slotTo, EquipItem to, ModCollection? collection = null) { Swaps.Clear(); Loaded = false; diff --git a/Penumbra/Mods/Manager/ModCacheManager.cs b/Penumbra/Mods/Manager/ModCacheManager.cs index 130c8fcb..38d98d7c 100644 --- a/Penumbra/Mods/Manager/ModCacheManager.cs +++ b/Penumbra/Mods/Manager/ModCacheManager.cs @@ -15,7 +15,7 @@ public class ModCacheManager : IDisposable, IService private readonly CommunicatorService _communicator; private readonly ObjectIdentification _identifier; private readonly ModStorage _modManager; - private bool _updatingItems; + private bool _updatingItems = false; public ModCacheManager(CommunicatorService communicator, ObjectIdentification identifier, ModStorage modStorage, Configuration config) { @@ -139,7 +139,6 @@ public class ModCacheManager : IDisposable, IService mod.ChangedItems.RemoveMachinistOffhands(); mod.LowerChangedItemsString = string.Join("\0", mod.ChangedItems.Keys.Select(k => k.ToLowerInvariant())); - ++mod.LastChangedItemsUpdate; } private static void UpdateCounts(Mod mod) diff --git a/Penumbra/Mods/Manager/ModDataEditor.cs b/Penumbra/Mods/Manager/ModDataEditor.cs index ffa73b76..162f823d 100644 --- a/Penumbra/Mods/Manager/ModDataEditor.cs +++ b/Penumbra/Mods/Manager/ModDataEditor.cs @@ -1,8 +1,7 @@ using Dalamud.Utility; +using Newtonsoft.Json.Linq; using OtterGui.Classes; using OtterGui.Services; -using Penumbra.GameData.Data; -using Penumbra.GameData.Structs; using Penumbra.Services; namespace Penumbra.Mods.Manager; @@ -10,44 +9,179 @@ namespace Penumbra.Mods.Manager; [Flags] public enum ModDataChangeType : ushort { - None = 0x0000, - Name = 0x0001, - Author = 0x0002, - Description = 0x0004, - Version = 0x0008, - Website = 0x0010, - Deletion = 0x0020, - Migration = 0x0040, - ModTags = 0x0080, - ImportDate = 0x0100, - Favorite = 0x0200, - LocalTags = 0x0400, - Note = 0x0800, - Image = 0x1000, - DefaultChangedItems = 0x2000, - PreferredChangedItems = 0x4000, - RequiredFeatures = 0x8000, + None = 0x0000, + Name = 0x0001, + Author = 0x0002, + Description = 0x0004, + Version = 0x0008, + Website = 0x0010, + Deletion = 0x0020, + Migration = 0x0040, + ModTags = 0x0080, + ImportDate = 0x0100, + Favorite = 0x0200, + LocalTags = 0x0400, + Note = 0x0800, + Image = 0x1000, } -public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService, ItemData itemData) : IService +public class ModDataEditor(SaveService saveService, CommunicatorService communicatorService) : IService { - public SaveService SaveService - => saveService; - /// Create the file containing the meta information about a mod from scratch. public void CreateMeta(DirectoryInfo directory, string? name, string? author, string? description, string? version, - string? website, params string[] tags) + string? website) { var mod = new Mod(directory); - mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString(name); + mod.Name = name.IsNullOrEmpty() ? mod.Name : new LowerString(name!); mod.Author = author != null ? new LowerString(author) : mod.Author; mod.Description = description ?? mod.Description; mod.Version = version ?? mod.Version; mod.Website = website ?? mod.Website; - mod.ModTags = tags; saveService.ImmediateSaveSync(new ModMeta(mod)); } + public ModDataChangeType LoadLocalData(Mod mod) + { + var dataFile = saveService.FileNames.LocalDataFile(mod); + + var importDate = 0L; + var localTags = Enumerable.Empty(); + var favorite = false; + var note = string.Empty; + + var save = true; + if (File.Exists(dataFile)) + try + { + var text = File.ReadAllText(dataFile); + var json = JObject.Parse(text); + + importDate = json[nameof(Mod.ImportDate)]?.Value() ?? importDate; + favorite = json[nameof(Mod.Favorite)]?.Value() ?? favorite; + note = json[nameof(Mod.Note)]?.Value() ?? note; + localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values().OfType() ?? localTags; + save = false; + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not load local mod data:\n{e}"); + } + + if (importDate == 0) + importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + ModDataChangeType changes = 0; + if (mod.ImportDate != importDate) + { + mod.ImportDate = importDate; + changes |= ModDataChangeType.ImportDate; + } + + changes |= ModLocalData.UpdateTags(mod, null, localTags); + + if (mod.Favorite != favorite) + { + mod.Favorite = favorite; + changes |= ModDataChangeType.Favorite; + } + + if (mod.Note != note) + { + mod.Note = note; + changes |= ModDataChangeType.Note; + } + + if (save) + saveService.QueueSave(new ModLocalData(mod)); + + return changes; + } + + public ModDataChangeType LoadMeta(ModCreator creator, Mod mod) + { + var metaFile = saveService.FileNames.ModMetaPath(mod); + if (!File.Exists(metaFile)) + { + Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}."); + return ModDataChangeType.Deletion; + } + + try + { + var text = File.ReadAllText(metaFile); + var json = JObject.Parse(text); + + var newName = json[nameof(Mod.Name)]?.Value() ?? string.Empty; + var newAuthor = json[nameof(Mod.Author)]?.Value() ?? string.Empty; + var newDescription = json[nameof(Mod.Description)]?.Value() ?? string.Empty; + var newImage = json[nameof(Mod.Image)]?.Value() ?? string.Empty; + var newVersion = json[nameof(Mod.Version)]?.Value() ?? string.Empty; + var newWebsite = json[nameof(Mod.Website)]?.Value() ?? string.Empty; + var newFileVersion = json[nameof(ModMeta.FileVersion)]?.Value() ?? 0; + var importDate = json[nameof(Mod.ImportDate)]?.Value(); + var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values().OfType(); + + ModDataChangeType changes = 0; + if (mod.Name != newName) + { + changes |= ModDataChangeType.Name; + mod.Name = newName; + } + + if (mod.Author != newAuthor) + { + changes |= ModDataChangeType.Author; + mod.Author = newAuthor; + } + + if (mod.Description != newDescription) + { + changes |= ModDataChangeType.Description; + mod.Description = newDescription; + } + + if (mod.Image != newImage) + { + changes |= ModDataChangeType.Image; + mod.Image = newImage; + } + + if (mod.Version != newVersion) + { + changes |= ModDataChangeType.Version; + mod.Version = newVersion; + } + + if (mod.Website != newWebsite) + { + changes |= ModDataChangeType.Website; + mod.Website = newWebsite; + } + + if (newFileVersion != ModMeta.FileVersion) + if (ModMigration.Migrate(creator, saveService, mod, json, ref newFileVersion)) + { + changes |= ModDataChangeType.Migration; + saveService.ImmediateSave(new ModMeta(mod)); + } + + if (importDate != null && mod.ImportDate != importDate.Value) + { + mod.ImportDate = importDate.Value; + changes |= ModDataChangeType.ImportDate; + } + + changes |= ModLocalData.UpdateTags(mod, modTags, null); + + return changes; + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not load mod meta for {metaFile}:\n{e}"); + return ModDataChangeType.Deletion; + } + } + public void ChangeModName(Mod mod, string newName) { if (mod.Name.Text == newName) @@ -97,16 +231,6 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic mod.Website = newWebsite; saveService.QueueSave(new ModMeta(mod)); communicatorService.ModDataChanged.Invoke(ModDataChangeType.Website, mod, null); - } - - public void ChangeRequiredFeatures(Mod mod, FeatureFlags flags) - { - if (mod.RequiredFeatures == flags) - return; - - mod.RequiredFeatures = flags; - saveService.QueueSave(new ModMeta(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.RequiredFeatures, mod, null); } public void ChangeModTag(Mod mod, int tagIdx, string newTag) @@ -190,125 +314,4 @@ public class ModDataEditor(SaveService saveService, CommunicatorService communic Penumbra.Log.Error($"Could not move local data file {oldFile} to {newFile}:\n{e}"); } } - - public void AddPreferredItem(Mod mod, CustomItemId id, bool toDefault, bool cleanExisting) - { - if (CleanExisting(mod.PreferredChangedItems)) - { - ++mod.LastChangedItemsUpdate; - saveService.QueueSave(new ModLocalData(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.PreferredChangedItems, mod, null); - } - - if (toDefault && CleanExisting(mod.DefaultPreferredItems)) - { - saveService.QueueSave(new ModMeta(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.DefaultChangedItems, mod, null); - } - - bool CleanExisting(HashSet items) - { - if (!items.Add(id)) - return false; - - if (!cleanExisting) - return true; - - var it1Exists = itemData.Primary.TryGetValue(id, out var it1); - var it2Exists = itemData.Secondary.TryGetValue(id, out var it2); - var it3Exists = itemData.Tertiary.TryGetValue(id, out var it3); - - foreach (var item in items.ToArray()) - { - if (item == id) - continue; - - if (it1Exists - && itemData.Primary.TryGetValue(item, out var oldItem1) - && oldItem1.PrimaryId == it1.PrimaryId - && oldItem1.Type == it1.Type) - items.Remove(item); - - else if (it2Exists - && itemData.Primary.TryGetValue(item, out var oldItem2) - && oldItem2.PrimaryId == it2.PrimaryId - && oldItem2.Type == it2.Type) - items.Remove(item); - - else if (it3Exists - && itemData.Primary.TryGetValue(item, out var oldItem3) - && oldItem3.PrimaryId == it3.PrimaryId - && oldItem3.Type == it3.Type) - items.Remove(item); - } - - return true; - } - } - - public void RemovePreferredItem(Mod mod, CustomItemId id, bool fromDefault) - { - if (!fromDefault && mod.PreferredChangedItems.Remove(id)) - { - ++mod.LastChangedItemsUpdate; - saveService.QueueSave(new ModLocalData(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.PreferredChangedItems, mod, null); - } - - if (fromDefault && mod.DefaultPreferredItems.Remove(id)) - { - saveService.QueueSave(new ModMeta(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.DefaultChangedItems, mod, null); - } - } - - public void ClearInvalidPreferredItems(Mod mod) - { - var currentChangedItems = mod.ChangedItems.Values.OfType().Select(i => i.Item.Id).Distinct().ToHashSet(); - var newSet = new HashSet(mod.PreferredChangedItems.Count); - - if (CheckItems(mod.PreferredChangedItems)) - { - mod.PreferredChangedItems = newSet; - ++mod.LastChangedItemsUpdate; - saveService.QueueSave(new ModLocalData(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.PreferredChangedItems, mod, null); - } - - newSet = new HashSet(mod.DefaultPreferredItems.Count); - if (CheckItems(mod.DefaultPreferredItems)) - { - mod.DefaultPreferredItems = newSet; - saveService.QueueSave(new ModMeta(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.DefaultChangedItems, mod, null); - } - - return; - - bool CheckItems(HashSet set) - { - var changes = false; - foreach (var item in set) - { - if (currentChangedItems.Contains(item)) - newSet.Add(item); - else - changes = true; - } - - return changes; - } - } - - public void ResetPreferredItems(Mod mod) - { - if (mod.PreferredChangedItems.SetEquals(mod.DefaultPreferredItems)) - return; - - mod.PreferredChangedItems.Clear(); - mod.PreferredChangedItems.UnionWith(mod.DefaultPreferredItems); - ++mod.LastChangedItemsUpdate; - saveService.QueueSave(new ModLocalData(mod)); - communicatorService.ModDataChanged.Invoke(ModDataChangeType.PreferredChangedItems, mod, null); - } } diff --git a/Penumbra/Mods/Manager/ModFileSystem.cs b/Penumbra/Mods/Manager/ModFileSystem.cs index 20a78995..693db944 100644 --- a/Penumbra/Mods/Manager/ModFileSystem.cs +++ b/Penumbra/Mods/Manager/ModFileSystem.cs @@ -37,11 +37,11 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, ISer public struct ImportDate : ISortMode { - public ReadOnlySpan Name - => "Import Date (Older First)"u8; + public string Name + => "Import Date (Older First)"; - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date."u8; + public string Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their import date."; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderBy(l => l.Value.ImportDate)); @@ -49,11 +49,11 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, ISer public struct InverseImportDate : ISortMode { - public ReadOnlySpan Name - => "Import Date (Newer First)"u8; + public string Name + => "Import Date (Newer First)"; - public ReadOnlySpan Description - => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date."u8; + public string Description + => "In each folder, sort all subfolders lexicographically, then sort all leaves using their inverse import date."; public IEnumerable GetChildren(Folder f) => f.GetSubFolders().Cast().Concat(f.GetLeaves().OrderByDescending(l => l.Value.ImportDate)); @@ -80,7 +80,7 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, ISer // Update sort order when defaulted mod names change. private void OnModDataChange(ModDataChangeType type, Mod mod, string? oldName) { - if (!type.HasFlag(ModDataChangeType.Name) || oldName == null || !TryGetValue(mod, out var leaf)) + if (!type.HasFlag(ModDataChangeType.Name) || oldName == null || !FindLeaf(mod, out var leaf)) return; var old = oldName.FixName(); @@ -111,7 +111,7 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, ISer CreateDuplicateLeaf(parent, mod.Name.Text, mod); break; case ModPathChangeType.Deleted: - if (TryGetValue(mod, out var leaf)) + if (FindLeaf(mod, out var leaf)) Delete(leaf); break; @@ -124,6 +124,16 @@ public sealed class ModFileSystem : FileSystem, IDisposable, ISavable, ISer } } + // Search the entire filesystem for the leaf corresponding to a mod. + public bool FindLeaf(Mod mod, [NotNullWhen(true)] out Leaf? leaf) + { + leaf = Root.GetAllDescendants(ISortMode.Lexicographical) + .OfType() + .FirstOrDefault(l => l.Value == mod); + return leaf != null; + } + + // Used for saving and loading. private static string ModToIdentifier(Mod mod) => mod.ModPath.Name; diff --git a/Penumbra/Mods/Manager/ModImportManager.cs b/Penumbra/Mods/Manager/ModImportManager.cs index bb282262..22cc0c86 100644 --- a/Penumbra/Mods/Manager/ModImportManager.cs +++ b/Penumbra/Mods/Manager/ModImportManager.cs @@ -70,6 +70,7 @@ public class ModImportManager(ModManager modManager, Configuration config, ModEd _import = null; } + public bool AddUnpackedMod([NotNullWhen(true)] out Mod? mod) { if (!_modsToAdd.TryDequeue(out var directory)) diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 77385bbd..bf1b6637 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -1,6 +1,5 @@ using OtterGui.Services; using Penumbra.Communication; -using Penumbra.Interop; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Services; @@ -144,10 +143,9 @@ public sealed class ModManager : ModStorage, IDisposable, IService _communicator.ModPathChanged.Invoke(ModPathChangeType.StartingReload, mod, mod.ModPath, mod.ModPath); if (!Creator.ReloadMod(mod, true, false, out var metaChange)) { - if (mod.RequiredFeatures is not FeatureFlags.Invalid) - Penumbra.Log.Warning(mod.Name.Length == 0 - ? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead." - : $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead."); + Penumbra.Log.Warning(mod.Name.Length == 0 + ? $"Reloading mod {oldName} has failed, new name is empty. Removing from loaded mods instead." + : $"Reloading mod {oldName} failed, {mod.ModPath.FullName} does not exist anymore or it has invalid data. Removing from loaded mods instead."); RemoveMod(mod); return; } @@ -253,8 +251,12 @@ public sealed class ModManager : ModStorage, IDisposable, IService { switch (type) { - case ModPathChangeType.Added: SetNew(mod); break; - case ModPathChangeType.Deleted: SetKnown(mod); break; + case ModPathChangeType.Added: + SetNew(mod); + break; + case ModPathChangeType.Deleted: + SetKnown(mod); + break; case ModPathChangeType.Moved: if (oldDirectory != null && newDirectory != null) DataEditor.MoveDataFile(oldDirectory, newDirectory); @@ -304,9 +306,6 @@ public sealed class ModManager : ModStorage, IDisposable, IService if (!firstTime && _config.ModDirectory != BasePath.FullName) TriggerModDirectoryChange(BasePath.FullName, Valid); } - - if (CloudApi.IsCloudSynced(BasePath.FullName)) - Penumbra.Log.Warning($"Mod base directory {BasePath.FullName} is cloud-synced. This may cause issues."); } private void TriggerModDirectoryChange(string newPath, bool valid) diff --git a/Penumbra/Mods/Manager/ModMigration.cs b/Penumbra/Mods/Manager/ModMigration.cs index f3b25f1a..3e58c515 100644 --- a/Penumbra/Mods/Manager/ModMigration.cs +++ b/Penumbra/Mods/Manager/ModMigration.cs @@ -1,8 +1,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; +using OtterGui; using Penumbra.Api.Enums; -using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; @@ -83,8 +82,9 @@ public static partial class ModMigration foreach (var (gamePath, swapPath) in swaps) mod.Default.FileSwaps.Add(gamePath, swapPath); - creator.IncorporateAllMetaChanges(mod, true, true); - saveService.SaveAllOptionGroups(mod, false, creator.Config.ReplaceNonAsciiOnImport); + creator.IncorporateMetaChanges(mod.Default, mod.ModPath, true, true); + foreach (var group in mod.Groups) + saveService.ImmediateSave(new ModSaveGroup(group, creator.Config.ReplaceNonAsciiOnImport)); // Delete meta files. foreach (var file in seenMetaFiles.Where(f => f.Exists)) @@ -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/CombiningModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/CombiningModGroupEditor.cs deleted file mode 100644 index 5acf5eb5..00000000 --- a/Penumbra/Mods/Manager/OptionEditor/CombiningModGroupEditor.cs +++ /dev/null @@ -1,49 +0,0 @@ -using OtterGui; -using OtterGui.Classes; -using OtterGui.Extensions; -using OtterGui.Services; -using Penumbra.Mods.Groups; -using Penumbra.Mods.Settings; -using Penumbra.Mods.SubMods; -using Penumbra.Services; - -namespace Penumbra.Mods.Manager.OptionEditor; - -public sealed class CombiningModGroupEditor(CommunicatorService communicator, SaveService saveService, Configuration config) - : ModOptionEditor(communicator, saveService, config), IService -{ - protected override CombiningModGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync) - => new(mod) - { - Name = newName, - Priority = priority, - }; - - protected override CombiningSubMod? CloneOption(CombiningModGroup group, IModOption option) - => throw new NotImplementedException(); - - protected override void RemoveOption(CombiningModGroup group, int optionIndex) - { - if (group.OptionData.RemoveWithPowerSet(group.Data, optionIndex)) - group.DefaultSettings.RemoveBit(optionIndex); - } - - protected override bool MoveOption(CombiningModGroup group, int optionIdxFrom, int optionIdxTo) - { - if (!group.OptionData.MoveWithPowerSet(group.Data, ref optionIdxFrom, ref optionIdxTo)) - return false; - - group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo); - return true; - } - - public void SetDisplayName(CombinedDataContainer container, string name, SaveType saveType = SaveType.Queue) - { - if (container.Name == name) - return; - - container.Name = name; - SaveService.Save(saveType, new ModSaveGroup(container.Group, Config.ReplaceNonAsciiOnImport)); - Communicator.ModOptionChanged.Invoke(ModOptionChangeType.DisplayChange, container.Group.Mod, container.Group, null, null, -1); - } -} diff --git a/Penumbra/Mods/Manager/OptionEditor/ImcAttributeCache.cs b/Penumbra/Mods/Manager/OptionEditor/ImcAttributeCache.cs index 12ed4c60..a7b73ac9 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ImcAttributeCache.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ImcAttributeCache.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using Penumbra.GameData.Structs; using Penumbra.Mods.Groups; using Penumbra.Mods.SubMods; diff --git a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs index 1c077c58..d01297db 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModGroupEditor.cs @@ -37,7 +37,6 @@ public class ModGroupEditor( SingleModGroupEditor singleEditor, MultiModGroupEditor multiEditor, ImcModGroupEditor imcEditor, - CombiningModGroupEditor combiningEditor, CommunicatorService communicator, SaveService saveService, Configuration config) : IService @@ -51,9 +50,6 @@ public class ModGroupEditor( public ImcModGroupEditor ImcEditor => imcEditor; - public CombiningModGroupEditor CombiningEditor - => combiningEditor; - /// Change the settings stored as default options in a mod. public void ChangeModGroupDefaultOption(IModGroup group, Setting defaultOption) { @@ -227,60 +223,52 @@ public class ModGroupEditor( case ImcSubMod i: ImcEditor.DeleteOption(i); return; - case CombiningSubMod c: - CombiningEditor.DeleteOption(c); - return; } } public IModOption? AddOption(IModGroup group, IModOption option) => group switch { - SingleModGroup s => SingleEditor.AddOption(s, option), - MultiModGroup m => MultiEditor.AddOption(m, option), - ImcModGroup i => ImcEditor.AddOption(i, option), - CombiningModGroup c => CombiningEditor.AddOption(c, option), - _ => null, + SingleModGroup s => SingleEditor.AddOption(s, option), + MultiModGroup m => MultiEditor.AddOption(m, option), + ImcModGroup i => ImcEditor.AddOption(i, option), + _ => null, }; public IModOption? AddOption(IModGroup group, string newName) => group switch { - SingleModGroup s => SingleEditor.AddOption(s, newName), - MultiModGroup m => MultiEditor.AddOption(m, newName), - ImcModGroup i => ImcEditor.AddOption(i, newName), - CombiningModGroup c => CombiningEditor.AddOption(c, newName), - _ => null, + SingleModGroup s => SingleEditor.AddOption(s, newName), + MultiModGroup m => MultiEditor.AddOption(m, newName), + ImcModGroup i => ImcEditor.AddOption(i, newName), + _ => null, }; public IModGroup? AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync) => type switch { - GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType), - GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), - GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType), - GroupType.Combining => CombiningEditor.AddModGroup(mod, newName, saveType), - _ => null, + GroupType.Single => SingleEditor.AddModGroup(mod, newName, saveType), + GroupType.Multi => MultiEditor.AddModGroup(mod, newName, saveType), + GroupType.Imc => ImcEditor.AddModGroup(mod, newName, default, default, saveType), + _ => null, }; public (IModGroup?, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string name, SaveType saveType = SaveType.ImmediateSync) => type switch { - GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType), - GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType), - GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType), - GroupType.Combining => CombiningEditor.FindOrAddModGroup(mod, name, saveType), - _ => (null, -1, false), + GroupType.Single => SingleEditor.FindOrAddModGroup(mod, name, saveType), + GroupType.Multi => MultiEditor.FindOrAddModGroup(mod, name, saveType), + GroupType.Imc => ImcEditor.FindOrAddModGroup(mod, name, saveType), + _ => (null, -1, false), }; public (IModOption?, int, bool) FindOrAddOption(IModGroup group, string name, SaveType saveType = SaveType.ImmediateSync) => group switch { - SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType), - MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType), - ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType), - CombiningModGroup c => CombiningEditor.FindOrAddOption(c, name, saveType), - _ => (null, -1, false), + SingleModGroup s => SingleEditor.FindOrAddOption(s, name, saveType), + MultiModGroup m => MultiEditor.FindOrAddOption(m, name, saveType), + ImcModGroup i => ImcEditor.FindOrAddOption(i, name, saveType), + _ => (null, -1, false), }; public void MoveOption(IModOption option, int toIdx) @@ -296,9 +284,6 @@ public class ModGroupEditor( case ImcSubMod i: ImcEditor.MoveOption(i, toIdx); return; - case CombiningSubMod c: - CombiningEditor.MoveOption(c, toIdx); - return; } } } diff --git a/Penumbra/Mods/Manager/OptionEditor/ModOptionEditor.cs b/Penumbra/Mods/Manager/OptionEditor/ModOptionEditor.cs index d9d672e3..c067102e 100644 --- a/Penumbra/Mods/Manager/OptionEditor/ModOptionEditor.cs +++ b/Penumbra/Mods/Manager/OptionEditor/ModOptionEditor.cs @@ -1,5 +1,5 @@ +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using Penumbra.Mods.Groups; using Penumbra.Mods.Settings; using Penumbra.Mods.SubMods; @@ -15,8 +15,8 @@ public abstract class ModOptionEditor( where TOption : class, IModOption { protected readonly CommunicatorService Communicator = communicator; - protected readonly SaveService SaveService = saveService; - protected readonly Configuration Config = config; + protected readonly SaveService SaveService = saveService; + protected readonly Configuration Config = config; /// Add a new, empty option group of the given type and name. public TGroup? AddModGroup(Mod mod, string newName, SaveType saveType = SaveType.ImmediateSync) @@ -25,7 +25,7 @@ public abstract class ModOptionEditor( return null; var maxPriority = mod.Groups.Count == 0 ? ModPriority.Default : mod.Groups.Max(o => o.Priority) + 1; - var group = CreateGroup(mod, newName, maxPriority); + var group = CreateGroup(mod, newName, maxPriority); mod.Groups.Add(group); SaveService.Save(saveType, new ModSaveGroup(group, Config.ReplaceNonAsciiOnImport)); Communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, group, null, null, -1); @@ -92,8 +92,8 @@ public abstract class ModOptionEditor( /// Delete the given option from the given group. public void DeleteOption(TOption option) { - var mod = option.Mod; - var group = option.Group; + var mod = option.Mod; + var group = option.Group; var optionIdx = option.GetIndex(); Communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, group, option, null, -1); RemoveOption((TGroup)group, optionIdx); @@ -104,7 +104,7 @@ public abstract class ModOptionEditor( /// Move an option inside the given option group. public void MoveOption(TOption option, int optionIdxTo) { - var idx = option.GetIndex(); + var idx = option.GetIndex(); var group = (TGroup)option.Group; if (!MoveOption(group, idx, optionIdxTo)) return; @@ -113,10 +113,10 @@ public abstract class ModOptionEditor( Communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMoved, group.Mod, group, option, null, idx); } - protected abstract TGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync); + protected abstract TGroup CreateGroup(Mod mod, string newName, ModPriority priority, SaveType saveType = SaveType.ImmediateSync); protected abstract TOption? CloneOption(TGroup group, IModOption option); - protected abstract void RemoveOption(TGroup group, int optionIndex); - protected abstract bool MoveOption(TGroup group, int optionIdxFrom, int optionIdxTo); + protected abstract void RemoveOption(TGroup group, int optionIndex); + protected abstract bool MoveOption(TGroup group, int optionIdxFrom, int optionIdxTo); } public static class ModOptionChangeTypeExtension @@ -132,22 +132,22 @@ public static class ModOptionChangeTypeExtension { (requiresSaving, requiresReloading, wasPrepared) = type switch { - ModOptionChangeType.GroupRenamed => (true, false, false), - ModOptionChangeType.GroupAdded => (true, false, false), - ModOptionChangeType.GroupDeleted => (true, true, false), - ModOptionChangeType.GroupMoved => (true, false, false), - ModOptionChangeType.GroupTypeChanged => (true, true, true), - ModOptionChangeType.PriorityChanged => (true, true, true), - ModOptionChangeType.OptionAdded => (true, true, true), - ModOptionChangeType.OptionDeleted => (true, true, false), - ModOptionChangeType.OptionMoved => (true, false, false), - ModOptionChangeType.OptionFilesChanged => (false, true, false), - ModOptionChangeType.OptionFilesAdded => (false, true, true), - ModOptionChangeType.OptionSwapsChanged => (false, true, false), - ModOptionChangeType.OptionMetaChanged => (false, true, false), - ModOptionChangeType.DisplayChange => (false, false, false), + ModOptionChangeType.GroupRenamed => (true, false, false), + ModOptionChangeType.GroupAdded => (true, false, false), + ModOptionChangeType.GroupDeleted => (true, true, false), + ModOptionChangeType.GroupMoved => (true, false, false), + ModOptionChangeType.GroupTypeChanged => (true, true, true), + ModOptionChangeType.PriorityChanged => (true, true, true), + ModOptionChangeType.OptionAdded => (true, true, true), + ModOptionChangeType.OptionDeleted => (true, true, false), + ModOptionChangeType.OptionMoved => (true, false, false), + ModOptionChangeType.OptionFilesChanged => (false, true, false), + ModOptionChangeType.OptionFilesAdded => (false, true, true), + ModOptionChangeType.OptionSwapsChanged => (false, true, false), + ModOptionChangeType.OptionMetaChanged => (false, true, false), + ModOptionChangeType.DisplayChange => (false, false, false), ModOptionChangeType.DefaultOptionChanged => (true, false, false), - _ => (false, false, false), + _ => (false, false, false), }; } } diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs index e262e8f1..488e3dc1 100644 --- a/Penumbra/Mods/Mod.cs +++ b/Penumbra/Mods/Mod.cs @@ -1,7 +1,6 @@ +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using Penumbra.GameData.Data; -using Penumbra.GameData.Structs; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; @@ -11,16 +10,6 @@ using Penumbra.String.Classes; namespace Penumbra.Mods; -[Flags] -public enum FeatureFlags : ulong -{ - None = 0, - Atch = 1ul << 0, - Shp = 1ul << 1, - Atr = 1ul << 2, - Invalid = 1ul << 62, -} - public sealed class Mod : IMod { public static readonly TemporaryMod ForcedFiles = new() @@ -58,45 +47,26 @@ public sealed class Mod : IMod => Name.Text; // Meta Data - public LowerString Name { get; internal set; } = "New Mod"; - public LowerString Author { get; internal set; } = LowerString.Empty; - public string Description { get; internal set; } = string.Empty; - public string Version { get; internal set; } = string.Empty; - public string Website { get; internal set; } = string.Empty; - public string Image { get; internal set; } = string.Empty; - public IReadOnlyList ModTags { get; internal set; } = []; - public HashSet DefaultPreferredItems { get; internal set; } = []; - public FeatureFlags RequiredFeatures { get; internal set; } = 0; + public LowerString Name { get; internal set; } = "New Mod"; + public LowerString Author { get; internal set; } = LowerString.Empty; + public string Description { get; internal set; } = string.Empty; + public string Version { get; internal set; } = string.Empty; + public string Website { get; internal set; } = string.Empty; + public string Image { get; internal set; } = string.Empty; + public IReadOnlyList ModTags { get; internal set; } = []; // Local Data - public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds(); - public IReadOnlyList LocalTags { get; internal set; } = []; - public string Note { get; internal set; } = string.Empty; - public HashSet PreferredChangedItems { get; internal set; } = []; - public bool Favorite { get; internal set; } = false; + public long ImportDate { get; internal set; } = DateTimeOffset.UnixEpoch.ToUnixTimeMilliseconds(); + public IReadOnlyList LocalTags { get; internal set; } = []; + public string Note { get; internal set; } = string.Empty; + public bool Favorite { get; internal set; } = false; + // Options public readonly DefaultSubMod Default; public readonly List Groups = []; - /// Compute the required feature flags for this mod. - public FeatureFlags ComputeRequiredFeatures() - { - var flags = FeatureFlags.None; - foreach (var option in AllDataContainers) - { - if (option.Manipulations.Atch.Count > 0) - flags |= FeatureFlags.Atch; - if (option.Manipulations.Atr.Count > 0) - flags |= FeatureFlags.Atr; - if (option.Manipulations.Shp.Count > 0) - flags |= FeatureFlags.Shp; - } - - return flags; - } - public AppliedModData GetData(ModSettings? settings = null) { if (settings is not { Enabled: true }) @@ -131,14 +101,13 @@ public sealed class Mod : IMod } // Cache - public readonly SortedList ChangedItems = new(); + public readonly SortedList ChangedItems = new(); public string LowerChangedItemsString { get; internal set; } = string.Empty; public string AllTagsLower { get; internal set; } = string.Empty; - public int TotalFileCount { get; internal set; } - public int TotalSwapCount { get; internal set; } - public int TotalManipulations { get; internal set; } - public ushort LastChangedItemsUpdate { get; internal set; } - public bool HasOptions { get; internal set; } + public int TotalFileCount { get; internal set; } + public int TotalSwapCount { get; internal set; } + public int TotalManipulations { get; internal set; } + public bool HasOptions { get; internal set; } } diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index 3a7bd105..1af9c1db 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -3,7 +3,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Filesystem; using OtterGui.Services; using Penumbra.Api.Enums; @@ -28,16 +27,15 @@ public partial class ModCreator( MetaFileManager metaFileManager, GamePathParser gamePathParser) : IService { - public const FeatureFlags SupportedFeatures = FeatureFlags.Atch | FeatureFlags.Shp | FeatureFlags.Atr; - public readonly Configuration Config = config; + public readonly Configuration Config = config; /// Creates directory and files necessary for a new mod without adding it to the manager. - public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "", string? author = null, params string[] tags) + public DirectoryInfo? CreateEmptyMod(DirectoryInfo basePath, string newName, string description = "") { try { var newDir = CreateModFolder(basePath, newName, Config.ReplaceNonAsciiOnImport, true); - dataEditor.CreateMeta(newDir, newName, author ?? Config.DefaultModAuthor, description, "1.0", string.Empty, tags); + dataEditor.CreateMeta(newDir, newName, Config.DefaultModAuthor, description, "1.0", string.Empty); CreateDefaultFiles(newDir); return newDir; } @@ -74,17 +72,23 @@ public partial class ModCreator( if (!Directory.Exists(mod.ModPath.FullName)) return false; - modDataChange = ModMeta.Load(dataEditor, this, mod); - if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0 || mod.RequiredFeatures is FeatureFlags.Invalid) + modDataChange = dataEditor.LoadMeta(this, mod); + if (modDataChange.HasFlag(ModDataChangeType.Deletion) || mod.Name.Length == 0) return false; - modDataChange |= ModLocalData.Load(dataEditor, mod); + modDataChange |= dataEditor.LoadLocalData(mod); LoadDefaultOption(mod); LoadAllGroups(mod); if (incorporateMetaChanges) - IncorporateAllMetaChanges(mod, true, deleteDefaultMetaChanges); - else if (deleteDefaultMetaChanges) - ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, saveService, false); + IncorporateAllMetaChanges(mod, true); + if (deleteDefaultMetaChanges && !Config.KeepDefaultMetaChanges) + { + foreach (var container in mod.AllDataContainers) + { + if (ModMetaEditor.DeleteDefaultValues(metaFileManager, container.Manipulations)) + saveService.ImmediateSaveSync(new ModSaveGroup(container, Config.ReplaceNonAsciiOnImport)); + } + } return true; } @@ -156,21 +160,19 @@ public partial class ModCreator( /// Convert all .meta and .rgsp files to their respective meta changes and add them to their options. /// Deletes the source files if delete is true. /// - public void IncorporateAllMetaChanges(Mod mod, bool delete, bool removeDefaultValues) + public void IncorporateAllMetaChanges(Mod mod, bool delete) { var changes = false; - List deleteList = []; + 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); } DeleteDeleteList(deleteList, delete); - if (removeDefaultValues && !Config.KeepDefaultMetaChanges) - changes |= ModMetaEditor.DeleteDefaultValues(mod, metaFileManager, null, false); if (!changes) return; @@ -184,7 +186,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; @@ -201,7 +203,8 @@ public partial class ModCreator( if (!file.Exists) continue; - var meta = new TexToolsMeta(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}"); deleteList.Add(file.FullName); @@ -213,7 +216,8 @@ 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}"); deleteList.Add(file.FullName); @@ -229,6 +233,9 @@ public partial class ModCreator( DeleteDeleteList(deleteList, delete); var changes = oldSize < option.Manipulations.Count; + if (deleteDefault && !Config.KeepDefaultMetaChanges) + changes |= ModMetaEditor.DeleteDefaultValues(metaFileManager, option.Manipulations); + return (changes, deleteList); } @@ -283,7 +290,7 @@ 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; } @@ -302,7 +309,7 @@ public partial class ModCreator( mod.Default.Files.TryAdd(gamePath, file); } - IncorporateMetaChanges(mod.Default, directory, true); + IncorporateMetaChanges(mod.Default, directory, true, true); saveService.ImmediateSaveSync(new ModSaveGroup(mod.ModPath, mod.Default, Config.ReplaceNonAsciiOnImport)); } @@ -440,10 +447,9 @@ public partial class ModCreator( var json = JObject.Parse(File.ReadAllText(file.FullName)); switch (json[nameof(Type)]?.ToObject() ?? GroupType.Single) { - case GroupType.Multi: return MultiModGroup.Load(mod, json); - case GroupType.Single: return SingleModGroup.Load(mod, json); - case GroupType.Imc: return ImcModGroup.Load(mod, json); - case GroupType.Combining: return CombiningModGroup.Load(mod, json); + case GroupType.Multi: return MultiModGroup.Load(mod, json); + case GroupType.Single: return SingleModGroup.Load(mod, json); + case GroupType.Imc: return ImcModGroup.Load(mod, json); } } catch (Exception e) diff --git a/Penumbra/Mods/ModLocalData.cs b/Penumbra/Mods/ModLocalData.cs index cc20fad6..beda0dc7 100644 --- a/Penumbra/Mods/ModLocalData.cs +++ b/Penumbra/Mods/ModLocalData.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Structs; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -22,83 +21,12 @@ public readonly struct ModLocalData(Mod mod) : ISavable { nameof(Mod.LocalTags), JToken.FromObject(mod.LocalTags) }, { nameof(Mod.Note), JToken.FromObject(mod.Note) }, { nameof(Mod.Favorite), JToken.FromObject(mod.Favorite) }, - { nameof(Mod.PreferredChangedItems), JToken.FromObject(mod.PreferredChangedItems) }, }; using var jWriter = new JsonTextWriter(writer); jWriter.Formatting = Formatting.Indented; jObject.WriteTo(jWriter); } - public static ModDataChangeType Load(ModDataEditor editor, Mod mod) - { - var dataFile = editor.SaveService.FileNames.LocalDataFile(mod); - - var importDate = 0L; - var localTags = Enumerable.Empty(); - var favorite = false; - var note = string.Empty; - - HashSet preferredChangedItems = []; - - var save = true; - if (File.Exists(dataFile)) - try - { - var text = File.ReadAllText(dataFile); - var json = JObject.Parse(text); - - importDate = json[nameof(Mod.ImportDate)]?.Value() ?? importDate; - favorite = json[nameof(Mod.Favorite)]?.Value() ?? favorite; - note = json[nameof(Mod.Note)]?.Value() ?? note; - localTags = (json[nameof(Mod.LocalTags)] as JArray)?.Values().OfType() ?? localTags; - preferredChangedItems = (json[nameof(Mod.PreferredChangedItems)] as JArray)?.Values().Select(i => (CustomItemId) i).ToHashSet() ?? mod.DefaultPreferredItems; - save = false; - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not load local mod data:\n{e}"); - } - else - { - preferredChangedItems = mod.DefaultPreferredItems; - } - - if (importDate == 0) - importDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); - - ModDataChangeType changes = 0; - if (mod.ImportDate != importDate) - { - mod.ImportDate = importDate; - changes |= ModDataChangeType.ImportDate; - } - - changes |= UpdateTags(mod, null, localTags); - - if (mod.Favorite != favorite) - { - mod.Favorite = favorite; - changes |= ModDataChangeType.Favorite; - } - - if (mod.Note != note) - { - mod.Note = note; - changes |= ModDataChangeType.Note; - } - - if (!preferredChangedItems.SetEquals(mod.PreferredChangedItems)) - { - mod.PreferredChangedItems = preferredChangedItems; - changes |= ModDataChangeType.PreferredChangedItems; - } - - if (save) - editor.SaveService.QueueSave(new ModLocalData(mod)); - - return changes; - } - internal static ModDataChangeType UpdateTags(Mod mod, IEnumerable? newModTags, IEnumerable? newLocalTags) { if (newModTags == null && newLocalTags == null) diff --git a/Penumbra/Mods/ModMeta.cs b/Penumbra/Mods/ModMeta.cs index b52eecf4..39dd20e4 100644 --- a/Penumbra/Mods/ModMeta.cs +++ b/Penumbra/Mods/ModMeta.cs @@ -1,7 +1,5 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Penumbra.GameData.Structs; -using Penumbra.Mods.Manager; using Penumbra.Services; namespace Penumbra.Mods; @@ -25,121 +23,9 @@ public readonly struct ModMeta(Mod mod) : ISavable { nameof(Mod.Version), JToken.FromObject(mod.Version) }, { nameof(Mod.Website), JToken.FromObject(mod.Website) }, { nameof(Mod.ModTags), JToken.FromObject(mod.ModTags) }, - { nameof(Mod.DefaultPreferredItems), JToken.FromObject(mod.DefaultPreferredItems) }, }; - if (mod.RequiredFeatures is not FeatureFlags.None) - { - var features = mod.RequiredFeatures; - var array = new JArray(); - foreach (var flag in Enum.GetValues()) - { - if ((features & flag) is not FeatureFlags.None) - array.Add(flag.ToString()); - } - - jObject[nameof(Mod.RequiredFeatures)] = array; - } - using var jWriter = new JsonTextWriter(writer); jWriter.Formatting = Formatting.Indented; jObject.WriteTo(jWriter); } - - public static ModDataChangeType Load(ModDataEditor editor, ModCreator creator, Mod mod) - { - var metaFile = editor.SaveService.FileNames.ModMetaPath(mod); - if (!File.Exists(metaFile)) - { - Penumbra.Log.Debug($"No mod meta found for {mod.ModPath.Name}."); - return ModDataChangeType.Deletion; - } - - try - { - var text = File.ReadAllText(metaFile); - var json = JObject.Parse(text); - - var newFileVersion = json[nameof(FileVersion)]?.Value() ?? 0; - - // Empty name gets checked after loading and is not allowed. - var newName = json[nameof(Mod.Name)]?.Value() ?? string.Empty; - - var newAuthor = json[nameof(Mod.Author)]?.Value() ?? string.Empty; - var newDescription = json[nameof(Mod.Description)]?.Value() ?? string.Empty; - var newImage = json[nameof(Mod.Image)]?.Value() ?? string.Empty; - var newVersion = json[nameof(Mod.Version)]?.Value() ?? string.Empty; - var newWebsite = json[nameof(Mod.Website)]?.Value() ?? string.Empty; - var modTags = (json[nameof(Mod.ModTags)] as JArray)?.Values().OfType(); - var defaultItems = (json[nameof(Mod.DefaultPreferredItems)] as JArray)?.Values().Select(i => (CustomItemId)i).ToHashSet() - ?? []; - var requiredFeatureArray = (json[nameof(Mod.RequiredFeatures)] as JArray)?.Values() ?? []; - var requiredFeatures = FeatureChecker.ParseFlags(mod.ModPath.Name, newName.Length > 0 ? newName : mod.Name.Length > 0 ? mod.Name : "Unknown", requiredFeatureArray!); - - ModDataChangeType changes = 0; - if (mod.Name != newName) - { - changes |= ModDataChangeType.Name; - mod.Name = newName; - } - - if (mod.Author != newAuthor) - { - changes |= ModDataChangeType.Author; - mod.Author = newAuthor; - } - - if (mod.Description != newDescription) - { - changes |= ModDataChangeType.Description; - mod.Description = newDescription; - } - - if (mod.Image != newImage) - { - changes |= ModDataChangeType.Image; - mod.Image = newImage; - } - - if (mod.Version != newVersion) - { - changes |= ModDataChangeType.Version; - mod.Version = newVersion; - } - - if (mod.Website != newWebsite) - { - changes |= ModDataChangeType.Website; - mod.Website = newWebsite; - } - - if (!mod.DefaultPreferredItems.SetEquals(defaultItems)) - { - changes |= ModDataChangeType.DefaultChangedItems; - mod.DefaultPreferredItems = defaultItems; - } - - if (newFileVersion != FileVersion) - if (ModMigration.Migrate(creator, editor.SaveService, mod, json, ref newFileVersion)) - { - changes |= ModDataChangeType.Migration; - editor.SaveService.ImmediateSave(new ModMeta(mod)); - } - - // Required features get checked during parsing, in which case the new required features signal invalid. - if (requiredFeatures != mod.RequiredFeatures) - { - changes |= ModDataChangeType.RequiredFeatures; - mod.RequiredFeatures = requiredFeatures; - } - - changes |= ModLocalData.UpdateTags(mod, modTags, null); - - return changes; - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not load mod meta for {metaFile}:\n{e}"); - return ModDataChangeType.Deletion; - } - } } diff --git a/Penumbra/Mods/ModSelection.cs b/Penumbra/Mods/ModSelection.cs index b728bd00..73d0272b 100644 --- a/Penumbra/Mods/ModSelection.cs +++ b/Penumbra/Mods/ModSelection.cs @@ -36,11 +36,10 @@ public class ModSelection : EventWrapper _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 ModSettings? OwnSettings { get; private set; } - public TemporaryModSettings? TemporarySettings { get; private set; } + 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) { @@ -84,15 +83,12 @@ public class ModSelection : EventWrapper { if (Mod == null) { - Settings = ModSettings.Empty; - Collection = ModCollection.Empty; - OwnSettings = null; + Settings = ModSettings.Empty; + Collection = ModCollection.Empty; } else { - (var settings, Collection) = _collections.Current.GetActualSettings(Mod.Index); - OwnSettings = _collections.Current.GetOwnSettings(Mod.Index); - TemporarySettings = _collections.Current.GetTempSettings(Mod.Index); + (var settings, Collection) = _collections.Current[Mod.Index]; Settings = settings ?? ModSettings.Empty; } } diff --git a/Penumbra/Mods/Settings/FullModSettings.cs b/Penumbra/Mods/Settings/FullModSettings.cs deleted file mode 100644 index 904b56bd..00000000 --- a/Penumbra/Mods/Settings/FullModSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Penumbra.Mods.Settings; - -public readonly record struct FullModSettings(ModSettings? Settings = null, TemporaryModSettings? TempSettings = null) -{ - public static readonly FullModSettings Empty = new(); - - public ModSettings? Resolve() - { - if (TempSettings == null) - return Settings; - if (TempSettings.ForceInherit) - return null; - - return TempSettings; - } - - public FullModSettings DeepCopy() - => new(Settings?.DeepCopy()); -} diff --git a/Penumbra/Mods/Settings/ModSettings.cs b/Penumbra/Mods/Settings/ModSettings.cs index bbdd6bfa..25e4805d 100644 --- a/Penumbra/Mods/Settings/ModSettings.cs +++ b/Penumbra/Mods/Settings/ModSettings.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using OtterGui.Filesystem; using Penumbra.Api.Enums; using Penumbra.Mods.Editor; @@ -11,18 +11,10 @@ namespace Penumbra.Mods.Settings; /// Contains the settings for a given mod. public class ModSettings { - public static readonly ModSettings Empty = new(true); - - public SettingList Settings { get; internal init; } = []; - public ModPriority Priority { get; set; } - public bool Enabled { get; set; } - public bool IsEmpty { get; protected init; } - - public ModSettings() - { } - - protected ModSettings(bool empty) - => IsEmpty = empty; + public static readonly ModSettings Empty = new(); + public SettingList Settings { get; private init; } = []; + public ModPriority Priority { get; set; } + public bool Enabled { get; set; } // Create an independent copy of the current settings. public ModSettings DeepCopy() diff --git a/Penumbra/Mods/Settings/TemporaryModSettings.cs b/Penumbra/Mods/Settings/TemporaryModSettings.cs deleted file mode 100644 index d3e36ef6..00000000 --- a/Penumbra/Mods/Settings/TemporaryModSettings.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Penumbra.Mods.Settings; - -public sealed class TemporaryModSettings : ModSettings -{ - public new static readonly TemporaryModSettings Empty = new(true); - - public const string OwnSource = "yourself"; - public string Source = string.Empty; - public int Lock; - public bool ForceInherit; - - // Create default settings for a given mod. - public static TemporaryModSettings DefaultSettings(Mod mod, string source, bool enabled = false, int key = 0) - => new() - { - Enabled = enabled, - Source = source, - Lock = key, - Priority = ModPriority.Default, - Settings = SettingList.Default(mod), - }; - - public TemporaryModSettings() - { } - - private TemporaryModSettings(bool empty) - : base(empty) - { } - - public TemporaryModSettings(Mod mod, ModSettings? clone, string source = OwnSource, int key = 0) - { - Source = source; - Lock = key; - ForceInherit = clone == null; - if (clone is { IsEmpty: false }) - { - Enabled = clone.Enabled; - Priority = clone.Priority; - Settings = clone.Settings.Clone(); - } - else - { - Enabled = false; - Priority = ModPriority.Default; - Settings = SettingList.Default(mod); - } - } -} - -public static class ModSettingsExtensions -{ - public static bool IsTemporary(this ModSettings? settings) - => settings is TemporaryModSettings; -} diff --git a/Penumbra/Mods/SubMods/CombinedDataContainer.cs b/Penumbra/Mods/SubMods/CombinedDataContainer.cs deleted file mode 100644 index bfca2afd..00000000 --- a/Penumbra/Mods/SubMods/CombinedDataContainer.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Extensions; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Editor; -using Penumbra.Mods.Groups; -using Penumbra.String.Classes; - -namespace Penumbra.Mods.SubMods; - -public class CombinedDataContainer(IModGroup group) : IModDataContainer -{ - public IMod Mod - => Group.Mod; - - public IModGroup Group { get; } = group; - - public string Name { get; set; } = string.Empty; - public Dictionary Files { get; set; } = []; - public Dictionary FileSwaps { get; set; } = []; - public MetaDictionary Manipulations { get; set; } = new(); - - public void AddDataTo(Dictionary redirections, MetaDictionary manipulations) - => SubMod.AddContainerTo(this, redirections, manipulations); - - public string GetName() - { - if (Name.Length > 0) - return Name; - - var index = GetDataIndex(); - if (index == 0) - return "None"; - - var sb = new StringBuilder(128); - for (var i = 0; i < IModGroup.MaxCombiningOptions; ++i) - { - if ((index & 1) != 0) - { - sb.Append(Group.Options[i].Name); - sb.Append(' ').Append('+').Append(' '); - } - - index >>= 1; - if (index == 0) - break; - } - - return sb.ToString(0, sb.Length - 3); - } - - public unsafe string GetDirectoryName() - { - if (Name.Length > 0) - return Name; - - var index = GetDataIndex(); - if (index == 0) - return "None"; - - var text = stackalloc char[IModGroup.MaxCombiningOptions].Slice(0, Group.Options.Count); - for (var i = 0; i < Group.Options.Count; ++i) - { - text[Group.Options.Count - 1 - i] = (index & 1) is 0 ? '0' : '1'; - index >>= 1; - } - - return new string(text); - } - - public string GetFullName() - => $"{Group.Name}: {GetName()}"; - - public (int GroupIndex, int DataIndex) GetDataIndices() - => (Group.GetIndex(), GetDataIndex()); - - private int GetDataIndex() - { - var dataIndex = Group.DataContainers.IndexOf(this); - if (dataIndex < 0) - throw new Exception($"Group {Group.Name} from SubMod {Name} does not contain this SubMod."); - - return dataIndex; - } - - public CombinedDataContainer(CombiningModGroup group, JToken token) - : this(group) - { - SubMod.LoadDataContainer(token, this, group.Mod.ModPath); - Name = token["Name"]?.ToObject() ?? string.Empty; - } -} diff --git a/Penumbra/Mods/SubMods/CombiningSubMod.cs b/Penumbra/Mods/SubMods/CombiningSubMod.cs deleted file mode 100644 index 6eb5de9d..00000000 --- a/Penumbra/Mods/SubMods/CombiningSubMod.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Newtonsoft.Json.Linq; -using Penumbra.Mods.Groups; - -namespace Penumbra.Mods.SubMods; - -public class CombiningSubMod(IModGroup group) : IModOption -{ - public IModGroup Group { get; } = group; - - public Mod Mod - => Group.Mod; - - public string Name { get; set; } = "Option"; - public string Description { get; set; } = string.Empty; - - public string FullName - => $"{Group.Name}: {Name}"; - - public int GetIndex() - => SubMod.GetIndex(this); - - public CombiningSubMod(CombiningModGroup group, JToken json) - : this(group) - => SubMod.LoadOptionData(json, this); -} diff --git a/Penumbra/Mods/SubMods/ComplexDataContainer.cs b/Penumbra/Mods/SubMods/ComplexDataContainer.cs deleted file mode 100644 index 0f0fdef8..00000000 --- a/Penumbra/Mods/SubMods/ComplexDataContainer.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Extensions; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Editor; -using Penumbra.Mods.Groups; -using Penumbra.String.Classes; - -namespace Penumbra.Mods.SubMods; - -public sealed class ComplexDataContainer(ComplexModGroup group) : IModDataContainer -{ - public IMod Mod - => Group.Mod; - - public IModGroup Group { get; } = group; - - public Dictionary Files { get; set; } = []; - public Dictionary FileSwaps { get; set; } = []; - public MetaDictionary Manipulations { get; set; } = new(); - - public MaskedSetting Association = MaskedSetting.Zero; - - public string Name { get; set; } = string.Empty; - - public string GetName() - => Name.Length > 0 ? Name : $"Container {Group.DataContainers.IndexOf(this)}"; - - public string GetDirectoryName() - => Name.Length > 0 ? Name : $"{Group.DataContainers.IndexOf(this)}"; - - public string GetFullName() - => $"{Group.Name}: {GetName()}"; - - public (int GroupIndex, int DataIndex) GetDataIndices() - => (Group.GetIndex(), Group.DataContainers.IndexOf(this)); - - public ComplexDataContainer(ComplexModGroup group, JToken json) - : this(group) - { - SubMod.LoadDataContainer(json, this, group.Mod.ModPath); - var mask = json["AssociationMask"]?.ToObject() ?? 0; - var value = json["AssociationMask"]?.ToObject() ?? 0; - Association = new MaskedSetting(mask, value); - Name = json["Name"]?.ToObject() ?? string.Empty; - } -} diff --git a/Penumbra/Mods/SubMods/ComplexSubMod.cs b/Penumbra/Mods/SubMods/ComplexSubMod.cs deleted file mode 100644 index 7c189170..00000000 --- a/Penumbra/Mods/SubMods/ComplexSubMod.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Newtonsoft.Json.Linq; -using OtterGui.Extensions; -using Penumbra.Mods.Groups; - -namespace Penumbra.Mods.SubMods; - -public sealed class ComplexSubMod(ComplexModGroup group) : IModOption -{ - public Mod Mod - => group.Mod; - - public IModGroup Group { get; } = group; - public string Name { get; set; } = "Option"; - - public string FullName - => $"{Group.Name}: {Name}"; - - public MaskedSetting Conditions = MaskedSetting.Zero; - public int Indentation = 0; - public string SubGroupLabel = string.Empty; - - public string Description { get; set; } = string.Empty; - - public int GetIndex() - => Group.Options.IndexOf(this); - - public ComplexSubMod(ComplexModGroup group, JToken json) - : this(group) - { - SubMod.LoadOptionData(json, this); - var mask = json["ConditionMask"]?.ToObject() ?? 0; - var value = json["ConditionMask"]?.ToObject() ?? 0; - Conditions = new MaskedSetting(mask, value); - Indentation = json["Indentation"]?.ToObject() ?? 0; - SubGroupLabel = json["SubGroup"]?.ToObject() ?? string.Empty; - } -} diff --git a/Penumbra/Mods/SubMods/DefaultSubMod.cs b/Penumbra/Mods/SubMods/DefaultSubMod.cs index 3282f518..3840468f 100644 --- a/Penumbra/Mods/SubMods/DefaultSubMod.cs +++ b/Penumbra/Mods/SubMods/DefaultSubMod.cs @@ -27,9 +27,6 @@ public class DefaultSubMod(IMod mod) : IModDataContainer public string GetName() => FullName; - public string GetDirectoryName() - => GetName(); - public string GetFullName() => FullName; diff --git a/Penumbra/Mods/SubMods/IModDataContainer.cs b/Penumbra/Mods/SubMods/IModDataContainer.cs index 92ccf7e1..1a89ec17 100644 --- a/Penumbra/Mods/SubMods/IModDataContainer.cs +++ b/Penumbra/Mods/SubMods/IModDataContainer.cs @@ -15,7 +15,6 @@ public interface IModDataContainer public MetaDictionary Manipulations { get; set; } public string GetName(); - public string GetDirectoryName(); public string GetFullName(); public (int GroupIndex, int DataIndex) GetDataIndices(); } diff --git a/Penumbra/Mods/SubMods/MaskedSetting.cs b/Penumbra/Mods/SubMods/MaskedSetting.cs deleted file mode 100644 index 75bb46c2..00000000 --- a/Penumbra/Mods/SubMods/MaskedSetting.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Penumbra.Mods.Groups; -using Penumbra.Mods.Settings; - -namespace Penumbra.Mods.SubMods; - -public readonly struct MaskedSetting(Setting mask, Setting value) -{ - public const int MaxSettings = IModGroup.MaxMultiOptions; - public static readonly MaskedSetting Zero = new(Setting.Zero, Setting.Zero); - public static readonly MaskedSetting FullMask = new(Setting.AllBits(IModGroup.MaxComplexOptions), Setting.Zero); - - public readonly Setting Mask = mask; - public readonly Setting Value = new(value.Value & mask.Value); - - public MaskedSetting(ulong mask, ulong value) - : this(new Setting(mask), new Setting(value)) - { } - - public MaskedSetting Limit(int numOptions) - => new(Mask.Value & Setting.AllBits(numOptions).Value, Value.Value); - - public bool IsZero - => Mask.Value is 0; - - public bool IsEnabled(Setting input) - => (input.Value & Mask.Value) == Value.Value; -} diff --git a/Penumbra/Mods/SubMods/OptionSubMod.cs b/Penumbra/Mods/SubMods/OptionSubMod.cs index aa3fed8f..8fac52d8 100644 --- a/Penumbra/Mods/SubMods/OptionSubMod.cs +++ b/Penumbra/Mods/SubMods/OptionSubMod.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; @@ -41,9 +41,6 @@ public abstract class OptionSubMod(IModGroup group) : IModOption, IModDataContai public string GetName() => Name; - public string GetDirectoryName() - => GetName(); - public string GetFullName() => FullName; diff --git a/Penumbra/Mods/SubMods/SubMod.cs b/Penumbra/Mods/SubMods/SubMod.cs index a7a2ee61..f6b1be96 100644 --- a/Penumbra/Mods/SubMods/SubMod.cs +++ b/Penumbra/Mods/SubMods/SubMod.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; +using OtterGui; using Penumbra.Meta.Manipulations; using Penumbra.String.Classes; @@ -81,41 +81,29 @@ public static class SubMod [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath) { - // #TODO: remove comments when TexTools updated. - //if (data.Files.Count > 0) - //{ - j.WritePropertyName(nameof(data.Files)); - j.WriteStartObject(); - foreach (var (gamePath, file) in data.Files) - { - if (file.ToRelPath(basePath, out var relPath)) - { - j.WritePropertyName(gamePath.ToString()); - j.WriteValue(relPath.ToString()); - } - } - - j.WriteEndObject(); - //} - - //if (data.FileSwaps.Count > 0) - //{ - j.WritePropertyName(nameof(data.FileSwaps)); - j.WriteStartObject(); - foreach (var (gamePath, file) in data.FileSwaps) + j.WritePropertyName(nameof(data.Files)); + j.WriteStartObject(); + foreach (var (gamePath, file) in data.Files) + { + if (file.ToRelPath(basePath, out var relPath)) { j.WritePropertyName(gamePath.ToString()); - j.WriteValue(file.ToString()); + j.WriteValue(relPath.ToString()); } + } - j.WriteEndObject(); - //} + j.WriteEndObject(); + j.WritePropertyName(nameof(data.FileSwaps)); + j.WriteStartObject(); + foreach (var (gamePath, file) in data.FileSwaps) + { + j.WritePropertyName(gamePath.ToString()); + j.WriteValue(file.ToString()); + } - //if (data.Manipulations.Count > 0) - //{ - j.WritePropertyName(nameof(data.Manipulations)); - serializer.Serialize(j, data.Manipulations); - //} + j.WriteEndObject(); + j.WritePropertyName(nameof(data.Manipulations)); + serializer.Serialize(j, data.Manipulations); } /// Write the data for a selectable mod option on a JsonWriter. diff --git a/Penumbra/Mods/TemporaryMod.cs b/Penumbra/Mods/TemporaryMod.cs index 8fdd09c5..b5499624 100644 --- a/Penumbra/Mods/TemporaryMod.cs +++ b/Penumbra/Mods/TemporaryMod.cs @@ -63,10 +63,10 @@ public class TemporaryMod : IMod DirectoryInfo? dir = null; try { - dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Identity.Name, config.ReplaceNonAsciiOnImport, true); + dir = ModCreator.CreateModFolder(modManager.BasePath, collection.Name, config.ReplaceNonAsciiOnImport, true); var fileDir = Directory.CreateDirectory(Path.Combine(dir.FullName, "files")); - modManager.DataEditor.CreateMeta(dir, collection.Identity.Name, character ?? config.DefaultModAuthor, - $"Mod generated from temporary collection {collection.Identity.Id} for {character ?? "Unknown Character"} with name {collection.Identity.Name}.", + modManager.DataEditor.CreateMeta(dir, collection.Name, character ?? config.DefaultModAuthor, + $"Mod generated from temporary collection {collection.Id} for {character ?? "Unknown Character"} with name {collection.Name}.", null, null); var mod = new Mod(dir); var defaultMod = mod.Default; @@ -99,11 +99,11 @@ public class TemporaryMod : IMod saveService.ImmediateSaveSync(new ModSaveGroup(dir, defaultMod, config.ReplaceNonAsciiOnImport)); modManager.AddMod(dir, false); Penumbra.Log.Information( - $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identity.Identifier}."); + $"Successfully generated mod {mod.Name} at {mod.ModPath.FullName} for collection {collection.Identifier}."); } catch (Exception e) { - Penumbra.Log.Error($"Could not save temporary collection {collection.Identity.Identifier} to permanent Mod:\n{e}"); + Penumbra.Log.Error($"Could not save temporary collection {collection.Identifier} to permanent Mod:\n{e}"); if (dir != null && Directory.Exists(dir.FullName)) { try diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index d433a0fb..534911df 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -1,6 +1,5 @@ using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; -using Dalamud.Game; +using ImGuiNET; using OtterGui; using OtterGui.Log; using OtterGui.Services; @@ -22,9 +21,8 @@ using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManage using Dalamud.Plugin.Services; using Lumina.Excel.Sheets; using Penumbra.GameData.Data; -using Penumbra.Interop; +using Penumbra.GameData.Files; using Penumbra.Interop.Hooks; -using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.ResourceLoading; namespace Penumbra; @@ -36,7 +34,6 @@ public class Penumbra : IDalamudPlugin public static readonly Logger Log = new(); public static MessageService Messager { get; private set; } = null!; - public static DynamisIpc Dynamis { get; private set; } = null!; private readonly ValidityChecker _validityChecker; private readonly ResidentResourceManager _residentResources; @@ -60,11 +57,8 @@ public class Penumbra : IDalamudPlugin { HookOverrides.Instance = HookOverrides.LoadFile(pluginInterface); _services = StaticServiceManager.CreateProvider(this, pluginInterface, Log); - // Invoke the IPC Penumbra.Launching method before any hooks or other services are created. - _services.GetService(); - Messager = _services.GetService(); - Dynamis = _services.GetService(); - _validityChecker = _services.GetService(); + Messager = _services.GetService(); + _validityChecker = _services.GetService(); _services.EnsureRequiredServices(); var startup = _services.GetService() @@ -193,7 +187,7 @@ public class Penumbra : IDalamudPlugin ReadOnlySpan relevantPlugins = [ "Glamourer", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge", - "IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn", + "IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "RoleplayingVoiceDalamud", ]; var plugins = _services.GetService().InstalledPlugins .GroupBy(p => p.InternalName) @@ -211,11 +205,9 @@ public class Penumbra : IDalamudPlugin public string GatherSupportInformation() { - var sb = new StringBuilder(10240); - var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory); - var cloudSynced = exists && CloudApi.IsCloudSynced(_config.ModDirectory); - var hdrEnabler = _services.GetService(); - var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null; + var sb = new StringBuilder(10240); + var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory); + var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null; sb.AppendLine("**Settings**"); sb.Append($"> **`Plugin Version: `** {_validityChecker.Version}\n"); sb.Append($"> **`Commit Hash: `** {_validityChecker.CommitHash}\n"); @@ -224,21 +216,16 @@ public class Penumbra : IDalamudPlugin sb.Append($"> **`Operating System: `** {(Dalamud.Utility.Util.IsWine() ? "Mac/Linux (Wine)" : "Windows")}\n"); if (Dalamud.Utility.Util.IsWine()) sb.Append($"> **`Locale Environment Variables:`** {CollectLocaleEnvironmentVariables()}\n"); - sb.Append( - $"> **`Root Directory: `** `{_config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}{(cloudSynced ? ", Cloud-Synced" : "")}\n"); + sb.Append($"> **`Root Directory: `** `{_config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}\n"); sb.Append( $"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n"); sb.Append($"> **`Game Data Files: `** {(_gameData.HasModifiedGameDataFiles ? "Modified" : "Pristine")}\n"); sb.Append($"> **`Auto-Deduplication: `** {_config.AutoDeduplicateOnImport}\n"); sb.Append($"> **`Auto-UI-Reduplication: `** {_config.AutoReduplicateUiOnImport}\n"); sb.Append($"> **`Debug Mode: `** {_config.DebugMode}\n"); - sb.Append($"> **`Penumbra Reloads: `** {hdrEnabler.PenumbraReloadCount}\n"); - sb.Append( - $"> **`HDR Enabled (from Start): `** {_config.HdrRenderTargets} ({hdrEnabler is { FirstLaunchHdrState: true, FirstLaunchHdrHookOverrideState: true }}){(hdrEnabler.HdrEnabledSuccess ? ", Detour Called" : ", **NEVER CALLED**")}\n"); - sb.Append($"> **`Custom Shapes Enabled: `** {_config.EnableCustomShapes}\n"); sb.Append($"> **`Hook Overrides: `** {HookOverrides.Instance.IsCustomLoaded}\n"); sb.Append( - $"> **`Synchronous Load (Dalamud): `** {(_services.GetService().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")} (first Start: {hdrEnabler.FirstLaunchWaitForPluginsState?.ToString() ?? "Unknown"})\n"); + $"> **`Synchronous Load (Dalamud): `** {(_services.GetService().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n"); sb.Append( $"> **`Logging: `** Log: {_config.Ephemeral.EnableResourceLogging}, Watcher: {_config.Ephemeral.EnableResourceWatcher} ({_config.MaxResourceWatcherRecords})\n"); sb.Append($"> **`Use Ownership: `** {_config.UseOwnerNameForCharacterCollection}\n"); @@ -258,24 +245,24 @@ public class Penumbra : IDalamudPlugin void PrintCollection(ModCollection c, CollectionCache _) => sb.Append( - $"> **`Collection {c.Identity.AnonymizedName + ':',-18}`** Inheritances: `{c.Inheritance.DirectlyInheritsFrom.Count,3}`, Enabled Mods: `{c.ActualSettings.Count(s => s is { Enabled: true }),4}`, Conflicts: `{c.AllConflicts.SelectMany(x => x).Sum(x => x is { HasPriority: true, Solved: true } ? x.Conflicts.Count : 0),5}/{c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority ? x.Conflicts.Count : 0),5}`\n"); + $"> **`Collection {c.AnonymizedName + ':',-18}`** Inheritances: `{c.DirectlyInheritsFrom.Count,3}`, Enabled Mods: `{c.ActualSettings.Count(s => s is { Enabled: true }),4}`, Conflicts: `{c.AllConflicts.SelectMany(x => x).Sum(x => x is { HasPriority: true, Solved: true } ? x.Conflicts.Count : 0),5}/{c.AllConflicts.SelectMany(x => x).Sum(x => x.HasPriority ? x.Conflicts.Count : 0),5}`\n"); sb.AppendLine("**Collections**"); sb.Append($"> **`#Collections: `** {_collectionManager.Storage.Count - 1}\n"); sb.Append($"> **`#Temp Collections: `** {_tempCollections.Count}\n"); sb.Append($"> **`Active Collections: `** {_collectionManager.Caches.Count}\n"); - sb.Append($"> **`Base Collection: `** {_collectionManager.Active.Default.Identity.AnonymizedName}\n"); - sb.Append($"> **`Interface Collection: `** {_collectionManager.Active.Interface.Identity.AnonymizedName}\n"); - sb.Append($"> **`Selected Collection: `** {_collectionManager.Active.Current.Identity.AnonymizedName}\n"); + sb.Append($"> **`Base Collection: `** {_collectionManager.Active.Default.AnonymizedName}\n"); + sb.Append($"> **`Interface Collection: `** {_collectionManager.Active.Interface.AnonymizedName}\n"); + sb.Append($"> **`Selected Collection: `** {_collectionManager.Active.Current.AnonymizedName}\n"); foreach (var (type, name, _) in CollectionTypeExtensions.Special) { var collection = _collectionManager.Active.ByType(type); if (collection != null) - sb.Append($"> **`{name,-29}`** {collection.Identity.AnonymizedName}\n"); + sb.Append($"> **`{name,-29}`** {collection.AnonymizedName}\n"); } foreach (var (name, id, collection) in _collectionManager.Active.Individuals.Assignments) - sb.Append($"> **`{id[0].Incognito(name) + ':',-29}`** {collection.Identity.AnonymizedName}\n"); + sb.Append($"> **`{id[0].Incognito(name) + ':',-29}`** {collection.AnonymizedName}\n"); foreach (var collection in _collectionManager.Caches.Active) PrintCollection(collection, collection._cache!); diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index fa45ffbf..9b613729 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -1,17 +1,26 @@ - + + net8.0-windows + preview + x64 Penumbra absolute gangstas Penumbra - Copyright © 2025 + Copyright © 2022 9.0.0.1 9.0.0.1 bin\$(Configuration)\ + true + enable + true + false + false + true + $(MSBuildWarningsAsMessages);MSB3277 PROFILING; - false @@ -24,16 +33,41 @@ PreserveNewest - - PreserveNewest - DirectXTexC.dll - - - PreserveNewest - + + $(AppData)\XIVLauncher\addon\Hooks\dev\ + + + + $(DalamudLibPath)Dalamud.dll + False + + + $(DalamudLibPath)ImGui.NET.dll + False + + + $(DalamudLibPath)ImGuiScene.dll + False + + + $(DalamudLibPath)Lumina.dll + False + + + $(DalamudLibPath)Lumina.Excel.dll + False + + + $(DalamudLibPath)FFXIVClientStructs.dll + False + + + $(DalamudLibPath)Newtonsoft.Json.dll + False + $(DalamudLibPath)Iced.dll False @@ -46,22 +80,20 @@ $(DalamudLibPath)SharpDX.Direct3D11.dll False - - $(DalamudLibPath)SharpDX.DXGI.dll - False - lib\OtterTex.dll + + - - - - - + + + + + @@ -72,6 +104,16 @@ + + + PreserveNewest + DirectXTexC.dll + + + PreserveNewest + + + diff --git a/Penumbra/Penumbra.json b/Penumbra/Penumbra.json index 32032282..4790da18 100644 --- a/Penumbra/Penumbra.json +++ b/Penumbra/Penumbra.json @@ -1,5 +1,5 @@ { - "Author": "Ottermandias, Nylfae, Adam, Wintermute", + "Author": "Ottermandias, Adam, Wintermute", "Name": "Penumbra", "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", @@ -8,9 +8,9 @@ "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "Tags": [ "modding" ], - "DalamudApiLevel": 13, + "DalamudApiLevel": 11, "LoadPriority": 69420, - "LoadRequiredState": 2, + "LoadState": 2, "LoadSync": true, "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } diff --git a/Penumbra/Services/CleanupService.cs b/Penumbra/Services/CleanupService.cs deleted file mode 100644 index bf76f5f0..00000000 --- a/Penumbra/Services/CleanupService.cs +++ /dev/null @@ -1,165 +0,0 @@ -using OtterGui.Services; -using Penumbra.Collections.Manager; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class CleanupService(SaveService saveService, ModManager mods, CollectionManager collections) : IService -{ - private CancellationTokenSource _cancel = new(); - private Task? _task; - - public double Progress { get; private set; } - - public bool IsRunning - => _task is { IsCompleted: false }; - - public void Cancel() - => _cancel.Cancel(); - - public void CleanUnusedLocalData() - { - if (IsRunning) - return; - - var usedFiles = mods.Select(saveService.FileNames.LocalDataFile).ToHashSet(); - Progress = 0; - var deleted = 0; - _cancel = new CancellationTokenSource(); - _task = Task.Run(() => - { - var localFiles = saveService.FileNames.LocalDataFiles.ToList(); - var step = 0.9 / localFiles.Count; - Progress = 0.1; - foreach (var file in localFiles) - { - if (_cancel.IsCancellationRequested) - break; - - try - { - if (!file.Exists || usedFiles.Contains(file.FullName)) - continue; - - file.Delete(); - Penumbra.Log.Debug($"[CleanupService] Deleted unused local data file {file.Name}."); - ++deleted; - } - catch (Exception ex) - { - Penumbra.Log.Error($"[CleanupService] Failed to delete unused local data file {file.Name}:\n{ex}"); - } - - Progress += step; - } - - Penumbra.Log.Information($"[CleanupService] Deleted {deleted} unused local data files."); - Progress = 1; - }); - } - - public void CleanBackupFiles() - { - if (IsRunning) - return; - - Progress = 0; - var deleted = 0; - _cancel = new CancellationTokenSource(); - _task = Task.Run(() => - { - var configFiles = Directory.EnumerateFiles(saveService.FileNames.ConfigDirectory, "*.json.bak", SearchOption.AllDirectories) - .ToList(); - Progress = 0.1; - if (_cancel.IsCancellationRequested) - return; - - var groupFiles = mods.BasePath.EnumerateFiles("group_*.json.bak", SearchOption.AllDirectories).ToList(); - Progress = 0.5; - var step = 0.4 / (groupFiles.Count + configFiles.Count); - foreach (var file in groupFiles) - { - if (_cancel.IsCancellationRequested) - break; - - try - { - if (!file.Exists) - continue; - - file.Delete(); - ++deleted; - Penumbra.Log.Debug($"[CleanupService] Deleted group backup file {file.FullName}."); - } - catch (Exception ex) - { - Penumbra.Log.Error($"[CleanupService] Failed to delete group backup file {file.FullName}:\n{ex}"); - } - - Progress += step; - } - - Penumbra.Log.Information($"[CleanupService] Deleted {deleted} group backup files."); - - deleted = 0; - foreach (var file in configFiles) - { - if (_cancel.IsCancellationRequested) - break; - - try - { - if (!File.Exists(file)) - continue; - - File.Delete(file); - ++deleted; - Penumbra.Log.Debug($"[CleanupService] Deleted config backup file {file}."); - } - catch (Exception ex) - { - Penumbra.Log.Error($"[CleanupService] Failed to delete config backup file {file}:\n{ex}"); - } - - Progress += step; - } - - Penumbra.Log.Information($"[CleanupService] Deleted {deleted} config backup files."); - Progress = 1; - }); - } - - public void CleanupAllUnusedSettings() - { - if (IsRunning) - return; - - Progress = 0; - var totalRemoved = 0; - var diffCollections = 0; - _cancel = new CancellationTokenSource(); - _task = Task.Run(() => - { - var step = 1.0 / collections.Storage.Count; - foreach (var collection in collections.Storage) - { - if (_cancel.IsCancellationRequested) - break; - - var count = collections.Storage.CleanUnavailableSettings(collection); - if (count > 0) - { - Penumbra.Log.Debug( - $"[CleanupService] Removed {count} unused settings from collection {collection.Identity.AnonymizedName}."); - totalRemoved += count; - ++diffCollections; - } - - Progress += step; - } - - Penumbra.Log.Information($"[CleanupService] Removed {totalRemoved} unused settings from {diffCollections} separate collections."); - Progress = 1; - }); - } -} diff --git a/Penumbra/Services/CommunicatorService.cs b/Penumbra/Services/CommunicatorService.cs index 35f15e9e..5d745419 100644 --- a/Penumbra/Services/CommunicatorService.cs +++ b/Penumbra/Services/CommunicatorService.cs @@ -81,12 +81,6 @@ public class CommunicatorService : IDisposable, IService /// public readonly ResolvedFileChanged ResolvedFileChanged = new(); - /// - public readonly PcpCreation PcpCreation = new(); - - /// - public readonly PcpParsing PcpParsing = new(); - public void Dispose() { CollectionChange.Dispose(); @@ -111,7 +105,5 @@ public class CommunicatorService : IDisposable, IService ChangedItemClick.Dispose(); SelectTab.Dispose(); ResolvedFileChanged.Dispose(); - PcpCreation.Dispose(); - PcpParsing.Dispose(); } } diff --git a/Penumbra/Services/ConfigMigrationService.cs b/Penumbra/Services/ConfigMigrationService.cs index 9fe8c420..5ba57cf4 100644 --- a/Penumbra/Services/ConfigMigrationService.cs +++ b/Penumbra/Services/ConfigMigrationService.cs @@ -27,8 +27,8 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu private Configuration _config = null!; private JObject _data = null!; - public string CurrentCollection = ModCollectionIdentity.DefaultCollectionName; - public string DefaultCollection = ModCollectionIdentity.DefaultCollectionName; + public string CurrentCollection = ModCollection.DefaultCollectionName; + public string DefaultCollection = ModCollection.DefaultCollectionName; public string ForcedCollection = string.Empty; public Dictionary CharacterCollections = []; public Dictionary ModSortOrder = []; @@ -240,7 +240,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu if (jObject["Name"]?.ToObject() == ForcedCollection) continue; - jObject[nameof(ModCollectionInheritance.DirectlyInheritsFrom)] = JToken.FromObject(new List { ForcedCollection }); + jObject[nameof(ModCollection.DirectlyInheritsFrom)] = JToken.FromObject(new List { ForcedCollection }); File.WriteAllText(collection.FullName, jObject.ToString()); } catch (Exception e) @@ -346,7 +346,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu if (!collectionJson.Exists) return; - var defaultCollectionFile = new FileInfo(saveService.FileNames.CollectionFile(ModCollectionIdentity.DefaultCollectionName)); + var defaultCollectionFile = new FileInfo(saveService.FileNames.CollectionFile(ModCollection.DefaultCollectionName)); if (defaultCollectionFile.Exists) return; @@ -380,7 +380,7 @@ public class ConfigMigrationService(SaveService saveService, BackupService backu var emptyStorage = new ModStorage(); // Only used for saving and immediately discarded, so the local collection id here is irrelevant. - var collection = ModCollection.CreateFromData(saveService, emptyStorage, ModCollectionIdentity.New(ModCollectionIdentity.DefaultCollectionName, LocalCollectionId.Zero, 1), 0, dict, []); + var collection = ModCollection.CreateFromData(saveService, emptyStorage, Guid.NewGuid(), ModCollection.DefaultCollectionName, LocalCollectionId.Zero, 0, 1, dict, []); saveService.ImmediateSaveSync(new ModCollectionSave(emptyStorage, collection)); } catch (Exception e) diff --git a/Penumbra/Services/CrashHandlerService.cs b/Penumbra/Services/CrashHandlerService.cs index 4814795c..9103b29c 100644 --- a/Penumbra/Services/CrashHandlerService.cs +++ b/Penumbra/Services/CrashHandlerService.cs @@ -240,7 +240,7 @@ public sealed class CrashHandlerService : IDisposable, IService var name = GetActorName(character); lock (_eventWriter) { - _eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Identity.Id, type); + _eventWriter?.AnimationFuncInvoked.WriteLine(character, name.Span, collection.Id, type); } } catch (Exception ex) @@ -293,7 +293,7 @@ public sealed class CrashHandlerService : IDisposable, IService var name = GetActorName(resolveData.AssociatedGameObject); lock (_eventWriter) { - _eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Identity.Id, + _eventWriter!.FileLoaded.WriteLine(resolveData.AssociatedGameObject, name.Span, resolveData.ModCollection.Id, manipulatedPath.Value.InternalName.Span, originalPath.Path.Span); } } diff --git a/Penumbra/Services/FileWatcher.cs b/Penumbra/Services/FileWatcher.cs deleted file mode 100644 index 1d572f05..00000000 --- a/Penumbra/Services/FileWatcher.cs +++ /dev/null @@ -1,209 +0,0 @@ -using OtterGui.Services; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class FileWatcher : IDisposable, IService -{ - // TODO: use ConcurrentSet when it supports comparers in Luna. - private readonly ConcurrentDictionary _pending = new(StringComparer.OrdinalIgnoreCase); - private readonly ModImportManager _modImportManager; - private readonly MessageService _messageService; - private readonly Configuration _config; - - private bool _pausedConsumer; - private FileSystemWatcher? _fsw; - private CancellationTokenSource? _cts = new(); - private Task? _consumer; - - public FileWatcher(ModImportManager modImportManager, MessageService messageService, Configuration config) - { - _modImportManager = modImportManager; - _messageService = messageService; - _config = config; - - if (_config.EnableDirectoryWatch) - { - SetupFileWatcher(_config.WatchDirectory); - SetupConsumerTask(); - } - } - - public void Toggle(bool value) - { - if (_config.EnableDirectoryWatch == value) - return; - - _config.EnableDirectoryWatch = value; - _config.Save(); - if (value) - { - SetupFileWatcher(_config.WatchDirectory); - SetupConsumerTask(); - } - else - { - EndFileWatcher(); - EndConsumerTask(); - } - } - - internal void PauseConsumer(bool pause) - => _pausedConsumer = pause; - - private void EndFileWatcher() - { - if (_fsw is null) - return; - - _fsw.Dispose(); - _fsw = null; - } - - private void SetupFileWatcher(string directory) - { - EndFileWatcher(); - _fsw = new FileSystemWatcher - { - IncludeSubdirectories = false, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime, - InternalBufferSize = 32 * 1024, - }; - - // Only wake us for the exact patterns we care about - _fsw.Filters.Add("*.pmp"); - _fsw.Filters.Add("*.pcp"); - _fsw.Filters.Add("*.ttmp"); - _fsw.Filters.Add("*.ttmp2"); - - _fsw.Created += OnPath; - _fsw.Renamed += OnPath; - UpdateDirectory(directory); - } - - - private void EndConsumerTask() - { - if (_cts is not null) - { - _cts.Cancel(); - _cts = null; - } - _consumer = null; - } - - private void SetupConsumerTask() - { - EndConsumerTask(); - _cts = new CancellationTokenSource(); - _consumer = Task.Factory.StartNew( - () => ConsumerLoopAsync(_cts.Token), - _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); - } - - public void UpdateDirectory(string newPath) - { - if (_config.WatchDirectory != newPath) - { - _config.WatchDirectory = newPath; - _config.Save(); - } - - if (_fsw is null) - return; - - _fsw.EnableRaisingEvents = false; - if (!Directory.Exists(newPath) || newPath.Length is 0) - { - _fsw.Path = string.Empty; - } - else - { - _fsw.Path = newPath; - _fsw.EnableRaisingEvents = true; - } - } - - private void OnPath(object? sender, FileSystemEventArgs e) - => _pending.TryAdd(e.FullPath, 0); - - private async Task ConsumerLoopAsync(CancellationToken token) - { - while (true) - { - var (path, _) = _pending.FirstOrDefault(); - if (path is null || _pausedConsumer) - { - await Task.Delay(500, token).ConfigureAwait(false); - continue; - } - - try - { - await ProcessOneAsync(path, token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - Penumbra.Log.Debug("[FileWatcher] Canceled via Token."); - } - catch (Exception ex) - { - Penumbra.Log.Warning($"[FileWatcher] Error during Processing: {ex}"); - } - finally - { - _pending.TryRemove(path, out _); - } - } - } - - private async Task ProcessOneAsync(string path, CancellationToken token) - { - // Downloads often finish via rename; file may be locked briefly. - // Wait until it exists and is readable; also require two stable size checks. - const int maxTries = 40; - long lastLen = -1; - - for (var i = 0; i < maxTries && !token.IsCancellationRequested; i++) - { - if (!File.Exists(path)) - { - await Task.Delay(100, token); - continue; - } - - try - { - var fi = new FileInfo(path); - var len = fi.Length; - if (len > 0 && len == lastLen) - { - if (_config.EnableAutomaticModImport) - _modImportManager.AddUnpack(path); - else - _messageService.AddMessage(new InstallNotification(_modImportManager, path), false); - return; - } - - lastLen = len; - } - catch (IOException) - { - Penumbra.Log.Debug($"[FileWatcher] File is still being written to."); - } - catch (UnauthorizedAccessException) - { - Penumbra.Log.Debug($"[FileWatcher] File is locked."); - } - - await Task.Delay(150, token); - } - } - - - public void Dispose() - { - EndConsumerTask(); - EndFileWatcher(); - } -} diff --git a/Penumbra/Services/FilenameService.cs b/Penumbra/Services/FilenameService.cs index ee096109..817af0d2 100644 --- a/Penumbra/Services/FilenameService.cs +++ b/Penumbra/Services/FilenameService.cs @@ -24,7 +24,7 @@ public class FilenameService(IDalamudPluginInterface pi) : IService /// Obtain the path of a collection file given its name. public string CollectionFile(ModCollection collection) - => CollectionFile(collection.Identity.Identifier); + => CollectionFile(collection.Identifier); /// Obtain the path of a collection file given its name. public string CollectionFile(string collectionName) diff --git a/Penumbra/Services/InstallNotification.cs b/Penumbra/Services/InstallNotification.cs deleted file mode 100644 index e3956076..00000000 --- a/Penumbra/Services/InstallNotification.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.EventArgs; -using OtterGui.Text; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class InstallNotification(ModImportManager modImportManager, string filePath) : OtterGui.Classes.MessageService.IMessage -{ - public string Message - => "A new mod has been found!"; - - public NotificationType NotificationType - => NotificationType.Info; - - public uint NotificationDuration - => uint.MaxValue; - - public string NotificationTitle { get; } = Path.GetFileNameWithoutExtension(filePath); - - public string LogMessage - => $"A new mod has been found: {Path.GetFileName(filePath)}"; - - public void OnNotificationActions(INotificationDrawArgs args) - { - var region = ImGui.GetContentRegionAvail(); - var buttonSize = new Vector2((region.X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); - if (ImUtf8.ButtonEx("Install"u8, ""u8, buttonSize)) - { - modImportManager.AddUnpack(filePath); - args.Notification.DismissNow(); - } - - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Ignore"u8, ""u8, buttonSize)) - args.Notification.DismissNow(); - } -} diff --git a/Penumbra/Services/MessageService.cs b/Penumbra/Services/MessageService.cs index 70ccf47b..e610cb6a 100644 --- a/Penumbra/Services/MessageService.cs +++ b/Penumbra/Services/MessageService.cs @@ -7,7 +7,6 @@ using Dalamud.Plugin.Services; using Lumina.Excel.Sheets; using OtterGui.Log; using OtterGui.Services; -using Penumbra.GameData.Data; using Penumbra.Mods.Manager; using Penumbra.String.Classes; using Notification = OtterGui.Classes.Notification; @@ -30,7 +29,7 @@ public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INoti new TextPayload($"{(char)SeIconChar.LinkMarker}"), new UIForegroundPayload(0), new UIGlowPayload(0), - new TextPayload(item.Name.ExtractTextExtended()), + new TextPayload(item.Name.ExtractText()), new RawPayload([0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]), new RawPayload([0x02, 0x13, 0x02, 0xEC, 0x03]), }; diff --git a/Penumbra/Services/MigrationManager.cs b/Penumbra/Services/MigrationManager.cs index 8db62e48..aa2d445e 100644 --- a/Penumbra/Services/MigrationManager.cs +++ b/Penumbra/Services/MigrationManager.cs @@ -1,14 +1,9 @@ using Dalamud.Interface.ImGuiNotification; -using Lumina.Data.Files; using OtterGui.Classes; using OtterGui.Services; -using Lumina.Extensions; -using Penumbra.GameData.Files.Utility; -using Penumbra.Import.Textures; +using Penumbra.GameData.Files; using SharpCompress.Common; using SharpCompress.Readers; -using MdlFile = Penumbra.GameData.Files.MdlFile; -using MtrlFile = Penumbra.GameData.Files.MtrlFile; namespace Penumbra.Services; @@ -300,26 +295,6 @@ public class MigrationManager(Configuration config) : IService } } - public void FixMipMaps(IReader reader, string directory, ExtractionOptions options) - { - var path = Path.Combine(directory, reader.Entry.Key!); - using var s = new MemoryStream(); - using var e = reader.OpenEntryStream(); - e.CopyTo(s); - var length = s.Position; - s.Seek(0, SeekOrigin.Begin); - var br = new BinaryReader(s, Encoding.UTF8, true); - var header = br.ReadStructure(); - br.Dispose(); - TexFileParser.FixMipOffsets(length, ref header, out var actualSize); - - s.Seek(0, SeekOrigin.Begin); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - using var f = File.Open(path, FileMode.Create, FileAccess.Write); - f.Write(header); - f.Write(s.GetBuffer().AsSpan(80, (int)actualSize - 80)); - } - /// Update the data of a .mdl file during TTMP extraction. Returns either the existing array or a new one. public byte[] MigrateTtmpModel(string path, byte[] data) { @@ -372,25 +347,6 @@ public class MigrationManager(Configuration config) : IService } } - public byte[] FixTtmpMipMaps(string path, byte[] data) - { - using var m = new MemoryStream(data); - var br = new BinaryReader(m, Encoding.UTF8, true); - var header = br.ReadStructure(); - br.Dispose(); - TexFileParser.FixMipOffsets(data.Length, ref header, out var actualSize); - if (actualSize == data.Length) - return data; - - var ret = new byte[actualSize]; - using var m2 = new MemoryStream(ret); - using var bw = new BinaryWriter(m2); - bw.Write(header); - bw.Write(data.AsSpan(80, (int)actualSize - 80)); - - return ret; - } - private static bool MigrateModel(string path, MdlFile mdl, bool createBackup) { diff --git a/Penumbra/Services/ModMigrator.cs b/Penumbra/Services/ModMigrator.cs deleted file mode 100644 index 043d9631..00000000 --- a/Penumbra/Services/ModMigrator.cs +++ /dev/null @@ -1,349 +0,0 @@ -using Dalamud.Plugin.Services; -using OtterGui.Classes; -using OtterGui.Services; -using Penumbra.Api.Enums; -using Penumbra.GameData.Data; -using Penumbra.GameData.Files; -using Penumbra.GameData.Files.MaterialStructs; -using Penumbra.GameData.Structs; -using Penumbra.Import.Textures; -using Penumbra.Mods; -using Penumbra.Mods.SubMods; -using Penumbra.String.Classes; - -namespace Penumbra.Services; - -public class ModMigrator(IDataManager gameData, TextureManager textures) : IService -{ - private sealed class FileDataDict : MultiDictionary; - - private readonly Lazy _glassReferenceMaterial = new(() => - { - var bytes = gameData.GetFile("chara/equipment/e5001/material/v0001/mt_c0101e5001_met_b.mtrl"); - return new MtrlFile(bytes!.Data); - }); - - private readonly HashSet _changedMods = []; - private readonly HashSet _failedMods = []; - - private readonly FileDataDict Textures = []; - private readonly FileDataDict Models = []; - private readonly FileDataDict Materials = []; - private readonly FileDataDict FileSwaps = []; - - private readonly ConcurrentBag _messages = []; - - public void Update(IEnumerable mods) - { - CollectFiles(mods); - foreach (var (from, (to, container)) in FileSwaps) - MigrateFileSwaps(from, to, container); - foreach (var (model, list) in Models.Grouped) - MigrateModel(model, (Mod)list[0].Container.Mod); - } - - private void CollectFiles(IEnumerable mods) - { - foreach (var mod in mods) - { - foreach (var container in mod.AllDataContainers) - { - foreach (var (gamePath, file) in container.Files) - { - switch (ResourceTypeExtensions.FromExtension(gamePath.Extension().Span)) - { - case ResourceType.Tex: Textures.TryAdd(file.FullName, (gamePath.ToString(), container)); break; - case ResourceType.Mdl: Models.TryAdd(file.FullName, (gamePath.ToString(), container)); break; - case ResourceType.Mtrl: Materials.TryAdd(file.FullName, (gamePath.ToString(), container)); break; - } - } - - foreach (var (swapFrom, swapTo) in container.FileSwaps) - FileSwaps.TryAdd(swapTo.FullName, (swapFrom.ToString(), container)); - } - } - } - - public Task CreateIndexFile(string normalPath, string targetPath) - { - const int rowBlend = 17; - - return Task.Run(async () => - { - var tex = textures.LoadTex(normalPath); - var data = tex.GetPixelData(); - var rgbaData = new RgbaPixelData(data.Width, data.Height, data.Rgba); - if (!BitOperations.IsPow2(rgbaData.Height) || !BitOperations.IsPow2(rgbaData.Width)) - { - var requiredHeight = (int)BitOperations.RoundUpToPowerOf2((uint)rgbaData.Height); - var requiredWidth = (int)BitOperations.RoundUpToPowerOf2((uint)rgbaData.Width); - rgbaData = rgbaData.Resize((requiredWidth, requiredHeight)); - } - - Parallel.ForEach(Enumerable.Range(0, rgbaData.PixelData.Length / 4), idx => - { - var pixelIdx = 4 * idx; - var normal = rgbaData.PixelData[pixelIdx + 3]; - - // Copied from TT - var blendRem = normal % (2 * rowBlend); - var originalRow = normal / rowBlend; - switch (blendRem) - { - // Goes to next row, clamped to the closer row. - case > 25: - blendRem = 0; - ++originalRow; - break; - // Stays in this row, clamped to the closer row. - case > 17: blendRem = 17; break; - } - - var newBlend = (byte)(255 - MathF.Round(blendRem / 17f * 255f)); - - // Slight add here to push the color deeper into the row to ensure BC5 compression doesn't - // cause any artifacting. - var newRow = (byte)(originalRow / 2 * 17 + 4); - - rgbaData.PixelData[pixelIdx] = newRow; - rgbaData.PixelData[pixelIdx] = newBlend; - rgbaData.PixelData[pixelIdx] = 0; - rgbaData.PixelData[pixelIdx] = 255; - }); - await textures.SaveAs(CombinedTexture.TextureSaveType.BC5, true, true, new BaseImage(), targetPath, rgbaData.PixelData, - rgbaData.Width, rgbaData.Height); - }); - } - - private void MigrateModel(string filePath, Mod mod) - { - if (MigrationManager.TryMigrateSingleModel(filePath, true)) - { - _messages.Add($"Migrated model {filePath} in {mod.Name}."); - } - else - { - _messages.Add($"Failed to migrate model {filePath} in {mod.Name}"); - _failedMods.Add(mod); - } - } - - private void SetGlassReferenceValues(MtrlFile mtrl) - { - var reference = _glassReferenceMaterial.Value; - mtrl.ShaderPackage.ShaderKeys = reference.ShaderPackage.ShaderKeys.ToArray(); - mtrl.ShaderPackage.Constants = reference.ShaderPackage.Constants.ToArray(); - mtrl.AdditionalData = reference.AdditionalData.ToArray(); - mtrl.ShaderPackage.Flags &= ~(0x04u | 0x08u); - // From TT. - if (mtrl.Table is ColorTable t) - foreach (ref var row in t.AsRows()) - row.SpecularColor = new HalfColor((Half)0.8100586, (Half)0.8100586, (Half)0.8100586); - } - - private ref struct MaterialPack - { - public readonly MtrlFile File; - public readonly bool UsesMaskAsSpecular; - - private readonly Dictionary Samplers = []; - - public MaterialPack(MtrlFile file) - { - File = file; - UsesMaskAsSpecular = File.ShaderPackage.ShaderKeys.Any(x => x.Key is 0xC8BD1DEF && x.Value is 0xA02F4828 or 0x198D11CD); - Add(Samplers, TextureUsage.Normal, ShpkFile.NormalSamplerId); - Add(Samplers, TextureUsage.Index, ShpkFile.IndexSamplerId); - Add(Samplers, TextureUsage.Mask, ShpkFile.MaskSamplerId); - Add(Samplers, TextureUsage.Diffuse, ShpkFile.DiffuseSamplerId); - Add(Samplers, TextureUsage.Specular, ShpkFile.SpecularSamplerId); - return; - - void Add(Dictionary dict, TextureUsage usage, uint samplerId) - { - var idx = new SamplerIndex(file, samplerId); - if (idx.Texture >= 0) - dict.Add(usage, idx); - } - } - - public readonly record struct SamplerIndex(int Sampler, int Texture) - { - public SamplerIndex(MtrlFile file, uint samplerId) - : this(file.FindSampler(samplerId), -1) - => Texture = Sampler < 0 ? -1 : file.ShaderPackage.Samplers[Sampler].TextureIndex; - } - - public enum TextureUsage - { - Unknown, - Normal, - Index, - Mask, - Diffuse, - Specular, - } - - public static bool AdaptPath(IDataManager data, string path, TextureUsage usage, out string newPath) - { - newPath = path; - if (Path.GetExtension(newPath) is not ".tex") - return false; - - if (data.FileExists(newPath)) - return true; - - ReadOnlySpan<(string, string)> pairs = usage switch - { - TextureUsage.Unknown => - [ - ("_n.tex", "_norm.tex"), - ("_m.tex", "_mult.tex"), - ("_m.tex", "_mask.tex"), - ("_d.tex", "_base.tex"), - ], - TextureUsage.Normal => - [ - ("_n_", "_norm_"), - ("_n.tex", "_norm.tex"), - ], - TextureUsage.Mask => - [ - ("_m_", "_mult_"), - ("_m_", "_mask_"), - ("_m.tex", "_mult.tex"), - ("_m.tex", "_mask.tex"), - ], - TextureUsage.Diffuse => - [ - ("_d_", "_base_"), - ("_d.tex", "_base.tex"), - ], - TextureUsage.Index => [], - TextureUsage.Specular => [], - _ => [], - }; - foreach (var (from, to) in pairs) - { - newPath = path.Replace(from, to); - if (data.FileExists(newPath)) - return true; - } - - return false; - } - } - - private void MigrateMaterial(string filePath, IReadOnlyList<(string GamePath, IModDataContainer Container)> redirections) - { - try - { - var bytes = File.ReadAllBytes(filePath); - var mtrl = new MtrlFile(bytes); - if (!CheckUpdateNeeded(mtrl)) - return; - - // Update colorsets, flags and character shader package. - var changes = mtrl.MigrateToDawntrail(); - - if (!changes) - switch (mtrl.ShaderPackage.Name) - { - case "hair.shpk": break; - case "characterglass.shpk": - SetGlassReferenceValues(mtrl); - changes = true; - break; - } - - // Remove DX11 flags and update paths if necessary. - foreach (ref var tex in mtrl.Textures.AsSpan()) - { - if (tex.DX11) - { - changes = true; - if (GamePaths.Tex.HandleDx11Path(tex, out var newPath)) - tex.Path = newPath; - tex.DX11 = false; - } - - if (gameData.FileExists(tex.Path)) - continue; - } - - // Dyeing, from TT. - if (mtrl.DyeTable is ColorDyeTable dye) - foreach (ref var row in dye.AsRows()) - row.Template += 1000; - } - catch - { - // ignored - } - - static bool CheckUpdateNeeded(MtrlFile mtrl) - { - if (!mtrl.IsDawntrail) - return true; - - if (mtrl.ShaderPackage.Name is not "hair.shpk") - return false; - - var foundOld = 0; - foreach (var c in mtrl.ShaderPackage.Constants) - { - switch (c.Id) - { - case 0x36080AD0: foundOld |= 1; break; // == 1, from TT - case 0x992869AB: foundOld |= 2; break; // == 3 (skin) or 4 (hair) from TT - } - - if (foundOld is 3) - return true; - } - - return false; - } - } - - private void MigrateFileSwaps(string swapFrom, string swapTo, IModDataContainer container) - { - var fromExists = gameData.FileExists(swapFrom); - var toExists = gameData.FileExists(swapTo); - if (fromExists && toExists) - return; - - if (ResourceTypeExtensions.FromExtension(Path.GetExtension(swapFrom.AsSpan())) is not ResourceType.Tex - || ResourceTypeExtensions.FromExtension(Path.GetExtension(swapTo.AsSpan())) is not ResourceType.Tex) - { - _messages.Add( - $"Could not migrate file swap {swapFrom} -> {swapTo} in {container.Mod.Name}: {container.GetFullName()}. Only textures may be migrated.{(fromExists ? "\n\tSource File does not exist." : "")}{(toExists ? "\n\tTarget File does not exist." : "")}"); - return; - } - - var newSwapFrom = swapFrom; - if (!fromExists && !MaterialPack.AdaptPath(gameData, swapFrom, MaterialPack.TextureUsage.Unknown, out newSwapFrom)) - { - _messages.Add($"Could not migrate file swap {swapFrom} -> {swapTo} in {container.Mod.Name}: {container.GetFullName()}."); - return; - } - - var newSwapTo = swapTo; - if (!toExists && !MaterialPack.AdaptPath(gameData, swapTo, MaterialPack.TextureUsage.Unknown, out newSwapTo)) - { - _messages.Add($"Could not migrate file swap {swapFrom} -> {swapTo} in {container.Mod.Name}: {container.GetFullName()}."); - return; - } - - if (!Utf8GamePath.FromString(swapFrom, out var path) || !Utf8GamePath.FromString(newSwapFrom, out var newPath)) - { - _messages.Add( - $"Could not migrate file swap {swapFrom} -> {swapTo} in {container.Mod.Name}: {container.GetFullName()}. Unknown Error."); - return; - } - - container.FileSwaps.Remove(path); - container.FileSwaps.Add(newPath, new FullPath(newSwapTo)); - _changedMods.Add((Mod)container.Mod); - } -} diff --git a/Penumbra/Services/PcpService.cs b/Penumbra/Services/PcpService.cs deleted file mode 100644 index 17646564..00000000 --- a/Penumbra/Services/PcpService.cs +++ /dev/null @@ -1,308 +0,0 @@ -using Dalamud.Game.ClientState.Objects.Types; -using FFXIVClientStructs.FFXIV.Client.Game.Object; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using OtterGui.Classes; -using OtterGui.Services; -using Penumbra.Collections; -using Penumbra.Collections.Manager; -using Penumbra.Communication; -using Penumbra.GameData.Actors; -using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; -using Penumbra.Interop.PathResolving; -using Penumbra.Interop.ResourceTree; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods; -using Penumbra.Mods.Groups; -using Penumbra.Mods.Manager; -using Penumbra.Mods.SubMods; -using Penumbra.String.Classes; - -namespace Penumbra.Services; - -public class PcpService : IApiService, IDisposable -{ - public const string Extension = ".pcp"; - - private readonly Configuration _config; - private readonly SaveService _files; - private readonly ResourceTreeFactory _treeFactory; - private readonly ObjectManager _objectManager; - private readonly ActorManager _actors; - private readonly FrameworkManager _framework; - private readonly CollectionResolver _collectionResolver; - private readonly CollectionManager _collections; - private readonly ModCreator _modCreator; - private readonly ModExportManager _modExport; - private readonly CommunicatorService _communicator; - private readonly SHA1 _sha1 = SHA1.Create(); - private readonly ModFileSystem _fileSystem; - private readonly ModManager _mods; - - public PcpService(Configuration config, - SaveService files, - ResourceTreeFactory treeFactory, - ObjectManager objectManager, - ActorManager actors, - FrameworkManager framework, - CollectionManager collections, - CollectionResolver collectionResolver, - ModCreator modCreator, - ModExportManager modExport, - CommunicatorService communicator, - ModFileSystem fileSystem, - ModManager mods) - { - _config = config; - _files = files; - _treeFactory = treeFactory; - _objectManager = objectManager; - _actors = actors; - _framework = framework; - _collectionResolver = collectionResolver; - _collections = collections; - _modCreator = modCreator; - _modExport = modExport; - _communicator = communicator; - _fileSystem = fileSystem; - _mods = mods; - - _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.PcpService); - } - - public void CleanPcpMods() - { - var mods = _mods.Where(m => m.ModTags.Contains("PCP")).ToList(); - Penumbra.Log.Information($"[PCPService] Deleting {mods.Count} mods containing the tag PCP."); - foreach (var mod in mods) - _mods.DeleteMod(mod); - } - - public void CleanPcpCollections() - { - var collections = _collections.Storage.Where(c => c.Identity.Name.StartsWith("PCP/")).ToList(); - Penumbra.Log.Information($"[PCPService] Deleting {collections.Count} collections starting with PCP/."); - foreach (var collection in collections) - _collections.Storage.RemoveCollection(collection); - } - - private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory) - { - if (type is not ModPathChangeType.Added || _config.PcpSettings.DisableHandling || newDirectory is null) - return; - - try - { - var file = Path.Combine(newDirectory.FullName, "character.json"); - if (!File.Exists(file)) - { - // First version had collection.json, changed. - var oldFile = Path.Combine(newDirectory.FullName, "collection.json"); - if (File.Exists(oldFile)) - { - Penumbra.Log.Information("[PCPService] Renaming old PCP file from collection.json to character.json."); - File.Move(oldFile, file, true); - } - else - return; - } - - Penumbra.Log.Information($"[PCPService] Found a PCP file for {mod.Name}, applying."); - var text = File.ReadAllText(file); - var jObj = JObject.Parse(text); - var collection = ModCollection.Empty; - // Create collection. - if (_config.PcpSettings.CreateCollection) - { - var identifier = _actors.FromJson(jObj["Actor"] as JObject); - if (identifier.IsValid && jObj["Collection"]?.ToObject() is { } collectionName) - { - var name = $"PCP/{collectionName}"; - if (_collections.Storage.AddCollection(name, null)) - { - collection = _collections.Storage[^1]; - _collections.Editor.SetModState(collection, mod, true); - - // Assign collection. - if (_config.PcpSettings.AssignCollection) - { - var identifierGroup = _collections.Active.Individuals.GetGroup(identifier); - _collections.Active.SetCollection(collection, CollectionType.Individual, identifierGroup); - } - } - } - } - - // Move to folder. - if (_fileSystem.TryGetValue(mod, out var leaf)) - { - try - { - var folder = _fileSystem.FindOrCreateAllFolders(_config.PcpSettings.FolderName); - _fileSystem.Move(leaf, folder); - } - catch - { - // ignored. - } - } - - // Invoke IPC. - if (_config.PcpSettings.AllowIpc) - _communicator.PcpParsing.Invoke(jObj, mod.Identifier, collection.Identity.Id); - } - catch (Exception ex) - { - Penumbra.Log.Error($"Error reading the character.json file from {mod.Identifier}:\n{ex}"); - } - } - - public void Dispose() - => _communicator.ModPathChanged.Unsubscribe(OnModPathChange); - - public async Task<(bool, string)> CreatePcp(ObjectIndex objectIndex, string note = "", CancellationToken cancel = default) - { - try - { - Penumbra.Log.Information($"[PCPService] Creating PCP file for game object {objectIndex.Index}."); - var (identifier, tree, meta) = await _framework.Framework.RunOnFrameworkThread(() => - { - var (actor, identifier) = CheckActor(objectIndex); - cancel.ThrowIfCancellationRequested(); - unsafe - { - var collection = _collectionResolver.IdentifyCollection((GameObject*)actor.Address, true); - if (!collection.Valid || !collection.ModCollection.HasCache) - throw new Exception($"Actor {identifier} has no mods applying, nothing to do."); - - cancel.ThrowIfCancellationRequested(); - if (_treeFactory.FromCharacter(actor, 0) is not { } tree) - throw new Exception($"Unable to fetch modded resources for {identifier}."); - - var meta = new MetaDictionary(collection.ModCollection.MetaCache, actor.Address); - return (identifier.CreatePermanent(), tree, meta); - } - }); - cancel.ThrowIfCancellationRequested(); - var time = DateTime.Now; - var modDirectory = CreateMod(identifier, note, time); - await CreateDefaultMod(modDirectory, meta, tree, cancel); - await CreateCollectionInfo(modDirectory, objectIndex, identifier, note, time, cancel); - var file = ZipUp(modDirectory); - return (true, file); - } - catch (Exception ex) - { - return (false, ex.Message); - } - } - - private static string ZipUp(DirectoryInfo directory) - { - var fileName = directory.FullName + Extension; - ZipFile.CreateFromDirectory(directory.FullName, fileName, CompressionLevel.Optimal, false); - directory.Delete(true); - return fileName; - } - - private async Task CreateCollectionInfo(DirectoryInfo directory, ObjectIndex index, ActorIdentifier actor, string note, DateTime time, - CancellationToken cancel = default) - { - var jObj = new JObject - { - ["Version"] = 1, - ["Actor"] = actor.ToJson(), - ["Mod"] = directory.Name, - ["Collection"] = note.Length > 0 ? $"{actor.ToName()}: {note}" : actor.ToName(), - ["Time"] = time, - ["Note"] = note, - }; - if (note.Length > 0) - cancel.ThrowIfCancellationRequested(); - if (_config.PcpSettings.AllowIpc) - await _framework.Framework.RunOnFrameworkThread(() => _communicator.PcpCreation.Invoke(jObj, index.Index, directory.FullName)); - var filePath = Path.Combine(directory.FullName, "character.json"); - await using var file = File.Open(filePath, File.Exists(filePath) ? FileMode.Truncate : FileMode.CreateNew); - await using var stream = new StreamWriter(file); - await using var json = new JsonTextWriter(stream); - json.Formatting = Formatting.Indented; - await jObj.WriteToAsync(json, cancel); - } - - private DirectoryInfo CreateMod(ActorIdentifier actor, string note, DateTime time) - { - var directory = _modExport.ExportDirectory; - directory.Create(); - var actorName = actor.ToName(); - var authorName = _actors.GetCurrentPlayer().ToName(); - var suffix = note.Length > 0 - ? note - : time.ToString("yyyy-MM-ddTHH\\:mm", CultureInfo.InvariantCulture); - var modName = $"{actorName} - {suffix}"; - var description = $"On-Screen Data for {actorName} as snapshotted on {time}."; - return _modCreator.CreateEmptyMod(directory, modName, description, authorName, "PCP") - ?? throw new Exception($"Unable to create mod {modName} in {directory.FullName}."); - } - - private async Task CreateDefaultMod(DirectoryInfo modDirectory, MetaDictionary meta, ResourceTree tree, - CancellationToken cancel = default) - { - var subDirectory = modDirectory.CreateSubdirectory("files"); - var subMod = new DefaultSubMod(null!) - { - Manipulations = meta, - }; - - foreach (var node in tree.FlatNodes) - { - cancel.ThrowIfCancellationRequested(); - var gamePath = node.GamePath; - var fullPath = node.FullPath; - if (fullPath.IsRooted) - { - var hash = await _sha1.ComputeHashAsync(File.OpenRead(fullPath.FullName), cancel).ConfigureAwait(false); - cancel.ThrowIfCancellationRequested(); - var name = Convert.ToHexString(hash) + fullPath.Extension; - var newFile = Path.Combine(subDirectory.FullName, name); - if (!File.Exists(newFile)) - File.Copy(fullPath.FullName, newFile); - subMod.Files.TryAdd(gamePath, new FullPath(newFile)); - } - else if (gamePath.Path != fullPath.InternalName) - { - subMod.FileSwaps.TryAdd(gamePath, fullPath); - } - } - - cancel.ThrowIfCancellationRequested(); - - var saveGroup = new ModSaveGroup(modDirectory, subMod, _config.ReplaceNonAsciiOnImport); - var filePath = _files.FileNames.OptionGroupFile(modDirectory.FullName, -1, string.Empty, _config.ReplaceNonAsciiOnImport); - cancel.ThrowIfCancellationRequested(); - await using var fileStream = File.Open(filePath, File.Exists(filePath) ? FileMode.Truncate : FileMode.CreateNew); - await using var writer = new StreamWriter(fileStream); - saveGroup.Save(writer); - } - - private (ICharacter Actor, ActorIdentifier Identifier) CheckActor(ObjectIndex objectIndex) - { - var actor = _objectManager[objectIndex]; - if (!actor.Valid) - throw new Exception($"No Actor at index {objectIndex} found."); - - if (!actor.Identifier(_actors, out var identifier)) - throw new Exception($"Could not create valid identifier for actor at index {objectIndex}."); - - if (!actor.IsCharacter) - throw new Exception($"Actor {identifier} at index {objectIndex} is not a valid character."); - - if (!actor.Model.Valid) - throw new Exception($"Actor {identifier} at index {objectIndex} has no model."); - - if (_objectManager.Objects.CreateObjectReference(actor.Address) is not ICharacter character) - throw new Exception($"Actor {identifier} at index {objectIndex} could not be converted to ICharacter"); - - return (character, identifier); - } -} diff --git a/Penumbra/Services/StainService.cs b/Penumbra/Services/StainService.cs index 17294aa8..0a437da0 100644 --- a/Penumbra/Services/StainService.cs +++ b/Penumbra/Services/StainService.cs @@ -1,7 +1,7 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; +using ImGuiNET; using OtterGui.Services; using OtterGui.Widgets; using Penumbra.GameData.DataContainers; @@ -16,7 +16,7 @@ namespace Penumbra.Services; public class StainService : IService { public sealed class StainTemplateCombo(FilterComboColors[] stainCombos, StmFile stmFile) - : FilterComboCache(stmFile.Entries.Keys.Prepend(0), MouseWheelType.None, Penumbra.Log) + : FilterComboCache(stmFile.Entries.Keys.Prepend((ushort)0), MouseWheelType.None, Penumbra.Log) where TDyePack : unmanaged, IDyePack { // FIXME There might be a better way to handle that. @@ -31,7 +31,7 @@ public class StainService : IService return baseSize + ImGui.GetTextLineHeight() * 3 + ImGui.GetStyle().ItemInnerSpacing.X * 3; } - protected override string ToString(StmKeyType obj) + protected override string ToString(ushort obj) => $"{obj,4}"; protected override void DrawFilter(int currentSelected, float width) diff --git a/Penumbra/Services/StaticServiceManager.cs b/Penumbra/Services/StaticServiceManager.cs index 27582395..c0dc9314 100644 --- a/Penumbra/Services/StaticServiceManager.cs +++ b/Penumbra/Services/StaticServiceManager.cs @@ -17,8 +17,6 @@ using IPenumbraApi = Penumbra.Api.Api.IPenumbraApi; namespace Penumbra.Services; -#pragma warning disable SeStringEvaluator - public static class StaticServiceManager { public static ServiceManager CreateProvider(Penumbra penumbra, IDalamudPluginInterface pi, Logger log) @@ -62,6 +60,5 @@ public static class StaticServiceManager .AddDalamudService(pi) .AddDalamudService(pi) .AddDalamudService(pi) - .AddDalamudService(pi) - .AddDalamudService(pi); + .AddDalamudService(pi); } diff --git a/Penumbra/UI/AdvancedWindow/FileEditor.cs b/Penumbra/UI/AdvancedWindow/FileEditor.cs index 424bc56f..c783e17f 100644 --- a/Penumbra/UI/AdvancedWindow/FileEditor.cs +++ b/Penumbra/UI/AdvancedWindow/FileEditor.cs @@ -1,14 +1,13 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Compression; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; -using Penumbra.GameData.Data; using Penumbra.GameData.Files; using Penumbra.Mods.Editor; using Penumbra.Services; @@ -81,7 +80,7 @@ public class FileEditor( private Exception? _currentException; private bool _changed; - private string _defaultPath = typeof(T) == typeof(ModEditWindow.PbdTab) ? GamePaths.Pbd.Path : string.Empty; + private string _defaultPath = string.Empty; private bool _inInput; private Utf8GamePath _defaultPathUtf8; private bool _isDefaultPathUtf8Valid; diff --git a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs index e9d76990..3f7f2f6c 100644 --- a/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs +++ b/Penumbra/UI/AdvancedWindow/ItemSwapTab.cs @@ -1,10 +1,9 @@ using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections; @@ -13,10 +12,8 @@ using Penumbra.Communication; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; -using Penumbra.Import.Structs; using Penumbra.Meta; using Penumbra.Mods; -using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; using Penumbra.Mods.ItemSwap; using Penumbra.Mods.Manager; @@ -48,20 +45,19 @@ public class ItemSwapTab : IDisposable, ITab, IUiService _config = config; _swapData = new ItemSwapContainer(metaFileManager, identifier); - var a = collectionManager.Active; _selectors = new Dictionary { // @formatter:off - [SwapType.Hat] = (new ItemSelector(a, itemService, selector, FullEquipType.Head), new ItemSelector(a, itemService, null, FullEquipType.Head), "Take this Hat", "and put it on this one" ), - [SwapType.Top] = (new ItemSelector(a, itemService, selector, FullEquipType.Body), new ItemSelector(a, itemService, null, FullEquipType.Body), "Take this Top", "and put it on this one" ), - [SwapType.Gloves] = (new ItemSelector(a, itemService, selector, FullEquipType.Hands), new ItemSelector(a, itemService, null, FullEquipType.Hands), "Take these Gloves", "and put them on these" ), - [SwapType.Pants] = (new ItemSelector(a, itemService, selector, FullEquipType.Legs), new ItemSelector(a, itemService, null, FullEquipType.Legs), "Take these Pants", "and put them on these" ), - [SwapType.Shoes] = (new ItemSelector(a, itemService, selector, FullEquipType.Feet), new ItemSelector(a, itemService, null, FullEquipType.Feet), "Take these Shoes", "and put them on these" ), - [SwapType.Earrings] = (new ItemSelector(a, itemService, selector, FullEquipType.Ears), new ItemSelector(a, itemService, null, FullEquipType.Ears), "Take these Earrings", "and put them on these" ), - [SwapType.Necklace] = (new ItemSelector(a, itemService, selector, FullEquipType.Neck), new ItemSelector(a, itemService, null, FullEquipType.Neck), "Take this Necklace", "and put it on this one" ), - [SwapType.Bracelet] = (new ItemSelector(a, itemService, selector, FullEquipType.Wrists), new ItemSelector(a, itemService, null, FullEquipType.Wrists), "Take these Bracelets", "and put them on these" ), - [SwapType.Ring] = (new ItemSelector(a, itemService, selector, FullEquipType.Finger), new ItemSelector(a, itemService, null, FullEquipType.Finger), "Take this Ring", "and put it on this one" ), - [SwapType.Glasses] = (new ItemSelector(a, itemService, selector, FullEquipType.Glasses), new ItemSelector(a, itemService, null, FullEquipType.Glasses), "Take these Glasses", "and put them on these" ), + [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 }; @@ -137,34 +133,23 @@ public class ItemSwapTab : IDisposable, ITab, IUiService Glasses, } - private class ItemSelector(ActiveCollections collections, ItemData data, ModFileSystemSelector? selector, FullEquipType type) - : FilterComboCache<(EquipItem Item, bool InMod, SingleArray InCollection)>(() => + private class ItemSelector(ItemData data, ModFileSystemSelector? selector, FullEquipType type) + : FilterComboCache<(EquipItem Item, bool InMod)>(() => { var list = data.ByType[type]; - var enumerable = selector?.Selected is { } mod && mod.ChangedItems.Values.Any(o => o is IdentifiedItem i && i.Item.Type == type) - ? list.Select(i => (i, mod.ChangedItems.ContainsKey(i.Name), collections.Current.ChangedItems.TryGetValue(i.Name, out var m) ? m.Item1 : new SingleArray())) - .OrderByDescending(p => p.Item2).ThenByDescending(p => p.Item3.Count) - : selector is null - ? list.Select(i => (i, false, collections.Current.ChangedItems.TryGetValue(i.Name, out var m) ? m.Item1 : new SingleArray())).OrderBy(p => p.Item3.Count) - : list.Select(i => (i, false, collections.Current.ChangedItems.TryGetValue(i.Name, out var m) ? m.Item1 : new SingleArray())).OrderByDescending(p => p.Item3.Count); - return enumerable.ToList(); + if (selector?.Selected is { } mod && mod.ChangedItems.Values.Any(o => o is IdentifiedItem i && i.Item.Type == type)) + return list.Select(i => (i, mod.ChangedItems.ContainsKey(i.Name))).OrderByDescending(p => p.Item2).ToList(); + + return list.Select(i => (i, false)).ToList(); }, MouseWheelType.None, Penumbra.Log) { protected override bool DrawSelectable(int globalIdx, bool selected) { - var (_, inMod, inCollection) = Items[globalIdx]; - using var color = inMod - ? ImRaii.PushColor(ImGuiCol.Text, ColorId.ResTreeLocalPlayer.Value()) - : inCollection.Count > 0 - ? ImRaii.PushColor(ImGuiCol.Text, ColorId.ResTreeNonNetworked.Value()) - : null; - var ret = base.DrawSelectable(globalIdx, selected); - if (inCollection.Count > 0) - ImUtf8.HoverTooltip(string.Join('\n', inCollection.Select(m => m.Name.Text))); - return ret; + using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ResTreeLocalPlayer.Value(), Items[globalIdx].InMod); + return base.DrawSelectable(globalIdx, selected); } - protected override string ToString((EquipItem Item, bool InMod, SingleArray InCollection) obj) + protected override string ToString((EquipItem Item, bool InMod) obj) => obj.Item.Name; } @@ -194,7 +179,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService private bool _useLeftRing = true; private bool _useRightRing = true; - private HashSet? _affectedItems; + private EquipItem[]? _affectedItems; private void UpdateState() { @@ -290,38 +275,16 @@ public class ItemSwapTab : IDisposable, ITab, IUiService case SwapType.Hair: case SwapType.Tail: return - $"Created by swapping {_lastTab} {_sourceId} onto {_lastTab} {_targetId} for {_currentRace.ToName()} {_currentGender.ToName()}s in {_mod!.Name}{OriginalAuthor()}"; + $"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}{OriginalAuthor()}"; + $"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}{OriginalAuthor()}"; + $"Created by swapping {_selectors[_lastTab].Source.CurrentSelection.Item.Name} onto {_selectors[_lastTab].Target.CurrentSelection.Item.Name} in {_mod!.Name}."; } } - private string OriginalAuthor() - { - if (_mod!.Author.IsEmpty || _mod!.Author.Text is "TexTools User" or DefaultTexToolsData.Author) - return "."; - - return $" by {_mod!.Author}."; - } - - private string CreateAuthor() - { - if (_mod!.Author.IsEmpty) - return _config.DefaultModAuthor; - if (_mod!.Author.Text == _config.DefaultModAuthor) - return _config.DefaultModAuthor; - if (_mod!.Author.Text is "TexTools User" or DefaultTexToolsData.Author) - return _config.DefaultModAuthor; - if (_config.DefaultModAuthor is DefaultTexToolsData.Author) - return _mod!.Author; - - return $"{_mod!.Author} (Swap by {_config.DefaultModAuthor})"; - } - private void UpdateOption() { _selectedGroup = _mod?.Groups.FirstOrDefault(g => g.Name == _newGroupName); @@ -333,7 +296,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService private void CreateMod() { - var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName, CreateDescription(), CreateAuthor()); + var newDir = _modManager.Creator.CreateEmptyMod(_modManager.BasePath, _newModName, CreateDescription()); if (newDir == null) return; @@ -555,11 +518,11 @@ public class ItemSwapTab : IDisposable, ITab, IUiService _dirty |= selector.Draw("##itemTarget", selector.CurrentSelection.Item.Name, string.Empty, InputWidth * 2 * UiHelpers.Scale, ImGui.GetTextLineHeightWithSpacing()); - if (_affectedItems is not { Count: > 1 }) + if (_affectedItems is not { Length: > 1 }) return; ImGui.SameLine(); - ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Count - 1} other Items.", Vector2.Zero, + ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, Colors.PressEnterWarningBg); if (ImGui.IsItemHovered()) ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, selector.CurrentSelection.Item.Name)) @@ -616,11 +579,11 @@ public class ItemSwapTab : IDisposable, ITab, IUiService _dirty |= ImGui.Checkbox("Swap Left Ring", ref _useLeftRing); } - if (_affectedItems is not { Count: > 1 }) + if (_affectedItems is not { Length: > 1 }) return; ImGui.SameLine(); - ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Count - 1} other Items.", Vector2.Zero, + ImGuiUtil.DrawTextButton($"which will also affect {_affectedItems.Length - 1} other Items.", Vector2.Zero, Colors.PressEnterWarningBg); if (ImGui.IsItemHovered()) ImGui.SetTooltip(string.Join('\n', _affectedItems.Where(i => !ReferenceEquals(i.Name, targetSelector.CurrentSelection.Item.Name)) @@ -774,12 +737,12 @@ public class ItemSwapTab : IDisposable, ITab, IUiService if (collectionType is not CollectionType.Current || _mod == null || newCollection == null) return; - UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection.GetInheritedSettings(_mod.Index).Settings : null); + UpdateMod(_mod, _mod.Index < newCollection.Settings.Count ? newCollection[_mod.Index].Settings : null); } private void OnSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting oldValue, int groupIdx, bool inherited) { - if (collection != _collectionManager.Active.Current || mod != _mod || type is ModSettingChange.TemporarySetting) + if (collection != _collectionManager.Active.Current || mod != _mod) return; _swapData.LoadMod(_mod, _modSettings); @@ -791,7 +754,7 @@ public class ItemSwapTab : IDisposable, ITab, IUiService if (collection != _collectionManager.Active.Current || _mod == null) return; - UpdateMod(_mod, collection.GetInheritedSettings(_mod.Index).Settings); + UpdateMod(_mod, collection[_mod.Index].Settings); _swapData.LoadMod(_mod, _modSettings); _dirty = true; } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs b/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs index 24a5f9c2..5c636b1d 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MaterialTemplatePickers.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using FFXIVClientStructs.Interop; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; @@ -131,7 +131,7 @@ public sealed unsafe class MaterialTemplatePickers : IUiService if (texture == null) continue; var handle = _textureArraySlicer.GetImGuiHandle(texture, sliceIndex); - if (handle.IsNull) + if (handle == 0) continue; var position = regionStart with { X = regionStart.X + (itemSize.X + itemSpacing) * j }; diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs index fad9adeb..ab93dc5f 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ColorTable.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; @@ -603,11 +603,10 @@ public partial class MtrlTab value => dyeTable[rowIdx].Channel = (byte)(Math.Clamp(value, 1, StainService.ChannelCount) - 1)); ImGui.SameLine(subColWidth); ImGui.SetNextItemWidth(scalarSize); - _stainService.GudTemplateCombo.CurrentDyeChannel = dye.Channel; if (_stainService.GudTemplateCombo.Draw("##dyeTemplate", dye.Template.ToString(), string.Empty, scalarSize + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton)) { - dye.Template = _stainService.GudTemplateCombo.CurrentSelection.UShort; + dye.Template = _stainService.GudTemplateCombo.CurrentSelection; ret = true; } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs index 9ea9c2e0..38f02100 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.CommonColorTable.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; +using ImGuiNET; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.GameData.Files; using OtterGui.Text; @@ -22,7 +22,7 @@ public partial class MtrlTab private bool DrawColorTableSection(bool disabled) { - if (!_shpkLoading && !TextureIds.Contains(ShpkFile.TableSamplerId) || Mtrl.Table == null) + if (!_shpkLoading && !SamplerIds.Contains(ShpkFile.TableSamplerId) || Mtrl.Table == null) return false; ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); @@ -202,74 +202,24 @@ public partial class MtrlTab if (Mtrl.Table == null) return false; - if (ImUtf8.IconButton(FontAwesomeIcon.Paste, - "Import an exported row from your clipboard onto this row.\n\nRight-Click for more options."u8, + if (!ImUtf8.IconButton(FontAwesomeIcon.Paste, "Import an exported row from your clipboard onto this row."u8, ImGui.GetFrameHeight() * Vector2.One, disabled)) - try - { - var text = ImGui.GetClipboardText(); - var data = Convert.FromBase64String(text); - var row = Mtrl.Table.RowAsBytes(rowIdx); - var dyeRow = Mtrl.DyeTable != null ? Mtrl.DyeTable.RowAsBytes(rowIdx) : []; - if (data.Length != row.Length && data.Length != row.Length + dyeRow.Length) - return false; - - data.AsSpan(0, row.Length).TryCopyTo(row); - data.AsSpan(row.Length).TryCopyTo(dyeRow); - - UpdateColorTableRowPreview(rowIdx); - - return true; - } - catch - { - return false; - } - - return ColorTablePasteFromClipboardContext(rowIdx, disabled); - } - - private unsafe bool ColorTablePasteFromClipboardContext(int rowIdx, bool disabled) - { - if (!disabled && ImGui.IsItemClicked(ImGuiMouseButton.Right)) - ImUtf8.OpenPopup("context"u8); - - using var context = ImUtf8.Popup("context"u8); - if (!context) - return false; - - using var _ = ImRaii.Disabled(disabled); - - IColorTable.ValueTypes copy = 0; - IColorDyeTable.ValueTypes dyeCopy = 0; - if (ImUtf8.Selectable("Import Colors Only"u8)) - { - copy = IColorTable.ValueTypes.Colors; - dyeCopy = IColorDyeTable.ValueTypes.Colors; - } - - if (ImUtf8.Selectable("Import Other Values Only"u8)) - { - copy = ~IColorTable.ValueTypes.Colors; - dyeCopy = ~IColorDyeTable.ValueTypes.Colors; - } - - if (copy == 0) return false; try { var text = ImGui.GetClipboardText(); var data = Convert.FromBase64String(text); - var row = Mtrl.Table!.RowAsHalves(rowIdx); - var halves = new Span(Unsafe.AsPointer(ref data[0]), row.Length); + var row = Mtrl.Table.RowAsBytes(rowIdx); var dyeRow = Mtrl.DyeTable != null ? Mtrl.DyeTable.RowAsBytes(rowIdx) : []; - if (!Mtrl.Table.MergeSpecificValues(row, halves, copy)) + if (data.Length != row.Length && data.Length != row.Length + dyeRow.Length) return false; - Mtrl.DyeTable?.MergeSpecificValues(dyeRow, data.AsSpan(row.Length * 2), dyeCopy); + data.AsSpan(0, row.Length).TryCopyTo(row); + data.AsSpan(row.Length).TryCopyTo(dyeRow); UpdateColorTableRowPreview(rowIdx); + return true; } catch @@ -338,10 +288,10 @@ public partial class MtrlTab var tmp = inputSqrt; if (ImUtf8.ColorEdit(label, ref tmp, ImGuiColorEditFlags.NoInputs - | ImGuiColorEditFlags.DisplayRgb - | ImGuiColorEditFlags.InputRgb + | ImGuiColorEditFlags.DisplayRGB + | ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.NoTooltip - | ImGuiColorEditFlags.Hdr) + | ImGuiColorEditFlags.HDR) && tmp != inputSqrt) { setter((HalfColor)PseudoSquareRgb(tmp)); @@ -373,10 +323,10 @@ public partial class MtrlTab var tmp = Vector4.Zero; ImUtf8.ColorEdit(label, ref tmp, ImGuiColorEditFlags.NoInputs - | ImGuiColorEditFlags.DisplayRgb - | ImGuiColorEditFlags.InputRgb + | ImGuiColorEditFlags.DisplayRGB + | ImGuiColorEditFlags.InputRGB | ImGuiColorEditFlags.NoTooltip - | ImGuiColorEditFlags.Hdr + | ImGuiColorEditFlags.HDR | ImGuiColorEditFlags.AlphaPreview); if (letter.Length > 0 && ImGui.IsItemVisible()) @@ -594,7 +544,7 @@ public partial class MtrlTab internal static float PseudoSqrtRgb(float x) => x < 0.0f ? -MathF.Sqrt(-x) : MathF.Sqrt(x); - public static Vector3 PseudoSqrtRgb(Vector3 vec) + internal static Vector3 PseudoSqrtRgb(Vector3 vec) => new(PseudoSqrtRgb(vec.X), PseudoSqrtRgb(vec.Y), PseudoSqrtRgb(vec.Z)); internal static Vector4 PseudoSqrtRgb(Vector4 vec) diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Constants.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Constants.cs index 4ad6968b..176ec3f4 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Constants.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Constants.cs @@ -1,8 +1,7 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget.Editors; diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs index e75cd633..f21d86a9 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LegacyColorTable.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Text; using Penumbra.GameData.Files.MaterialStructs; @@ -67,7 +67,7 @@ public partial class MtrlTab private static void DrawLegacyColorTableHeader(bool hasDyeTable) { ImGui.TableNextColumn(); - ImUtf8.TableHeader(""u8); + ImUtf8.TableHeader(default(ReadOnlySpan)); ImGui.TableNextColumn(); ImUtf8.TableHeader("Row"u8); ImGui.TableNextColumn(); @@ -184,7 +184,7 @@ public partial class MtrlTab if (_stainService.LegacyTemplateCombo.Draw("##dyeTemplate", dye.Template.ToString(), string.Empty, intSize + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton)) { - dyeTable[rowIdx].Template = _stainService.LegacyTemplateCombo.CurrentSelection.UShort; + dyeTable[rowIdx].Template = _stainService.LegacyTemplateCombo.CurrentSelection; ret = true; } @@ -299,7 +299,7 @@ public partial class MtrlTab if (_stainService.LegacyTemplateCombo.Draw("##dyeTemplate", dye.Template.ToString(), string.Empty, intSize + ImGui.GetStyle().ScrollbarSize / 2, ImGui.GetTextLineHeightWithSpacing(), ImGuiComboFlags.NoArrowButton)) { - dyeTable[rowIdx].Template = _stainService.LegacyTemplateCombo.CurrentSelection.UShort; + dyeTable[rowIdx].Template = _stainService.LegacyTemplateCombo.CurrentSelection; ret = true; } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs index dfa3a963..01a40980 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.LivePreview.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData.Files.MaterialStructs; @@ -138,7 +138,7 @@ public partial class MtrlTab foreach (var constant in Mtrl.ShaderPackage.Constants) { var values = Mtrl.GetConstantValue(constant); - if (values != []) + if (values != null) SetMaterialParameter(constant.Id, 0, values); } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ShaderPackage.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ShaderPackage.cs index 43040ca3..ae57a122 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ShaderPackage.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.ShaderPackage.cs @@ -1,17 +1,15 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using Penumbra.GameData; using Penumbra.GameData.Data; using Penumbra.GameData.Files; using Penumbra.GameData.Files.ShaderStructs; -using Penumbra.Interop.Processing; using Penumbra.String.Classes; using static Penumbra.GameData.Files.ShpkFile; @@ -126,15 +124,11 @@ public partial class MtrlTab private FullPath FindAssociatedShpk(out string defaultPath, out Utf8GamePath defaultGamePath) { - defaultPath = GamePaths.Shader(Mtrl.ShaderPackage.Name); + defaultPath = GamePaths.Shader.ShpkPath(Mtrl.ShaderPackage.Name); if (!Utf8GamePath.FromString(defaultPath, out defaultGamePath)) return FullPath.Empty; - var path = _edit.FindBestMatch(defaultGamePath); - if (!path.IsRooted || ShpkPathPreProcessor.SanityCheck(path.FullName) == ShpkPathPreProcessor.SanityCheckResult.Success) - return path; - - return new FullPath(defaultPath); + return _edit.FindBestMatch(defaultGamePath); } private void LoadShpk(FullPath path) @@ -222,7 +216,7 @@ public partial class MtrlTab else foreach (var (key, index) in Mtrl.ShaderPackage.ShaderKeys.WithIndex()) { - var keyName = Names.KnownNames.TryResolve(key.Key); + var keyName = Names.KnownNames.TryResolve(key.Category); var valueName = keyName.WithKnownSuffixes().TryResolve(Names.KnownNames, key.Value); _shaderKeys.Add((keyName.ToString(), index, string.Empty, true, [(valueName.ToString(), key.Value, string.Empty)])); } @@ -372,7 +366,6 @@ public partial class MtrlTab ret = true; _associatedShpk = null; _loadedShpkPath = FullPath.Empty; - UnpinResources(true); LoadShpk(FindAssociatedShpk(out _, out _)); } @@ -384,7 +377,7 @@ public partial class MtrlTab var shpkFlags = (int)Mtrl.ShaderPackage.Flags; ImGui.SetNextItemWidth(UiHelpers.Scale * 250.0f); if (!ImGui.InputInt("Shader Flags", ref shpkFlags, 0, 0, - flags: ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))) + ImGuiInputTextFlags.CharsHexadecimal | (disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None))) return false; Mtrl.ShaderPackage.Flags = (uint)shpkFlags; @@ -449,8 +442,8 @@ public partial class MtrlTab { using var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont); ref var key = ref Mtrl.ShaderPackage.ShaderKeys[index]; - using var id = ImUtf8.PushId((int)key.Key); - var shpkKey = _associatedShpk?.GetMaterialKeyById(key.Key); + using var id = ImUtf8.PushId((int)key.Category); + var shpkKey = _associatedShpk?.GetMaterialKeyById(key.Category); var currentValue = key.Value; var (currentLabel, _, currentDescription) = values.FirstOrNull(v => v.Value == currentValue) ?? ($"0x{currentValue:X8}", currentValue, string.Empty); @@ -466,7 +459,6 @@ public partial class MtrlTab { key.Value = value; ret = true; - UnpinResources(false); Update(); } diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Textures.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Textures.cs index 82ba7be4..7ab2900d 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.Textures.cs @@ -1,9 +1,9 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; +using Penumbra.GameData; using Penumbra.GameData.Files.MaterialStructs; using Penumbra.String.Classes; using static Penumbra.GameData.Files.MaterialStructs.SamplerFlags; @@ -16,22 +16,18 @@ public partial class MtrlTab public readonly List<(string Label, int TextureIndex, int SamplerIndex, string Description, bool MonoFont)> Textures = new(4); public readonly HashSet UnfoldedTextures = new(4); - public readonly HashSet TextureIds = new(16); public readonly HashSet SamplerIds = new(16); public float TextureLabelWidth; - private bool _samplersPinned; private void UpdateTextures() { Textures.Clear(); - TextureIds.Clear(); SamplerIds.Clear(); if (_associatedShpk == null) { - TextureIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); if (Mtrl.Table != null) - TextureIds.Add(TableSamplerId); + SamplerIds.Add(TableSamplerId); foreach (var (sampler, index) in Mtrl.ShaderPackage.Samplers.WithIndex()) Textures.Add(($"0x{sampler.SamplerId:X8}", sampler.TextureIndex, index, string.Empty, true)); @@ -39,39 +35,31 @@ public partial class MtrlTab else { foreach (var index in _vertexShaders) - { - TextureIds.UnionWith(_associatedShpk.VertexShaders[index].Textures.Select(texture => texture.Id)); SamplerIds.UnionWith(_associatedShpk.VertexShaders[index].Samplers.Select(sampler => sampler.Id)); - } - foreach (var index in _pixelShaders) - { - TextureIds.UnionWith(_associatedShpk.PixelShaders[index].Textures.Select(texture => texture.Id)); SamplerIds.UnionWith(_associatedShpk.PixelShaders[index].Samplers.Select(sampler => sampler.Id)); - } - - if (_samplersPinned || !_shadersKnown) + if (!_shadersKnown) { - TextureIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); + SamplerIds.UnionWith(Mtrl.ShaderPackage.Samplers.Select(sampler => sampler.SamplerId)); if (Mtrl.Table != null) - TextureIds.Add(TableSamplerId); + SamplerIds.Add(TableSamplerId); } - foreach (var textureId in TextureIds) + foreach (var samplerId in SamplerIds) { - var shpkTexture = _associatedShpk.GetTextureById(textureId); - if (shpkTexture is not { Slot: 2 } && (shpkTexture is not null || textureId == TableSamplerId)) + var shpkSampler = _associatedShpk.GetSamplerById(samplerId); + if (shpkSampler is not { Slot: 2 }) continue; - var dkData = TryGetShpkDevkitData("Samplers", textureId, true); + var dkData = TryGetShpkDevkitData("Samplers", samplerId, true); var hasDkLabel = !string.IsNullOrEmpty(dkData?.Label); - var sampler = Mtrl.GetOrAddSampler(textureId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex); - Textures.Add((hasDkLabel ? dkData!.Label : shpkTexture!.Value.Name, sampler.TextureIndex, samplerIndex, + var sampler = Mtrl.GetOrAddSampler(samplerId, dkData?.DefaultTexture ?? string.Empty, out var samplerIndex); + Textures.Add((hasDkLabel ? dkData!.Label : shpkSampler.Value.Name, sampler.TextureIndex, samplerIndex, dkData?.Description ?? string.Empty, !hasDkLabel)); } - if (TextureIds.Contains(TableSamplerId)) + if (SamplerIds.Contains(TableSamplerId)) Mtrl.Table ??= new ColorTable(); } @@ -162,13 +150,13 @@ public partial class MtrlTab } ImGui.TableNextColumn(); - using (ImRaii.PushFont(UiBuilder.MonoFont, monoFont)) - { - ImGui.AlignTextToFramePadding(); - if (description.Length > 0) - ImGuiUtil.LabeledHelpMarker(label, description); - else - ImGui.TextUnformatted(label); + using (var font = ImRaii.PushFont(UiBuilder.MonoFont, monoFont)) + { + ImGui.AlignTextToFramePadding(); + if (description.Length > 0) + ImGuiUtil.LabeledHelpMarker(label, description); + else + ImGui.TextUnformatted(label); } if (unfolded) @@ -217,67 +205,58 @@ public partial class MtrlTab ret = true; } - if (SamplerIds.Contains(sampler.SamplerId)) + ref var samplerFlags = ref Wrap(ref sampler.Flags); + + ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); + var addressMode = samplerFlags.UAddressMode; + if (ComboTextureAddressMode("##UAddressMode"u8, ref addressMode)) { - ref var samplerFlags = ref Wrap(ref sampler.Flags); - - ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); - var addressMode = samplerFlags.UAddressMode; - if (ComboTextureAddressMode("##UAddressMode"u8, ref addressMode)) - { - samplerFlags.UAddressMode = addressMode; - ret = true; - SetSamplerFlags(sampler.SamplerId, sampler.Flags); - } - - ImGui.SameLine(); - ImUtf8.LabeledHelpMarker("U Address Mode"u8, - "Method to use for resolving a U texture coordinate that is outside the 0 to 1 range."); - - ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); - addressMode = samplerFlags.VAddressMode; - if (ComboTextureAddressMode("##VAddressMode"u8, ref addressMode)) - { - samplerFlags.VAddressMode = addressMode; - ret = true; - SetSamplerFlags(sampler.SamplerId, sampler.Flags); - } - - ImGui.SameLine(); - ImUtf8.LabeledHelpMarker("V Address Mode"u8, - "Method to use for resolving a V texture coordinate that is outside the 0 to 1 range."); - - var lodBias = samplerFlags.LodBias; - ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); - if (ImUtf8.DragScalar("##LoDBias"u8, ref lodBias, -8.0f, 7.984375f, 0.1f)) - { - samplerFlags.LodBias = lodBias; - ret = true; - SetSamplerFlags(sampler.SamplerId, sampler.Flags); - } - - ImGui.SameLine(); - ImUtf8.LabeledHelpMarker("Level of Detail Bias"u8, - "Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther."); - - var minLod = samplerFlags.MinLod; - ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); - if (ImUtf8.DragScalar("##MinLoD"u8, ref minLod, 0, 15, 0.1f)) - { - samplerFlags.MinLod = minLod; - ret = true; - SetSamplerFlags(sampler.SamplerId, sampler.Flags); - } - - ImGui.SameLine(); - ImUtf8.LabeledHelpMarker("Minimum Level of Detail"u8, - "Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap."); + samplerFlags.UAddressMode = addressMode; + ret = true; + SetSamplerFlags(sampler.SamplerId, sampler.Flags); } - else + + ImGui.SameLine(); + ImUtf8.LabeledHelpMarker("U Address Mode"u8, "Method to use for resolving a U texture coordinate that is outside the 0 to 1 range."); + + ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); + addressMode = samplerFlags.VAddressMode; + if (ComboTextureAddressMode("##VAddressMode"u8, ref addressMode)) { - ImUtf8.Text("This texture does not have a dedicated sampler."u8); + samplerFlags.VAddressMode = addressMode; + ret = true; + SetSamplerFlags(sampler.SamplerId, sampler.Flags); } + ImGui.SameLine(); + ImUtf8.LabeledHelpMarker("V Address Mode"u8, "Method to use for resolving a V texture coordinate that is outside the 0 to 1 range."); + + var lodBias = samplerFlags.LodBias; + ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); + if (ImUtf8.DragScalar("##LoDBias"u8, ref lodBias, -8.0f, 7.984375f, 0.1f)) + { + samplerFlags.LodBias = lodBias; + ret = true; + SetSamplerFlags(sampler.SamplerId, sampler.Flags); + } + + ImGui.SameLine(); + ImUtf8.LabeledHelpMarker("Level of Detail Bias"u8, + "Offset from the calculated mipmap level.\n\nHigher means that the texture will start to lose detail nearer.\nLower means that the texture will keep its detail until farther."); + + var minLod = samplerFlags.MinLod; + ImGui.SetNextItemWidth(UiHelpers.Scale * 100.0f); + if (ImUtf8.DragScalar("##MinLoD"u8, ref minLod, 0, 15, 0.1f)) + { + samplerFlags.MinLod = minLod; + ret = true; + SetSamplerFlags(sampler.SamplerId, sampler.Flags); + } + + ImGui.SameLine(); + ImUtf8.LabeledHelpMarker("Minimum Level of Detail"u8, + "Most detailed mipmap level to use.\n\n0 is the full-sized texture, 1 is the half-sized texture, 2 is the quarter-sized texture, and so on.\n15 will forcibly reduce the texture to its smallest mipmap."); + using var t = ImUtf8.TreeNode("Advanced Settings"u8); if (!t) return ret; diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs index 2c7c889e..6e16de99 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs @@ -1,6 +1,5 @@ -using Dalamud.Interface.Components; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; @@ -58,7 +57,6 @@ public sealed partial class MtrlTab : IWritable, IDisposable Mtrl = file; FilePath = filePath; Writable = writable; - _samplersPinned = true; _associatedBaseDevkit = TryLoadShpkDevkit("_base", out _loadedBaseDevkitPathName); Update(); LoadShpk(FindAssociatedShpk(out _, out _)); @@ -120,22 +118,11 @@ public sealed partial class MtrlTab : IWritable, IDisposable using var dis = ImRaii.Disabled(disabled); var tmp = shaderFlags.EnableTransparency; - - // guardrail: the game crashes if transparency is enabled on characterstockings.shpk - var disallowTransparency = Mtrl.ShaderPackage.Name == "characterstockings.shpk"; - using (ImRaii.Disabled(disallowTransparency)) + if (ImUtf8.Checkbox("Enable Transparency"u8, ref tmp)) { - if (ImUtf8.Checkbox("Enable Transparency"u8, ref tmp)) - { - shaderFlags.EnableTransparency = tmp; - ret = true; - SetShaderPackageFlags(Mtrl.ShaderPackage.Flags); - } - } - - if (disallowTransparency) - { - ImGuiComponents.HelpMarker("Enabling transparency for shader package characterstockings.shpk will crash the game."); + shaderFlags.EnableTransparency = tmp; + ret = true; + SetShaderPackageFlags(Mtrl.ShaderPackage.Flags); } ImGui.SameLine(200 * UiHelpers.Scale + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X); @@ -185,22 +172,6 @@ public sealed partial class MtrlTab : IWritable, IDisposable Widget.DrawHexViewer(Mtrl.AdditionalData); } - private void UnpinResources(bool all) - { - _samplersPinned = false; - - if (!all) - return; - - var keys = Mtrl.ShaderPackage.ShaderKeys; - for (var i = 0; i < keys.Length; i++) - keys[i].Pinned = false; - - var constants = Mtrl.ShaderPackage.Constants; - for (var i = 0; i < constants.Length; i++) - constants[i].Pinned = false; - } - private void Update() { UpdateShaders(); @@ -216,12 +187,12 @@ public sealed partial class MtrlTab : IWritable, IDisposable } public bool Valid - => Mtrl.Valid; // FIXME This should be _shadersKnown && Mtrl.Valid but the algorithm for _shadersKnown is flawed as of 7.2. + => _shadersKnown && Mtrl.Valid; public byte[] Write() { var output = Mtrl.Clone(); - output.GarbageCollect(_associatedShpk, TextureIds); + output.GarbageCollect(_associatedShpk, SamplerIds); return output.Write(); } diff --git a/Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs index 4a74cda5..4cf01faa 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/AtchMetaDrawer.cs @@ -1,14 +1,11 @@ using Dalamud.Interface; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Collections.Cache; -using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; using Penumbra.GameData.Files.AtchStructs; @@ -16,7 +13,6 @@ using Penumbra.Meta; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.UI.Classes; -using Notification = OtterGui.Classes.Notification; namespace Penumbra.UI.AdvancedWindow.Meta; @@ -31,10 +27,9 @@ public sealed class AtchMetaDrawer : MetaDrawer, ISer public override float ColumnHeight => 2 * ImUtf8.FrameHeightSpacing; - private AtchFile? _currentBaseAtchFile; - private AtchPoint? _currentBaseAtchPoint; - private readonly AtchPointCombo _combo; - private string _fileImport = string.Empty; + private AtchFile? _currentBaseAtchFile; + private AtchPoint? _currentBaseAtchPoint; + private AtchPointCombo _combo; public AtchMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) : base(editor, metaFiles) @@ -49,50 +44,6 @@ public sealed class AtchMetaDrawer : MetaDrawer, ISer => obj.ToName(); } - private sealed class RaceCodeException(string filePath) : Exception($"Could not identify race code from path {filePath}."); - - public void ImportFile(string filePath) - { - try - { - if (filePath.Length == 0 || !File.Exists(filePath)) - throw new FileNotFoundException(); - - var gr = Parser.ParseRaceCode(filePath); - if (gr is GenderRace.Unknown) - throw new RaceCodeException(filePath); - - var text = File.ReadAllBytes(filePath); - var file = new AtchFile(text); - foreach (var point in file.Points) - { - foreach (var (entry, index) in point.Entries.WithIndex()) - { - var identifier = new AtchIdentifier(point.Type, gr, (ushort)index); - var defaultValue = AtchCache.GetDefault(MetaFiles, identifier); - if (defaultValue == null) - continue; - - if (defaultValue.Value.Equals(entry)) - Editor.Changes |= Editor.Remove(identifier); - else - Editor.Changes |= Editor.TryAdd(identifier, entry) || Editor.Update(identifier, entry); - } - } - } - catch (RaceCodeException ex) - { - Penumbra.Messager.AddMessage(new Notification(ex, "The imported .atch file does not contain a race code (cXXXX) in its name.", - "Could not import .atch file:", - NotificationType.Warning)); - } - catch (Exception ex) - { - Penumbra.Messager.AddMessage(new Notification(ex, "Unable to import .atch file.", "Could not import .atch file:", - NotificationType.Warning)); - } - } - protected override void DrawNew() { @@ -167,12 +118,12 @@ public sealed class AtchMetaDrawer : MetaDrawer, ISer private void UpdateFile() { - _currentBaseAtchFile = MetaFiles.AtchManager.AtchFileBase[Identifier.GenderRace]; + _currentBaseAtchFile = MetaFiles.AtchManager.AtchFileBase[Identifier.GenderRace]; _currentBaseAtchPoint = _currentBaseAtchFile.GetPoint(Identifier.Type); if (_currentBaseAtchPoint == null) { _currentBaseAtchPoint = _currentBaseAtchFile.Points.First(); - Identifier = Identifier with { Type = _currentBaseAtchPoint.Type }; + Identifier = Identifier with { Type = _currentBaseAtchPoint.Type }; } if (Identifier.EntryIndex >= _currentBaseAtchPoint.Entries.Length) @@ -287,7 +238,7 @@ public sealed class AtchMetaDrawer : MetaDrawer, ISer if (!ret) return false; - index = Math.Clamp(index, (ushort)0, (ushort)(currentAtchPoint.Entries.Length - 1)); + index = Math.Clamp(index, (ushort)0, (ushort)(currentAtchPoint!.Entries.Length - 1)); identifier = identifier with { EntryIndex = index }; return true; } diff --git a/Penumbra/UI/AdvancedWindow/Meta/AtrMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/AtrMetaDrawer.cs deleted file mode 100644 index 4b375c26..00000000 --- a/Penumbra/UI/AdvancedWindow/Meta/AtrMetaDrawer.cs +++ /dev/null @@ -1,274 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using Newtonsoft.Json.Linq; -using OtterGui.Raii; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Editor; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.AdvancedWindow.Meta; - -public sealed class AtrMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) - : MetaDrawer(editor, metaFiles), IService -{ - public override ReadOnlySpan Label - => "Attributes(ATR)###ATR"u8; - - private ShapeAttributeString _buffer = ShapeAttributeString.TryRead("atrx_"u8, out var s) ? s : ShapeAttributeString.Empty; - private bool _identifierValid; - - public override int NumColumns - => 7; - - public override float ColumnHeight - => ImUtf8.FrameHeightSpacing; - - protected override void Initialize() - { - Identifier = new AtrIdentifier(HumanSlot.Unknown, null, ShapeAttributeString.Empty, GenderRace.Unknown); - Entry = AtrEntry.True; - } - - protected override void DrawNew() - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current ATR manipulations to clipboard."u8, - new Lazy(() => MetaDictionary.SerializeTo([], Editor.Atr))); - - ImGui.TableNextColumn(); - var canAdd = !Editor.Contains(Identifier) && _identifierValid; - var tt = canAdd - ? "Stage this edit."u8 - : _identifierValid - ? "This entry does not contain a valid attribute."u8 - : "This entry is already edited."u8; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) - Editor.Changes |= Editor.TryAdd(Identifier, AtrEntry.False); - - DrawIdentifierInput(ref Identifier); - DrawEntry(ref Entry, true); - } - - protected override void DrawEntry(AtrIdentifier identifier, AtrEntry entry) - { - DrawMetaButtons(identifier, entry); - DrawIdentifier(identifier); - - if (DrawEntry(ref entry, false)) - Editor.Changes |= Editor.Update(identifier, entry); - } - - protected override IEnumerable<(AtrIdentifier, AtrEntry)> Enumerate() - => Editor.Atr - .OrderBy(kvp => kvp.Key.Attribute) - .ThenBy(kvp => kvp.Key.Slot) - .ThenBy(kvp => kvp.Key.Id) - .Select(kvp => (kvp.Key, kvp.Value)); - - protected override int Count - => Editor.Atr.Count; - - private bool DrawIdentifierInput(ref AtrIdentifier identifier) - { - ImGui.TableNextColumn(); - var changes = DrawHumanSlot(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawGenderRaceConditionInput(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawPrimaryId(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawAttributeKeyInput(ref identifier, ref _buffer, ref _identifierValid); - return changes; - } - - private static void DrawIdentifier(AtrIdentifier identifier) - { - ImGui.TableNextColumn(); - - ImUtf8.TextFramed(ShpMetaDrawer.SlotName(identifier.Slot), FrameColor); - ImUtf8.HoverTooltip("Model Slot"u8); - - ImGui.TableNextColumn(); - if (identifier.GenderRaceCondition is not GenderRace.Unknown) - { - ImUtf8.TextFramed($"{identifier.GenderRaceCondition.ToName()} ({identifier.GenderRaceCondition.ToRaceCode()})", FrameColor); - ImUtf8.HoverTooltip("Gender & Race Code for this attribute to be set."); - } - else - { - ImUtf8.TextFramed("Any Gender & Race"u8, FrameColor); - } - - ImGui.TableNextColumn(); - if (identifier.Id.HasValue) - ImUtf8.TextFramed($"{identifier.Id.Value.Id}", FrameColor); - else - ImUtf8.TextFramed("All IDs"u8, FrameColor); - ImUtf8.HoverTooltip("Primary ID"u8); - - ImGui.TableNextColumn(); - ImUtf8.TextFramed(identifier.Attribute.AsSpan, FrameColor); - } - - private static bool DrawEntry(ref AtrEntry entry, bool disabled) - { - using var dis = ImRaii.Disabled(disabled); - ImGui.TableNextColumn(); - var value = entry.Value; - var changes = ImUtf8.Checkbox("##atrEntry"u8, ref value); - if (changes) - entry = new AtrEntry(value); - ImUtf8.HoverTooltip("Whether to enable or disable this attribute for the selected items."); - return changes; - } - - public static bool DrawPrimaryId(ref AtrIdentifier identifier, float unscaledWidth = 100) - { - var allSlots = identifier.Slot is HumanSlot.Unknown; - var all = !identifier.Id.HasValue; - var ret = false; - using (ImRaii.Disabled(allSlots)) - { - if (ImUtf8.Checkbox("##atrAll"u8, ref all)) - { - identifier = identifier with { Id = all ? null : 0 }; - ret = true; - } - } - - ImUtf8.HoverTooltip(allSlots - ? "When using all slots, you also need to use all IDs."u8 - : "Enable this attribute for all model IDs."u8); - - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (all) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); - ImUtf8.TextFramed("All IDs"u8, ImGui.GetColorU32(ImGuiCol.FrameBg, all || allSlots ? ImGui.GetStyle().DisabledAlpha : 1f), - new Vector2(unscaledWidth, 0), ImGui.GetColorU32(ImGuiCol.TextDisabled)); - } - else - { - var max = identifier.Slot.ToSpecificEnum() is BodySlot ? byte.MaxValue : ExpandedEqpGmpBase.Count - 1; - if (IdInput("##atrPrimaryId"u8, unscaledWidth, identifier.Id.GetValueOrDefault(0).Id, out var setId, 0, max, false)) - { - identifier = identifier with { Id = setId }; - ret = true; - } - } - - ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'e####' part of an item path or similar for customizations."u8); - - return ret; - } - - public bool DrawHumanSlot(ref AtrIdentifier identifier, float unscaledWidth = 150) - { - var ret = false; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - using (var combo = ImUtf8.Combo("##atrSlot"u8, ShpMetaDrawer.SlotName(identifier.Slot))) - { - if (combo) - foreach (var slot in ShpMetaDrawer.AvailableSlots) - { - if (!ImUtf8.Selectable(ShpMetaDrawer.SlotName(slot), slot == identifier.Slot) || slot == identifier.Slot) - continue; - - ret = true; - if (slot is HumanSlot.Unknown) - { - identifier = identifier with - { - Id = null, - Slot = slot, - }; - } - else - { - identifier = identifier with - { - Id = identifier.Id.HasValue - ? (PrimaryId)Math.Clamp(identifier.Id.Value.Id, 0, - slot.ToSpecificEnum() is BodySlot ? byte.MaxValue : ExpandedEqpGmpBase.Count - 1) - : null, - Slot = slot, - }; - ret = true; - } - } - } - - ImUtf8.HoverTooltip("Model Slot"u8); - return ret; - } - - private static bool DrawGenderRaceConditionInput(ref AtrIdentifier identifier, float unscaledWidth = 250) - { - var ret = false; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - - using (var combo = ImUtf8.Combo("##shpGenderRace"u8, - identifier.GenderRaceCondition is GenderRace.Unknown - ? "Any Gender & Race" - : $"{identifier.GenderRaceCondition.ToName()} ({identifier.GenderRaceCondition.ToRaceCode()})")) - { - if (combo) - { - if (ImUtf8.Selectable("Any Gender & Race"u8, identifier.GenderRaceCondition is GenderRace.Unknown) - && identifier.GenderRaceCondition is not GenderRace.Unknown) - { - identifier = identifier with { GenderRaceCondition = GenderRace.Unknown }; - ret = true; - } - - foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1)) - { - if (ImUtf8.Selectable($"{gr.ToName()} ({gr.ToRaceCode()})", identifier.GenderRaceCondition == gr) - && identifier.GenderRaceCondition != gr) - { - identifier = identifier with { GenderRaceCondition = gr }; - ret = true; - } - } - } - } - - ImUtf8.HoverTooltip( - "Only activate this attribute for this gender & race code."u8); - - return ret; - } - - public static unsafe bool DrawAttributeKeyInput(ref AtrIdentifier identifier, ref ShapeAttributeString buffer, ref bool valid, - float unscaledWidth = 150) - { - var ret = false; - var ptr = Unsafe.AsPointer(ref buffer); - var span = new Span(ptr, ShapeAttributeString.MaxLength + 1); - using (new ImRaii.ColorStyle().Push(ImGuiCol.Border, Colors.RegexWarningBorder, !valid).Push(ImGuiStyleVar.FrameBorderSize, 1f, !valid)) - { - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - if (ImUtf8.InputText("##atrAttribute"u8, span, out int newLength, "Attribute..."u8)) - { - buffer.ForceLength((byte)newLength); - valid = buffer.ValidateCustomAttributeString(); - if (valid) - identifier = identifier with { Attribute = buffer }; - ret = true; - } - } - - ImUtf8.HoverTooltip("Supported attribute need to have the format `atrx_*` and a maximum length of 30 characters."u8); - return ret; - } -} diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs index 16af5217..348a0d4c 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqdpMetaDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs index 77c2915a..d6df95cb 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EqpMetaDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs index 84e09be5..e5e28a3d 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/EstMetaDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs index b03f4aa5..929feadd 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GlobalEqpMetaDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs index 4053560b..3691a4f7 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/GmpMetaDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.GameData.Structs; diff --git a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs index bb87cd47..34488a87 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/ImcMetaDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs index f608a194..4c9142d8 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Raii; @@ -44,13 +44,9 @@ public abstract class MetaDrawer(ModMetaEditor editor, Meta DrawNew(); var height = ColumnHeight; - var skips = ImGuiClip.GetNecessarySkipsAtPos(height, ImGui.GetCursorPosY(), Count); - if (skips < Count) - { - var remainder = ImGuiClip.ClippedTableDraw(Enumerate(), skips, DrawLine, Count); - if (remainder > 0) - ImGuiClip.DrawEndDummy(remainder, height); - } + 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); @@ -158,7 +154,7 @@ public abstract class MetaDrawer(ModMetaEditor editor, Meta if (!ImUtf8.IconButton(FontAwesomeIcon.Clipboard, tooltip)) return; - var text = Functions.ToCompressedBase64(manipulations.Value, 0); + var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion); if (text.Length > 0) ImGui.SetClipboardText(text); } diff --git a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs index 792611e2..d1c7cd52 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/MetaDrawers.cs @@ -11,9 +11,7 @@ public class MetaDrawers( GmpMetaDrawer gmp, ImcMetaDrawer imc, RspMetaDrawer rsp, - AtchMetaDrawer atch, - ShpMetaDrawer shp, - AtrMetaDrawer atr) : IService + AtchMetaDrawer atch) : IService { public readonly EqdpMetaDrawer Eqdp = eqdp; public readonly EqpMetaDrawer Eqp = eqp; @@ -23,8 +21,6 @@ public class MetaDrawers( public readonly ImcMetaDrawer Imc = imc; public readonly GlobalEqpMetaDrawer GlobalEqp = globalEqp; public readonly AtchMetaDrawer Atch = atch; - public readonly ShpMetaDrawer Shp = shp; - public readonly AtrMetaDrawer Atr = atr; public IMetaDrawer? Get(MetaManipulationType type) => type switch @@ -36,8 +32,6 @@ public class MetaDrawers( MetaManipulationType.Gmp => Gmp, MetaManipulationType.Rsp => Rsp, MetaManipulationType.Atch => Atch, - MetaManipulationType.Shp => Shp, - MetaManipulationType.Atr => Atr, MetaManipulationType.GlobalEqp => GlobalEqp, _ => null, }; diff --git a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs index 88abe0cb..d60f877b 100644 --- a/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs +++ b/Penumbra/UI/AdvancedWindow/Meta/RspMetaDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Newtonsoft.Json.Linq; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/AdvancedWindow/Meta/ShpMetaDrawer.cs b/Penumbra/UI/AdvancedWindow/Meta/ShpMetaDrawer.cs deleted file mode 100644 index 59692195..00000000 --- a/Penumbra/UI/AdvancedWindow/Meta/ShpMetaDrawer.cs +++ /dev/null @@ -1,366 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using Newtonsoft.Json.Linq; -using OtterGui.Raii; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Meta; -using Penumbra.Meta.Files; -using Penumbra.Meta.Manipulations; -using Penumbra.Mods.Editor; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.AdvancedWindow.Meta; - -public sealed class ShpMetaDrawer(ModMetaEditor editor, MetaFileManager metaFiles) - : MetaDrawer(editor, metaFiles), IService -{ - public override ReadOnlySpan Label - => "Shape Keys (SHP)###SHP"u8; - - private ShapeAttributeString _buffer = ShapeAttributeString.TryRead("shpx_"u8, out var s) ? s : ShapeAttributeString.Empty; - private bool _identifierValid; - - public override int NumColumns - => 8; - - public override float ColumnHeight - => ImUtf8.FrameHeightSpacing; - - protected override void Initialize() - { - Identifier = new ShpIdentifier(HumanSlot.Unknown, null, ShapeAttributeString.Empty, ShapeConnectorCondition.None, GenderRace.Unknown); - } - - protected override void DrawNew() - { - ImGui.TableNextColumn(); - CopyToClipboardButton("Copy all current SHP manipulations to clipboard."u8, - new Lazy(() => MetaDictionary.SerializeTo([], Editor.Shp))); - - ImGui.TableNextColumn(); - var canAdd = !Editor.Contains(Identifier) && _identifierValid; - var tt = canAdd - ? "Stage this edit."u8 - : _identifierValid - ? "This entry does not contain a valid shape key."u8 - : "This entry is already edited."u8; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, tt, disabled: !canAdd)) - Editor.Changes |= Editor.TryAdd(Identifier, ShpEntry.True); - - DrawIdentifierInput(ref Identifier); - DrawEntry(ref Entry, true); - } - - protected override void DrawEntry(ShpIdentifier identifier, ShpEntry entry) - { - DrawMetaButtons(identifier, entry); - DrawIdentifier(identifier); - - if (DrawEntry(ref entry, false)) - Editor.Changes |= Editor.Update(identifier, entry); - } - - protected override IEnumerable<(ShpIdentifier, ShpEntry)> Enumerate() - => Editor.Shp - .OrderBy(kvp => kvp.Key.Shape) - .ThenBy(kvp => kvp.Key.Slot) - .ThenBy(kvp => kvp.Key.Id) - .ThenBy(kvp => kvp.Key.ConnectorCondition) - .Select(kvp => (kvp.Key, kvp.Value)); - - protected override int Count - => Editor.Shp.Count; - - private bool DrawIdentifierInput(ref ShpIdentifier identifier) - { - ImGui.TableNextColumn(); - var changes = DrawHumanSlot(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawGenderRaceConditionInput(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawPrimaryId(ref identifier); - - ImGui.TableNextColumn(); - changes |= DrawShapeKeyInput(ref identifier, ref _buffer, ref _identifierValid); - - ImGui.TableNextColumn(); - changes |= DrawConnectorConditionInput(ref identifier); - return changes; - } - - private static void DrawIdentifier(ShpIdentifier identifier) - { - ImGui.TableNextColumn(); - - ImUtf8.TextFramed(SlotName(identifier.Slot), FrameColor); - ImUtf8.HoverTooltip("Model Slot"u8); - - ImGui.TableNextColumn(); - if (identifier.GenderRaceCondition is not GenderRace.Unknown) - { - ImUtf8.TextFramed($"{identifier.GenderRaceCondition.ToName()} ({identifier.GenderRaceCondition.ToRaceCode()})", FrameColor); - ImUtf8.HoverTooltip("Gender & Race Code for this shape key to be set."); - } - else - { - ImUtf8.TextFramed("Any Gender & Race"u8, FrameColor); - } - - ImGui.TableNextColumn(); - if (identifier.Id.HasValue) - ImUtf8.TextFramed($"{identifier.Id.Value.Id}", FrameColor); - else - ImUtf8.TextFramed("All IDs"u8, FrameColor); - ImUtf8.HoverTooltip("Primary ID"u8); - - ImGui.TableNextColumn(); - ImUtf8.TextFramed(identifier.Shape.AsSpan, FrameColor); - - ImGui.TableNextColumn(); - if (identifier.ConnectorCondition is not ShapeConnectorCondition.None) - { - ImUtf8.TextFramed($"{identifier.ConnectorCondition}", FrameColor); - ImUtf8.HoverTooltip("Connector condition for this shape to be activated."); - } - } - - private static bool DrawEntry(ref ShpEntry entry, bool disabled) - { - using var dis = ImRaii.Disabled(disabled); - ImGui.TableNextColumn(); - var value = entry.Value; - var changes = ImUtf8.Checkbox("##shpEntry"u8, ref value); - if (changes) - entry = new ShpEntry(value); - ImUtf8.HoverTooltip("Whether to enable or disable this shape key for the selected items."); - return changes; - } - - public static bool DrawPrimaryId(ref ShpIdentifier identifier, float unscaledWidth = 100) - { - var allSlots = identifier.Slot is HumanSlot.Unknown; - var all = !identifier.Id.HasValue; - var ret = false; - using (ImRaii.Disabled(allSlots)) - { - if (ImUtf8.Checkbox("##shpAll"u8, ref all)) - { - identifier = identifier with { Id = all ? null : 0 }; - ret = true; - } - } - - ImUtf8.HoverTooltip(allSlots ? "When using all slots, you also need to use all IDs."u8 : "Enable this shape key for all model IDs."u8); - - ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); - if (all) - { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0.05f, 0.5f)); - ImUtf8.TextFramed("All IDs"u8, ImGui.GetColorU32(ImGuiCol.FrameBg, all || allSlots ? ImGui.GetStyle().DisabledAlpha : 1f), - new Vector2(unscaledWidth, 0), ImGui.GetColorU32(ImGuiCol.TextDisabled)); - } - else - { - var max = identifier.Slot.ToSpecificEnum() is BodySlot ? byte.MaxValue : ExpandedEqpGmpBase.Count - 1; - if (IdInput("##shpPrimaryId"u8, unscaledWidth, identifier.Id.GetValueOrDefault(0).Id, out var setId, 0, max, false)) - { - identifier = identifier with { Id = setId }; - ret = true; - } - } - - ImUtf8.HoverTooltip("Primary ID - You can usually find this as the 'e####' part of an item path or similar for customizations."u8); - - return ret; - } - - public bool DrawHumanSlot(ref ShpIdentifier identifier, float unscaledWidth = 170) - { - var ret = false; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - using (var combo = ImUtf8.Combo("##shpSlot"u8, SlotName(identifier.Slot))) - { - if (combo) - foreach (var slot in AvailableSlots) - { - if (!ImUtf8.Selectable(SlotName(slot), slot == identifier.Slot) || slot == identifier.Slot) - continue; - - ret = true; - if (slot is HumanSlot.Unknown) - { - identifier = identifier with - { - Id = null, - Slot = slot, - }; - } - else - { - identifier = identifier with - { - Id = identifier.Id.HasValue - ? (PrimaryId)Math.Clamp(identifier.Id.Value.Id, 0, - slot.ToSpecificEnum() is BodySlot ? byte.MaxValue : ExpandedEqpGmpBase.Count - 1) - : null, - Slot = slot, - ConnectorCondition = Identifier.ConnectorCondition switch - { - ShapeConnectorCondition.Wrists when slot is HumanSlot.Body or HumanSlot.Hands => ShapeConnectorCondition.Wrists, - ShapeConnectorCondition.Waist when slot is HumanSlot.Body or HumanSlot.Legs => ShapeConnectorCondition.Waist, - ShapeConnectorCondition.Ankles when slot is HumanSlot.Legs or HumanSlot.Feet => ShapeConnectorCondition.Ankles, - _ => ShapeConnectorCondition.None, - }, - }; - ret = true; - } - } - } - - ImUtf8.HoverTooltip("Model Slot"u8); - return ret; - } - - public static unsafe bool DrawShapeKeyInput(ref ShpIdentifier identifier, ref ShapeAttributeString buffer, ref bool valid, - float unscaledWidth = 200) - { - var ret = false; - var ptr = Unsafe.AsPointer(ref buffer); - var span = new Span(ptr, ShapeAttributeString.MaxLength + 1); - using (new ImRaii.ColorStyle().Push(ImGuiCol.Border, Colors.RegexWarningBorder, !valid).Push(ImGuiStyleVar.FrameBorderSize, 1f, !valid)) - { - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - if (ImUtf8.InputText("##shpShape"u8, span, out int newLength, "Shape Key..."u8)) - { - buffer.ForceLength((byte)newLength); - valid = buffer.ValidateCustomShapeString(); - if (valid) - identifier = identifier with { Shape = buffer }; - ret = true; - } - } - - ImUtf8.HoverTooltip("Supported shape keys need to have the format `shpx_*` and a maximum length of 30 characters."u8); - return ret; - } - - private static bool DrawConnectorConditionInput(ref ShpIdentifier identifier, float unscaledWidth = 80) - { - var ret = false; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - var (showWrists, showWaist, showAnkles, disable) = identifier.Slot switch - { - HumanSlot.Unknown => (true, true, true, false), - HumanSlot.Body => (true, true, false, false), - HumanSlot.Legs => (false, true, true, false), - HumanSlot.Hands => (true, false, false, false), - HumanSlot.Feet => (false, false, true, false), - _ => (false, false, false, true), - }; - using var disabled = ImRaii.Disabled(disable); - using (var combo = ImUtf8.Combo("##shpCondition"u8, $"{identifier.ConnectorCondition}")) - { - if (combo) - { - if (ImUtf8.Selectable("None"u8, identifier.ConnectorCondition is ShapeConnectorCondition.None)) - identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.None }; - - if (showWrists && ImUtf8.Selectable("Wrists"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Wrists)) - identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Wrists }; - - if (showWaist && ImUtf8.Selectable("Waist"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Waist)) - identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Waist }; - - if (showAnkles && ImUtf8.Selectable("Ankles"u8, identifier.ConnectorCondition is ShapeConnectorCondition.Ankles)) - identifier = identifier with { ConnectorCondition = ShapeConnectorCondition.Ankles }; - } - } - - ImUtf8.HoverTooltip( - "Only activate this shape key if any custom connector shape keys (shpx_[wr|wa|an]_*) are also enabled through matching attributes."u8); - return ret; - } - - private static bool DrawGenderRaceConditionInput(ref ShpIdentifier identifier, float unscaledWidth = 250) - { - var ret = false; - ImGui.SetNextItemWidth(unscaledWidth * ImUtf8.GlobalScale); - - using (var combo = ImUtf8.Combo("##shpGenderRace"u8, identifier.GenderRaceCondition is GenderRace.Unknown - ? "Any Gender & Race" - : $"{identifier.GenderRaceCondition.ToName()} ({identifier.GenderRaceCondition.ToRaceCode()})")) - { - if (combo) - { - if (ImUtf8.Selectable("Any Gender & Race"u8, identifier.GenderRaceCondition is GenderRace.Unknown) - && identifier.GenderRaceCondition is not GenderRace.Unknown) - { - identifier = identifier with { GenderRaceCondition = GenderRace.Unknown }; - ret = true; - } - - foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1)) - { - if (ImUtf8.Selectable($"{gr.ToName()} ({gr.ToRaceCode()})", identifier.GenderRaceCondition == gr) - && identifier.GenderRaceCondition != gr) - { - identifier = identifier with { GenderRaceCondition = gr }; - ret = true; - } - } - } - } - - ImUtf8.HoverTooltip( - "Only activate this shape key for this gender & race code."u8); - - return ret; - } - - public static ReadOnlySpan AvailableSlots - => - [ - HumanSlot.Unknown, - HumanSlot.Head, - HumanSlot.Body, - HumanSlot.Hands, - HumanSlot.Legs, - HumanSlot.Feet, - HumanSlot.Ears, - HumanSlot.Neck, - HumanSlot.Wrists, - HumanSlot.RFinger, - HumanSlot.LFinger, - HumanSlot.Glasses, - HumanSlot.Hair, - HumanSlot.Face, - HumanSlot.Ear, - ]; - - public static ReadOnlySpan SlotName(HumanSlot slot) - => slot switch - { - HumanSlot.Unknown => "All Slots"u8, - HumanSlot.Head => "Equipment: Head"u8, - HumanSlot.Body => "Equipment: Body"u8, - HumanSlot.Hands => "Equipment: Hands"u8, - HumanSlot.Legs => "Equipment: Legs"u8, - HumanSlot.Feet => "Equipment: Feet"u8, - HumanSlot.Ears => "Equipment: Ears"u8, - HumanSlot.Neck => "Equipment: Neck"u8, - HumanSlot.Wrists => "Equipment: Wrists"u8, - HumanSlot.RFinger => "Equipment: Right Finger"u8, - HumanSlot.LFinger => "Equipment: Left Finger"u8, - HumanSlot.Glasses => "Equipment: Glasses"u8, - HumanSlot.Hair => "Customization: Hair"u8, - HumanSlot.Face => "Customization: Face"u8, - HumanSlot.Ear => "Customization: Ears"u8, - _ => "Unknown"u8, - }; -} diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs index 4f7ae8da..258e51ff 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Deformers.cs @@ -1,9 +1,8 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility.Raii; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Text; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs index 63c99b8a..b07633b6 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs @@ -1,8 +1,8 @@ +using System.Linq; using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using Penumbra.Mods.Editor; @@ -15,7 +15,6 @@ namespace Penumbra.UI.AdvancedWindow; public partial class ModEditWindow { private readonly HashSet _selectedFiles = new(256); - private readonly HashSet _cutPaths = []; private LowerString _fileFilter = LowerString.Empty; private bool _showGamePaths = true; private string _gamePathEdit = string.Empty; @@ -126,7 +125,7 @@ public partial class ModEditWindow using var id = ImRaii.PushId(i); ImGui.TableNextColumn(); - DrawSelectable(registry, i); + DrawSelectable(registry); if (!_showGamePaths) continue; @@ -178,79 +177,24 @@ public partial class ModEditWindow } } - private void DrawSelectable(FileRegistry registry, int i) + private void DrawSelectable(FileRegistry registry) { var selected = _selectedFiles.Contains(registry); var color = registry.SubModUsage.Count == 0 ? ColorId.ConflictingMod : registry.CurrentUsage == registry.SubModUsage.Count ? ColorId.NewMod : ColorId.InheritedMod; - using (ImRaii.PushColor(ImGuiCol.Text, color.Value())) + using var c = ImRaii.PushColor(ImGuiCol.Text, color.Value()); + if (UiHelpers.Selectable(registry.RelPath.Path, selected)) { - if (UiHelpers.Selectable(registry.RelPath.Path, selected)) - { - if (selected) - _selectedFiles.Remove(registry); - else - _selectedFiles.Add(registry); - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - ImUtf8.OpenPopup("context"u8); - - var rightText = DrawFileTooltip(registry, color); - - ImGui.SameLine(); - ImGuiUtil.RightAlign(rightText); + if (selected) + _selectedFiles.Remove(registry); + else + _selectedFiles.Add(registry); } - DrawContextMenu(registry, i); - } + var rightText = DrawFileTooltip(registry, color); - private void DrawContextMenu(FileRegistry registry, int i) - { - using var context = ImUtf8.Popup("context"u8); - if (!context) - return; - - if (ImUtf8.Selectable("Copy Full File Path")) - ImUtf8.SetClipboardText(registry.File.FullName); - - using (ImRaii.Disabled(registry.CurrentUsage == 0)) - { - if (ImUtf8.Selectable("Copy Game Paths"u8)) - { - _cutPaths.Clear(); - for (var j = 0; j < registry.SubModUsage.Count; ++j) - { - if (registry.SubModUsage[j].Item1 != _editor.Option) - continue; - - _cutPaths.Add(registry.SubModUsage[j].Item2); - } - } - } - - using (ImRaii.Disabled(registry.CurrentUsage == 0)) - { - if (ImUtf8.Selectable("Cut Game Paths"u8)) - { - _cutPaths.Clear(); - for (var j = 0; j < registry.SubModUsage.Count; ++j) - { - if (registry.SubModUsage[j].Item1 != _editor.Option) - continue; - - _cutPaths.Add(registry.SubModUsage[j].Item2); - _editor.FileEditor.SetGamePath(_editor.Option, i, j--, Utf8GamePath.Empty); - } - } - } - - using (ImRaii.Disabled(_cutPaths.Count == 0)) - { - if (ImUtf8.Selectable("Paste Game Paths"u8)) - foreach (var path in _cutPaths) - _editor.FileEditor.SetGamePath(_editor.Option!, i, -1, path); - } + ImGui.SameLine(); + ImGuiUtil.RightAlign(rightText); } private void PrintGamePath(int i, int j, FileRegistry registry, IModDataContainer subMod, Utf8GamePath gamePath) @@ -287,17 +231,6 @@ public partial class ModEditWindow using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString()); } - else if (tmp.Length > 0 && Path.GetExtension(tmp) != registry.File.Extension) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(pos); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.TextColored(0xFF00B0B0, FontAwesomeIcon.ExclamationCircle.ToIconString()); - } - - ImUtf8.HoverTooltip("The game path and the file do not have the same extension."u8); - } } private void PrintNewGamePath(int i, FileRegistry registry, IModDataContainer subMod) @@ -330,17 +263,6 @@ public partial class ModEditWindow using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString()); } - else if (tmp.Length > 0 && Path.GetExtension(tmp) != registry.File.Extension) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(pos); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.TextColored(0xFF00B0B0, FontAwesomeIcon.ExclamationCircle.ToIconString()); - } - - ImUtf8.HoverTooltip("The game path and the file do not have the same extension."u8); - } } private void DrawButtonHeader() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.cs index 3caff226..59b38465 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Materials.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; -using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.UI.AdvancedWindow.Materials; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs index 06cd0763..22271d38 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Meta.cs @@ -1,11 +1,9 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.Api.Api; -using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; using Penumbra.Meta.Manipulations; using Penumbra.UI.AdvancedWindow.Meta; using Penumbra.UI.Classes; @@ -45,10 +43,8 @@ public partial class ModEditWindow if (ImUtf8.Button("Write as TexTools Files"u8)) _metaFileManager.WriteAllTexToolsMeta(Mod!); ImGui.SameLine(); - if (ImUtf8.ButtonEx("Remove All Default-Values"u8, "Delete any entries from all lists that set the value to its default value."u8)) + 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(); - ImGui.SameLine(); - DrawAtchDragDrop(); using var child = ImRaii.Child("##meta", -Vector2.One, true); if (!child) @@ -61,52 +57,9 @@ public partial class ModEditWindow DrawEditHeader(MetaManipulationType.Gmp); DrawEditHeader(MetaManipulationType.Rsp); DrawEditHeader(MetaManipulationType.Atch); - DrawEditHeader(MetaManipulationType.Shp); - DrawEditHeader(MetaManipulationType.Atr); DrawEditHeader(MetaManipulationType.GlobalEqp); } - private void DrawAtchDragDrop() - { - _dragDropManager.CreateImGuiSource("atchDrag", f => f.Extensions.Contains(".atch"), f => - { - var gr = Parser.ParseRaceCode(f.Files.FirstOrDefault() ?? string.Empty); - if (gr is GenderRace.Unknown) - return false; - - ImUtf8.Text($"Dragging .atch for {gr.ToName()}..."); - return true; - }); - var hasAtch = _editor.Files.Atch.Count > 0; - if (ImUtf8.ButtonEx("Import .atch"u8, - _dragDropManager.IsDragging - ? ""u8 - : hasAtch - ? "Drag a .atch file containing its race code in the path here to import its values.\n\nClick to select an .atch file from the mod."u8 - : "Drag a .atch file containing its race code in the path here to import its values."u8, default, - !_dragDropManager.IsDragging && !hasAtch) - && hasAtch) - ImUtf8.OpenPopup("##atchPopup"u8); - if (_dragDropManager.CreateImGuiTarget("atchDrag", out var files, out _) && files.FirstOrDefault() is { } file) - _metaDrawers.Atch.ImportFile(file); - - using var popup = ImUtf8.Popup("##atchPopup"u8); - if (!popup) - return; - - if (!hasAtch) - { - ImGui.CloseCurrentPopup(); - return; - } - - foreach (var atchFile in _editor.Files.Atch) - { - if (ImUtf8.Selectable(atchFile.RelPath.Path.Span) && atchFile.File.Exists) - _metaDrawers.Atch.ImportFile(atchFile.File.FullName); - } - } - private void DrawEditHeader(MetaManipulationType type) { var drawer = _metaDrawers.Get(type); @@ -158,41 +111,43 @@ public partial class ModEditWindow if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clipboard.ToIconString(), iconSize, tooltip, false, true)) return; - var text = Functions.ToCompressedBase64(manipulations, 0); + var text = Functions.ToCompressedBase64(manipulations, MetaApi.CurrentVersion); if (text.Length > 0) ImGui.SetClipboardText(text); } private void AddFromClipboardButton() { - if (ImUtf8.Button("Add from Clipboard"u8)) + if (ImGui.Button("Add from Clipboard")) { var clipboard = ImGuiUtil.GetClipboardText(); - if (MetaApi.ConvertManips(clipboard, out var manips, out _)) + var version = Functions.FromCompressedBase64(clipboard, out var manips); + if (version == MetaApi.CurrentVersion && manips != null) { _editor.MetaEditor.UpdateTo(manips); _editor.MetaEditor.Changes = true; } } - ImUtf8.HoverTooltip( - "Try to add meta manipulations currently stored in the clipboard to the current manipulations.\nOverwrites already existing manipulations."u8); + ImGuiUtil.HoverTooltip( + "Try to add meta manipulations currently stored in the clipboard to the current manipulations.\nOverwrites already existing manipulations."); } private void SetFromClipboardButton() { - if (ImUtf8.Button("Set from Clipboard"u8)) + if (ImGui.Button("Set from Clipboard")) { var clipboard = ImGuiUtil.GetClipboardText(); - if (MetaApi.ConvertManips(clipboard, out var manips, out _)) + var version = Functions.FromCompressedBase64(clipboard, out var manips); + if (version == MetaApi.CurrentVersion && manips != null) { _editor.MetaEditor.SetTo(manips); _editor.MetaEditor.Changes = true; } } - ImUtf8.HoverTooltip( - "Try to set the current meta manipulations to the set currently stored in the clipboard.\nRemoves all other manipulations."u8); + ImGuiUtil.HoverTooltip( + "Try to set the current meta manipulations to the set currently stored in the clipboard.\nRemoves all other manipulations."); } } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index fc197bc0..b436448f 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -1,4 +1,4 @@ -using OtterGui.Extensions; +using OtterGui; using Penumbra.GameData; using Penumbra.GameData.Files; using Penumbra.Import.Models; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index a7db7f25..de088736 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -1,16 +1,14 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using ImGuiNET; using Lumina.Data.Parsing; using OtterGui; using OtterGui.Custom; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData; using Penumbra.GameData.Files; using Penumbra.Import.Models; -using Penumbra.Import.Models.Import; using Penumbra.String.Classes; using Penumbra.UI.Classes; @@ -18,7 +16,7 @@ namespace Penumbra.UI.AdvancedWindow; public partial class ModEditWindow { - private const int MdlMaterialMaximum = ModelImporter.MaterialLimit; + private const int MdlMaterialMaximum = 4; private const string MdlImportDocumentation = @"https://github.com/xivdev/Penumbra/wiki/Model-IO#user-content-9b49d296-23ab-410a-845b-a3be769b71ea"; @@ -31,18 +29,19 @@ public partial class ModEditWindow private class LoadedData { - public MdlFile LastFile = null!; + public MdlFile LastFile = null!; public readonly List SubMeshAttributeTags = []; public long[] LodTriCount = []; } - private string _modelNewMaterial = string.Empty; + private string _modelNewMaterial = string.Empty; private readonly LoadedData _main = new(); private readonly LoadedData _preview = new(); - private string _customPath = string.Empty; - private Utf8GamePath _customGamePath = Utf8GamePath.Empty; + private string _customPath = string.Empty; + private Utf8GamePath _customGamePath = Utf8GamePath.Empty; + private LoadedData UpdateFile(MdlFile file, bool force, bool disabled) @@ -67,7 +66,7 @@ public partial class ModEditWindow private bool DrawModelPanel(MdlTab tab, bool disabled) { - var ret = tab.Dirty; + var ret = tab.Dirty; var data = UpdateFile(tab.Mdl, ret, disabled); DrawVersionUpdate(tab, disabled); DrawImportExport(tab, disabled); @@ -88,14 +87,13 @@ public partial class ModEditWindow if (disabled || tab.Mdl.Version is not MdlFile.V5) return; - if (!ImUtf8.ButtonEx("Update MDL Version from V5 to V6"u8, - "Try using this if the bone weights of a pre-Dawntrail model seem wrong.\n\nThis is not revertible."u8, + if (!ImUtf8.ButtonEx("Update MDL Version from V5 to V6"u8, "Try using this if the bone weights of a pre-Dawntrail model seem wrong.\n\nThis is not revertible."u8, new Vector2(-0.1f, 0), false, 0, Colors.PressEnterWarningBg)) return; tab.Mdl.ConvertV5ToV6(); _modelTab.SaveFile(); - } + } private void DrawImportExport(MdlTab tab, bool disabled) { @@ -350,7 +348,7 @@ public partial class ModEditWindow if (!disabled) { ImGui.TableSetupColumn("actions", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X); - ImGui.TableSetupColumn("help", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X); + ImGui.TableSetupColumn("help", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X); } var inputFlags = disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None; @@ -369,11 +367,10 @@ public partial class ModEditWindow ImGui.TableNextColumn(); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, string.Empty, !validName, true)) { - ret |= true; - tab.Mdl.Materials = materials.AddItem(_modelNewMaterial); - _modelNewMaterial = string.Empty; + ret |= true; + tab.Mdl.Materials = materials.AddItem(_modelNewMaterial); + _modelNewMaterial = string.Empty; } - ImGui.TableNextColumn(); if (!validName && _modelNewMaterial.Length > 0) DrawInvalidMaterialMarker(); @@ -424,16 +421,14 @@ public partial class ModEditWindow // Add markers to invalid materials. if (!tab.ValidateMaterial(temp)) DrawInvalidMaterialMarker(); - + return ret; } private static void DrawInvalidMaterialMarker() { - using (ImRaii.PushFont(UiBuilder.IconFont)) - { + using (ImRaii.PushFont(UiBuilder.IconFont)) ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString()); - } ImGuiUtil.HoverTooltip( "Materials must be either relative (e.g. \"/filename.mtrl\")\n" @@ -501,11 +496,11 @@ public partial class ModEditWindow using var node = ImRaii.TreeNode($"Click to expand"); if (!node) return; - + var flags = ImGuiTableFlags.SizingFixedFit - | ImGuiTableFlags.RowBg - | ImGuiTableFlags.Borders - | ImGuiTableFlags.NoHostExtendX; + | ImGuiTableFlags.RowBg + | ImGuiTableFlags.Borders + | ImGuiTableFlags.NoHostExtendX; using var table = ImRaii.Table(string.Empty, 4, flags); if (!table) return; @@ -593,7 +588,6 @@ public partial class ModEditWindow if (!header) return false; - var ret = false; using (var table = ImRaii.Table("##data", 2, ImGuiTableFlags.SizingFixedFit)) { if (table) @@ -654,49 +648,22 @@ public partial class ModEditWindow using (var attributes = ImRaii.TreeNode("Attributes", ImGuiTreeNodeFlags.DefaultOpen)) { if (attributes) - for (var i = 0; i < data.LastFile.Attributes.Length; ++i) - { - using var id = ImUtf8.PushId(i); - ref var attribute = ref data.LastFile.Attributes[i]; - var name = attribute; - if (ImUtf8.InputText("##attribute"u8, ref name, "Attribute Name..."u8) && name.Length > 0 && name != attribute) - { - attribute = name; - ret = true; - } - } + foreach (var attribute in data.LastFile.Attributes) + ImRaii.TreeNode(attribute, ImGuiTreeNodeFlags.Leaf).Dispose(); } using (var bones = ImRaii.TreeNode("Bones", ImGuiTreeNodeFlags.DefaultOpen)) { if (bones) - for (var i = 0; i < data.LastFile.Bones.Length; ++i) - { - using var id = ImUtf8.PushId(i); - ref var bone = ref data.LastFile.Bones[i]; - var name = bone; - if (ImUtf8.InputText("##bone"u8, ref name, "Bone Name..."u8) && name.Length > 0 && name != bone) - { - bone = name; - ret = true; - } - } + foreach (var bone in data.LastFile.Bones) + ImRaii.TreeNode(bone, ImGuiTreeNodeFlags.Leaf).Dispose(); } using (var shapes = ImRaii.TreeNode("Shapes", ImGuiTreeNodeFlags.DefaultOpen)) { if (shapes) - for (var i = 0; i < data.LastFile.Shapes.Length; ++i) - { - using var id = ImUtf8.PushId(i); - ref var shape = ref data.LastFile.Shapes[i]; - var name = shape.ShapeName; - if (ImUtf8.InputText("##shape"u8, ref name, "Shape Name..."u8) && name.Length > 0 && name != shape.ShapeName) - { - shape.ShapeName = name; - ret = true; - } - } + foreach (var shape in data.LastFile.Shapes) + ImRaii.TreeNode(shape.ShapeName, ImGuiTreeNodeFlags.Leaf).Dispose(); } if (data.LastFile.RemainingData.Length > 0) @@ -706,7 +673,7 @@ public partial class ModEditWindow Widget.DrawHexViewer(data.LastFile.RemainingData); } - return ret; + return false; } private static bool GetFirstModel(IEnumerable files, [NotNullWhen(true)] out string? file) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs index f55ae576..6fb223df 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs @@ -1,7 +1,8 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Lumina.Data; -using OtterGui.Text; +using OtterGui; +using OtterGui.Raii; using Penumbra.Api.Enums; using Penumbra.GameData.Files; using Penumbra.Interop.ResourceTree; @@ -17,6 +18,7 @@ public partial class ModEditWindow private readonly FileDialogService _fileDialog; private readonly ResourceTreeFactory _resourceTreeFactory; private readonly ResourceTreeViewer _quickImportViewer; + private readonly Dictionary _quickImportWritables = new(); private readonly Dictionary<(Utf8GamePath, IWritable?), QuickImportAction> _quickImportActions = new(); private HashSet GetPlayerResourcesOfType(ResourceType type) @@ -41,7 +43,7 @@ public partial class ModEditWindow private void DrawQuickImportTab() { - using var tab = ImUtf8.TabItem("Import from Screen"u8); + using var tab = ImRaii.TabItem("Import from Screen"); if (!tab) { _quickImportActions.Clear(); @@ -55,11 +57,52 @@ public partial class ModEditWindow private void OnQuickImportRefresh() { + _quickImportWritables.Clear(); _quickImportActions.Clear(); } - private void DrawQuickImportActions(ResourceNode resourceNode, IWritable? writable, Vector2 buttonSize) + private void DrawQuickImportActions(ResourceNode resourceNode, Vector2 buttonSize) { + if (!_quickImportWritables!.TryGetValue(resourceNode.FullPath, out var writable)) + { + var path = resourceNode.FullPath.ToPath(); + if (resourceNode.FullPath.IsRooted) + { + writable = new RawFileWritable(path); + } + else + { + var file = _gameData.GetFile(path); + writable = file == null ? null : new RawGameFileWritable(file); + } + + _quickImportWritables.Add(resourceNode.FullPath, writable); + } + + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), buttonSize, "Export this file.", + resourceNode.FullPath.FullName.Length == 0 || writable == null, true)) + { + var fullPathStr = resourceNode.FullPath.FullName; + var ext = resourceNode.PossibleGamePaths.Length == 1 + ? Path.GetExtension(resourceNode.GamePath.ToString()) + : Path.GetExtension(fullPathStr); + _fileDialog.OpenSavePicker($"Export {Path.GetFileName(fullPathStr)} to...", ext, Path.GetFileNameWithoutExtension(fullPathStr), ext, + (success, name) => + { + if (!success) + return; + + try + { + _editor.Compactor.WriteAllBytes(name, writable!.Write()); + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not export {fullPathStr}:\n{e}"); + } + }, null, false); + } + ImGui.SameLine(); if (!_quickImportActions!.TryGetValue((resourceNode.GamePath, writable), out var quickImport)) { @@ -67,18 +110,32 @@ public partial class ModEditWindow _quickImportActions.Add((resourceNode.GamePath, writable), quickImport); } - var canQuickImport = quickImport.CanExecute; - var quickImportEnabled = canQuickImport && (!resourceNode.Protected || _config.DeleteModModifier.IsActive()); - if (ImUtf8.IconButton(FontAwesomeIcon.FileImport, - $"Add a copy of this file to {quickImport.OptionName}.{(canQuickImport && !quickImportEnabled ? $"\nHold {_config.DeleteModModifier} while clicking to add." : string.Empty)}", - buttonSize, - !quickImportEnabled)) + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), buttonSize, + $"Add a copy of this file to {quickImport.OptionName}.", !quickImport.CanExecute, true)) { quickImport.Execute(); _quickImportActions.Remove((resourceNode.GamePath, writable)); } } + private record class RawFileWritable(string Path) : IWritable + { + public bool Valid + => true; + + public byte[] Write() + => File.ReadAllBytes(Path); + } + + private record class RawGameFileWritable(FileResource FileResource) : IWritable + { + public bool Valid + => true; + + public byte[] Write() + => FileResource.Data; + } + public class QuickImportAction { public const string FallbackOptionName = "the current option"; @@ -128,19 +185,19 @@ public partial class ModEditWindow public static QuickImportAction Prepare(ModEditWindow owner, Utf8GamePath gamePath, IWritable? file) { var editor = owner._editor; - if (editor is null) + if (editor == null) return new QuickImportAction(owner._editor, FallbackOptionName, gamePath); var subMod = editor.Option!; var optionName = subMod is IModOption o ? o.FullName : FallbackOptionName; - if (gamePath.IsEmpty || file is null || editor.FileEditor.Changes) + if (gamePath.IsEmpty || file == null || editor.FileEditor.Changes) return new QuickImportAction(editor, optionName, gamePath); if (subMod.Files.ContainsKey(gamePath) || subMod.FileSwaps.ContainsKey(gamePath)) return new QuickImportAction(editor, optionName, gamePath); var mod = owner.Mod; - if (mod is null) + if (mod == null) return new QuickImportAction(editor, optionName, gamePath); var (preferredPath, subDirs) = GetPreferredPath(mod, subMod as IModOption, owner._config.ReplaceNonAsciiOnImport); @@ -175,7 +232,7 @@ public partial class ModEditWindow { var path = mod.ModPath; var subDirs = 0; - if (subMod is null) + if (subMod == null) return (path, subDirs); var name = subMod.Name; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.ShaderPackages.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.ShaderPackages.cs index baaf4a82..41f1da26 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.ShaderPackages.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.ShaderPackages.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui; using OtterGui.Classes; @@ -12,7 +12,6 @@ using static Penumbra.GameData.Files.ShpkFile; using OtterGui.Widgets; using OtterGui.Text; using Penumbra.GameData.Structs; -using OtterGui.Extensions; namespace Penumbra.UI.AdvancedWindow; @@ -147,7 +146,7 @@ public partial class ModEditWindow using var font = ImRaii.PushFont(UiBuilder.MonoFont); var size = new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeight() * 20); - ImGuiNative.InputTextMultiline(DisassemblyLabel.Path, shader.Disassembly!.RawDisassembly.Path, + ImGuiNative.igInputTextMultiline(DisassemblyLabel.Path, shader.Disassembly!.RawDisassembly.Path, (uint)shader.Disassembly!.RawDisassembly.Length + 1, size, ImGuiInputTextFlags.ReadOnly, null, null); } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.ShpkTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.ShpkTab.cs index 6c2953e0..b5b39e90 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.ShpkTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.ShpkTab.cs @@ -1,7 +1,7 @@ using Dalamud.Utility; using Newtonsoft.Json.Linq; +using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using Penumbra.GameData.Files; using Penumbra.GameData.Files.ShaderStructs; using Penumbra.GameData.Interop; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 34e1e0d4..c08e8a8e 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -1,6 +1,5 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterTex; using Penumbra.Import.Textures; @@ -26,17 +25,11 @@ public partial class ModEditWindow { ("As Is", "Save the current texture with its own format without additional conversion or compression, if possible."), ("RGBA (Uncompressed)", - "Save the current texture as an uncompressed BGRA bitmap.\nThis requires the most space but technically offers the best quality."), - ("BC1 (Simple Compression for Opaque RGB)", - "Save the current texture compressed via BC1/DXT1 compression.\nThis offers a 8:1 compression ratio and is quick with acceptable quality, but only supports RGB, without Alpha.\n\nCan be used for diffuse maps and equipment textures to save extra space."), - ("BC3 (Simple Compression for RGBA)", - "Save the current texture compressed via BC3/DXT5 compression.\nThis offers a 4:1 compression ratio and is quick with acceptable quality, and fully supports RGBA.\n\nGeneric format that can be used for most textures."), - ("BC4 (Simple Compression for Opaque Grayscale)", - "Save the current texture compressed via BC4 compression.\nThis offers a 8:1 compression ratio and has almost indistinguishable quality, but only supports Grayscale, without Alpha.\n\nCan be used for face paints and legacy marks."), - ("BC5 (Simple Compression for Opaque RG)", - "Save the current texture compressed via BC5 compression.\nThis offers a 4:1 compression ratio and has almost indistinguishable quality, but only supports RG, without B or Alpha.\n\nRecommended for index maps, unrecommended for normal maps."), - ("BC7 (Complex Compression for RGBA)", - "Save the current texture compressed via BC7 compression.\nThis offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while.\n\nGeneric format that can be used for most textures."), + "Save the current texture as an uncompressed BGRA bitmap. This requires the most space but technically offers the best quality."), + ("BC3 (Simple Compression)", + "Save the current texture compressed via BC3/DXT5 compression. This offers a 4:1 compression ratio and is quick with acceptable quality."), + ("BC7 (Complex Compression)", + "Save the current texture compressed via BC7 compression. This offers a 4:1 compression ratio and has almost indistinguishable quality, but may take a while."), }; private void DrawInputChild(string label, Texture tex, Vector2 size, Vector2 imageSize) @@ -141,7 +134,7 @@ public partial class ModEditWindow tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - AddChangeTask(_left.Path); + InvokeChange(Mod, _left.Path); AddReloadTask(_left.Path, false); } @@ -166,7 +159,7 @@ public partial class ModEditWindow !canConvertInPlace || _left.Format is DXGIFormat.BC7Typeless or DXGIFormat.BC7UNorm or DXGIFormat.BC7UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC7, _left.MipMaps > 1); - AddChangeTask(_left.Path); + InvokeChange(Mod, _left.Path); AddReloadTask(_left.Path, false); } @@ -176,7 +169,7 @@ public partial class ModEditWindow !canConvertInPlace || _left.Format is DXGIFormat.BC3Typeless or DXGIFormat.BC3UNorm or DXGIFormat.BC3UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.BC3, _left.MipMaps > 1); - AddChangeTask(_left.Path); + InvokeChange(Mod, _left.Path); AddReloadTask(_left.Path, false); } @@ -187,7 +180,7 @@ public partial class ModEditWindow || _left.Format is DXGIFormat.B8G8R8A8UNorm or DXGIFormat.B8G8R8A8Typeless or DXGIFormat.B8G8R8A8UNormSRGB)) { _center.SaveAsTex(_textures, _left.Path, CombinedTexture.TextureSaveType.Bitmap, _left.MipMaps > 1); - AddChangeTask(_left.Path); + InvokeChange(Mod, _left.Path); AddReloadTask(_left.Path, false); } } @@ -242,7 +235,7 @@ public partial class ModEditWindow if (a) { _center.SaveAs(null, _textures, b, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); - AddChangeTask(b); + InvokeChange(Mod, b); if (b == _left.Path) AddReloadTask(_left.Path, false); else if (b == _right.Path) @@ -252,17 +245,6 @@ public partial class ModEditWindow _forceTextureStartPath = false; } - private void AddChangeTask(string path) - { - _center.SaveTask.ContinueWith(t => - { - if (!t.IsCompletedSuccessfully) - return; - - _framework.RunOnFrameworkThread(() => InvokeChange(Mod, path)); - }, TaskScheduler.Default); - } - private void AddReloadTask(string path, bool right) { _center.SaveTask.ContinueWith(t => diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 5a0fb849..1a4065bb 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -4,14 +4,11 @@ using Dalamud.Interface.DragDrop; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; -using OtterGui.Log; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; -using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; using Penumbra.Communication; @@ -51,12 +48,11 @@ public partial class ModEditWindow : Window, IDisposable, IUiService private readonly IDragDropManager _dragDropManager; private readonly IDataManager _gameData; private readonly IFramework _framework; - private readonly OptionSelectCombo _optionSelect; private Vector2 _iconSize = Vector2.Zero; private bool _allowReduplicate; - public Mod? Mod { get; private set; } + public Mod? Mod { get; private set; } public bool IsLoading @@ -105,7 +101,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _modelTab.Reset(); _materialTab.Reset(); _shaderPackageTab.Reset(); - _itemSwapTab.UpdateMod(mod, _activeCollections.Current.GetInheritedSettings(mod.Index).Settings); + _itemSwapTab.UpdateMod(mod, _activeCollections.Current[mod.Index].Settings); UpdateModels(); _forceTextureStartPath = true; }); @@ -211,7 +207,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService if (IsLoading) { var radius = 100 * ImUtf8.GlobalScale; - var thickness = (int)(20 * 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)); @@ -219,7 +215,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService return; } - using var tabBar = ImUtf8.TabBar("##tabs"u8); + using var tabBar = ImRaii.TabBar("##tabs"); if (!tabBar) return; @@ -234,7 +230,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _materialTab.Draw(); DrawTextureTab(); _shaderPackageTab.Draw(); - using (var tab = ImUtf8.TabItem("Item Swap"u8)) + using (var tab = ImRaii.TabItem("Item Swap")) { if (tab) _itemSwapTab.DrawContent(); @@ -456,10 +452,11 @@ public partial class ModEditWindow : Window, IDisposable, IUiService private bool DrawOptionSelectHeader() { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero).Push(ImGuiStyleVar.FrameRounding, 0); - var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0); - var ret = false; - if (ImUtf8.ButtonEx("Default Option"u8, "Switch to the default option for the mod.\nThis resets unsaved changes."u8, width, + const string defaultOption = "Default Option"; + using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero).Push(ImGuiStyleVar.FrameRounding, 0); + var width = new Vector2(ImGui.GetContentRegionAvail().X / 3, 0); + var ret = false; + 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).Wait(); @@ -467,18 +464,29 @@ public partial class ModEditWindow : Window, IDisposable, IUiService } ImGui.SameLine(); - if (ImUtf8.ButtonEx("Refresh Data"u8, "Refresh data for the current option.\nThis resets unsaved changes."u8, width)) + 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).Wait(); ret = true; } ImGui.SameLine(); - if (_optionSelect.Draw(width.X)) + ImGui.SetNextItemWidth(width.X); + style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); + using var color = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()); + using var combo = ImRaii.Combo("##optionSelector", _editor.Option!.GetFullName()); + if (!combo) + return ret; + + foreach (var (option, idx) in Mod!.AllDataContainers.WithIndex()) { - var (groupIdx, dataIdx) = _optionSelect.CurrentSelection.Index; - _editor.LoadOption(groupIdx, dataIdx).Wait(); - ret = true; + using var id = ImRaii.PushId(idx); + if (ImGui.Selectable(option.GetFullName(), option == _editor.Option)) + { + var (groupIdx, dataIdx) = option.GetDataIndices(); + _editor.LoadOption(groupIdx, dataIdx).Wait(); + ret = true; + } } return ret; @@ -649,7 +657,6 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _fileDialog = fileDialog; _framework = framework; _metaDrawers = metaDrawers; - _optionSelect = new OptionSelectCombo(editor, this); _materialTab = new FileEditor(this, _communicator, gameData, config, _editor.Compactor, _fileDialog, "Materials", ".mtrl", () => PopulateIsOnPlayer(_editor.Files.Mtrl, ResourceType.Mtrl), DrawMaterialPanel, () => Mod?.ModPath.FullName ?? string.Empty, (bytes, path, writable) => mtrlTabFactory.Create(this, new MtrlFile(bytes), path, writable)); @@ -667,7 +674,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _center = new CombinedTexture(_left, _right); _textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex)); _resourceTreeFactory = resourceTreeFactory; - _quickImportViewer = resourceTreeViewerFactory.Create(1, OnQuickImportRefresh, DrawQuickImportActions); + _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) diff --git a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs index bf16fa37..bd62089f 100644 --- a/Penumbra/UI/AdvancedWindow/ModMergeTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModMergeTab.cs @@ -1,7 +1,6 @@ using Dalamud.Interface.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/AdvancedWindow/OptionSelectCombo.cs b/Penumbra/UI/AdvancedWindow/OptionSelectCombo.cs deleted file mode 100644 index c9996a1e..00000000 --- a/Penumbra/UI/AdvancedWindow/OptionSelectCombo.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Dalamud.Bindings.ImGui; -using OtterGui.Raii; -using OtterGui.Text; -using OtterGui.Widgets; -using Penumbra.Mods.Editor; -using Penumbra.UI.Classes; - -namespace Penumbra.UI.AdvancedWindow; - -public sealed class OptionSelectCombo(ModEditor editor, ModEditWindow window) - : FilterComboCache<(string FullName, (int Group, int Data) Index)>( - () => window.Mod!.AllDataContainers.Select(c => (c.GetFullName(), c.GetDataIndices())).ToList(), MouseWheelType.Control, Penumbra.Log) -{ - private ImRaii.ColorStyle _border; - - protected override void DrawCombo(string label, string preview, string tooltip, int currentSelected, float previewWidth, float itemHeight, - ImGuiComboFlags flags) - { - _border = ImRaii.PushFrameBorder(ImUtf8.GlobalScale, ColorId.FolderLine.Value()); - base.DrawCombo(label, preview, tooltip, currentSelected, previewWidth, itemHeight, flags); - _border.Dispose(); - } - - protected override void DrawFilter(int currentSelected, float width) - { - _border.Dispose(); - base.DrawFilter(currentSelected, width); - } - - public bool Draw(float width) - { - var flags = window.Mod!.AllDataContainers.Count() switch - { - 0 => ImGuiComboFlags.NoArrowButton, - > 8 => ImGuiComboFlags.HeightLargest, - _ => ImGuiComboFlags.None, - }; - return Draw("##optionSelector", editor.Option!.GetFullName(), string.Empty, width, ImGui.GetTextLineHeight(), flags); - } - - protected override bool DrawSelectable(int globalIdx, bool selected) - => ImUtf8.Selectable(Items[globalIdx].FullName, selected); -} diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs index ae450bec..3aff2ac9 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs @@ -1,60 +1,59 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; -using Dalamud.Interface.Colors; -using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; -using Dalamud.Plugin.Services; -using Lumina.Data; -using OtterGui; -using OtterGui.Classes; -using OtterGui.Compression; -using OtterGui.Extensions; +using ImGuiNET; using OtterGui.Raii; -using OtterGui.Text; -using Penumbra.Api.Enums; -using Penumbra.GameData.Files; -using Penumbra.GameData.Structs; +using OtterGui; using Penumbra.Interop.ResourceTree; -using Penumbra.Services; -using Penumbra.String; -using Penumbra.String.Classes; using Penumbra.UI.Classes; +using Penumbra.String; namespace Penumbra.UI.AdvancedWindow; -public class ResourceTreeViewer( - Configuration config, - ResourceTreeFactory treeFactory, - ChangedItemDrawer changedItemDrawer, - IncognitoService incognito, - int actionCapacity, - Action onRefresh, - Action drawActions, - CommunicatorService communicator, - PcpService pcpService, - IDataManager gameData, - FileDialogService fileDialog, - FileCompactor compactor) +public class ResourceTreeViewer { private const ResourceTreeFactory.Flags ResourceTreeFactoryFlags = - ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership; + ResourceTreeFactory.Flags.RedactExternalPaths | ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership; - private readonly HashSet _unfolded = []; + private readonly Configuration _config; + private readonly ResourceTreeFactory _treeFactory; + private readonly ChangedItemDrawer _changedItemDrawer; + private readonly IncognitoService _incognito; + private readonly int _actionCapacity; + private readonly Action _onRefresh; + private readonly Action _drawActions; + private readonly HashSet _unfolded; - private readonly Dictionary _filterCache = []; - private readonly Dictionary _writableCache = []; + private readonly Dictionary _filterCache; - private TreeCategory _categoryFilter = AllCategories; - private ChangedItemIconFlag _typeFilter = ChangedItemFlagExtensions.AllFlags; - private string _nameFilter = string.Empty; - private string _nodeFilter = string.Empty; - private string _note = string.Empty; + private TreeCategory _categoryFilter; + private ChangedItemIconFlag _typeFilter; + private string _nameFilter; + private string _nodeFilter; private Task? _task; + public ResourceTreeViewer(Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer, + IncognitoService incognito, int actionCapacity, Action onRefresh, Action drawActions) + { + _config = config; + _treeFactory = treeFactory; + _changedItemDrawer = changedItemDrawer; + _incognito = incognito; + _actionCapacity = actionCapacity; + _onRefresh = onRefresh; + _drawActions = drawActions; + _unfolded = []; + + _filterCache = []; + + _categoryFilter = AllCategories; + _typeFilter = ChangedItemFlagExtensions.AllFlags; + _nameFilter = string.Empty; + _nodeFilter = string.Empty; + } + public void Draw() { - DrawModifiedGameFilesWarning(); DrawControls(); _task ??= RefreshCharacterList(); @@ -75,7 +74,7 @@ public class ResourceTreeViewer( } else if (_task.IsCompletedSuccessfully) { - var debugMode = config.DebugMode; + var debugMode = _config.DebugMode; foreach (var (tree, index) in _task.Result.WithIndex()) { var category = Classify(tree); @@ -84,7 +83,7 @@ public class ResourceTreeViewer( using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value())) { - var isOpen = ImGui.CollapsingHeader($"{(incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", + var isOpen = ImGui.CollapsingHeader($"{(_incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0); if (debugMode) { @@ -99,30 +98,9 @@ public class ResourceTreeViewer( using var id = ImRaii.PushId(index); - ImUtf8.TextFrameAligned($"Collection: {(incognito.IncognitoMode ? tree.AnonymizedCollectionName : tree.CollectionName)}"); - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Export Character Pack"u8, - "Note that this recomputes the current data of the actor if it still exists, and does not use the cached data."u8)) - { - pcpService.CreatePcp((ObjectIndex)tree.GameObjectIndex, _note).ContinueWith(t => - { + ImGui.TextUnformatted($"Collection: {(_incognito.IncognitoMode ? tree.AnonymizedCollectionName : tree.CollectionName)}"); - var (success, text) = t.Result; - - if (success) - Penumbra.Messager.NotificationMessage($"Created {text}.", NotificationType.Success, false); - else - Penumbra.Messager.NotificationMessage(text, NotificationType.Error, false); - }); - _note = string.Empty; - } - - ImUtf8.SameLineInner(); - ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); - ImUtf8.InputText("##note"u8, ref _note, "Export note..."u8); - - - using var table = ImRaii.Table("##ResourceTree", 4, + using var table = ImRaii.Table("##ResourceTree", _actionCapacity > 0 ? 4 : 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) continue; @@ -130,8 +108,9 @@ public class ResourceTreeViewer( ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthStretch, 0.2f); ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.3f); ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f); - ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, - actionCapacity * 3 * ImGuiHelpers.GlobalScale + (actionCapacity + 1) * ImGui.GetFrameHeight()); + if (_actionCapacity > 0) + ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, + (_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight()); ImGui.TableHeadersRow(); DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31), 0); @@ -139,24 +118,6 @@ public class ResourceTreeViewer( } } - private void DrawModifiedGameFilesWarning() - { - if (!gameData.HasModifiedGameDataFiles) - return; - - using var style = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudOrange); - - ImUtf8.TextWrapped( - "Dalamud is reporting your FFXIV installation has modified game files. Any mods installed through TexTools will produce this message."u8); - ImUtf8.TextWrapped("Penumbra and some other plugins assume your FFXIV installation is unmodified in order to work."u8); - ImUtf8.TextWrapped( - "Data displayed here may be inaccurate because of this, which, in turn, can break functionality relying on it, such as Character Pack exports/imports, or mod synchronization functions provided by other plugins."u8); - ImUtf8.TextWrapped( - "Exit the game, open XIVLauncher, click the arrow next to Log In and select \"repair game files\" to resolve this issue. Afterwards, do not install any mods with TexTools. Your plugin configurations will remain, as will mods enabled in Penumbra."u8); - - ImGui.Separator(); - } - private void DrawControls() { var yOffset = (ChangedItemDrawer.TypeFilterIconSize.Y - ImGui.GetFrameHeight()) / 2f; @@ -189,7 +150,7 @@ public class ResourceTreeViewer( ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset); using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y))) { - filterChanged |= changedItemDrawer.DrawTypeFilter(ref _typeFilter); + filterChanged |= _changedItemDrawer.DrawTypeFilter(ref _typeFilter); } var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f; @@ -199,7 +160,7 @@ public class ResourceTreeViewer( ImGui.SetNextItemWidth(fieldWidth); filterChanged |= ImGui.InputTextWithHint("##NodeFilter", "Filter by Item/Part Name or Path...", ref _nodeFilter, 128); ImGui.SameLine(0, checkSpacing); - incognito.DrawToggle(ImGui.GetFrameHeightWithSpacing()); + _incognito.DrawToggle(ImGui.GetFrameHeightWithSpacing()); if (filterChanged) _filterCache.Clear(); @@ -210,24 +171,24 @@ public class ResourceTreeViewer( { try { - return treeFactory.FromObjectTable(ResourceTreeFactoryFlags) + return _treeFactory.FromObjectTable(ResourceTreeFactoryFlags) .Select(entry => entry.ResourceTree) .ToArray(); } finally { _filterCache.Clear(); - _writableCache.Clear(); _unfolded.Clear(); - onRefresh(); + _onRefresh(); } }); private void DrawNodes(IEnumerable resourceNodes, int level, nint pathHash, ChangedItemIconFlag parentFilterIconFlag) { - var debugMode = config.DebugMode; + var debugMode = _config.DebugMode; var frameHeight = ImGui.GetFrameHeight(); + var cellHeight = _actionCapacity > 0 ? frameHeight : 0.0f; foreach (var (resourceNode, index) in resourceNodes.WithIndex()) { @@ -270,7 +231,7 @@ public class ResourceTreeViewer( ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X); } - changedItemDrawer.DrawCategoryIcon(resourceNode.IconFlag); + _changedItemDrawer.DrawCategoryIcon(resourceNode.IconFlag); ImGui.SameLine(0f, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.TableHeader(resourceNode.Name); if (ImGui.IsItemClicked() && unfoldable) @@ -297,7 +258,7 @@ public class ResourceTreeViewer( 0 => "(none)", 1 => resourceNode.GamePath.ToString(), _ => "(multiple)", - }, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + }, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); if (hasGamePaths) { var allPaths = string.Join('\n', resourceNode.PossibleGamePaths); @@ -309,62 +270,32 @@ public class ResourceTreeViewer( ImGui.TableNextColumn(); if (resourceNode.FullPath.FullName.Length > 0) { - var hasMod = resourceNode.Mod.TryGetTarget(out var mod); - if (resourceNode is { ModName: not null, ModRelativePath: not null }) - { - var modName = $"[{(hasMod ? mod!.Name : resourceNode.ModName)}]"; - var textPos = ImGui.GetCursorPosX() + ImUtf8.CalcTextSize(modName).X + ImGui.GetStyle().ItemInnerSpacing.X; - using var group = ImUtf8.Group(); - using (var color = ImRaii.PushColor(ImGuiCol.Text, (hasMod ? ColorId.NewMod : ColorId.DisabledMod).Value())) - { - ImUtf8.Selectable(modName, false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); - } - - ImGui.SameLine(); - ImGui.SetCursorPosX(textPos); - ImUtf8.Text(resourceNode.ModRelativePath); - } - else if (resourceNode.FullPath.IsRooted) - { - var path = resourceNode.FullPath.FullName; - var lastDirectorySeparator = path.LastIndexOf('\\'); - var secondLastDirectorySeparator = lastDirectorySeparator > 0 - ? path.LastIndexOf('\\', lastDirectorySeparator - 1) - : -1; - if (secondLastDirectorySeparator >= 0) - path = $"…{path.AsSpan(secondLastDirectorySeparator)}"; - ImGui.Selectable(path.AsSpan(), false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); - } - else - { - ImGui.Selectable(resourceNode.FullPath.ToPath(), false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); - } - + var uiFullPathStr = resourceNode.ModName != null && resourceNode.ModRelativePath != null + ? $"[{resourceNode.ModName}] {resourceNode.ModRelativePath}" + : resourceNode.FullPath.ToPath(); + ImGui.Selectable(uiFullPathStr, false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); if (ImGui.IsItemClicked()) ImGui.SetClipboardText(resourceNode.FullPath.ToPath()); - if (hasMod && ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl) - communicator.SelectTab.Invoke(TabType.Mods, mod); - ImGuiUtil.HoverTooltip( - $"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{(hasMod ? "\nControl + Right-Click to jump to mod." : string.Empty)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); + $"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); } else { - ImUtf8.Selectable(GetPathStatusLabel(resourceNode.FullPathStatus), false, ImGuiSelectableFlags.Disabled, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled, + new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); ImGuiUtil.HoverTooltip( - $"{GetPathStatusDescription(resourceNode.FullPathStatus)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); + $"The actual path to this file is unavailable.\nIt may be managed by another plug-in.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); } mutedColor.Dispose(); - ImGui.TableNextColumn(); - using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale }); - DrawActions(resourceNode, new Vector2(frameHeight)); + if (_actionCapacity > 0) + { + ImGui.TableNextColumn(); + using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, + ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale }); + _drawActions(resourceNode, new Vector2(frameHeight)); + } if (unfolded) DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31), filterIcon); @@ -417,70 +348,8 @@ public class ResourceTreeViewer( || node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) || Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)); } - - void DrawActions(ResourceNode resourceNode, Vector2 buttonSize) - { - if (!_writableCache!.TryGetValue(resourceNode.FullPath, out var writable)) - { - var path = resourceNode.FullPath.ToPath(); - if (resourceNode.FullPath.IsRooted) - { - writable = new RawFileWritable(path); - } - else - { - var file = gameData.GetFile(path); - writable = file is null ? null : new RawGameFileWritable(file); - } - - _writableCache.Add(resourceNode.FullPath, writable); - } - - if (ImUtf8.IconButton(FontAwesomeIcon.Save, "Export this file."u8, buttonSize, - resourceNode.FullPath.FullName.Length is 0 || writable is null)) - { - var fullPathStr = resourceNode.FullPath.FullName; - var ext = resourceNode.PossibleGamePaths.Length == 1 - ? Path.GetExtension(resourceNode.GamePath.ToString()) - : Path.GetExtension(fullPathStr); - fileDialog.OpenSavePicker($"Export {Path.GetFileName(fullPathStr)} to...", ext, Path.GetFileNameWithoutExtension(fullPathStr), ext, - (success, name) => - { - if (!success) - return; - - try - { - compactor.WriteAllBytes(name, writable!.Write()); - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not export {fullPathStr}:\n{e}"); - } - }, null, false); - } - - drawActions(resourceNode, writable, new Vector2(frameHeight)); - } } - private static ReadOnlySpan GetPathStatusLabel(ResourceNode.PathStatus status) - => status switch - { - ResourceNode.PathStatus.External => "(managed by external tools)"u8, - ResourceNode.PathStatus.NonExistent => "(not found)"u8, - _ => "(unavailable)"u8, - }; - - private static string GetPathStatusDescription(ResourceNode.PathStatus status) - => status switch - { - ResourceNode.PathStatus.External => "The actual path to this file is unavailable, because it is managed by external tools.", - ResourceNode.PathStatus.NonExistent => - "The actual path to this file is unavailable, because it seems to have been moved or deleted since it was loaded.", - _ => "The actual path to this file is unavailable.", - }; - [Flags] private enum TreeCategory : uint { @@ -525,22 +394,4 @@ public class ResourceTreeViewer( Visible = 1, DescendentsOnly = 2, } - - private record RawFileWritable(string Path) : IWritable - { - public bool Valid - => true; - - public byte[] Write() - => File.ReadAllBytes(Path); - } - - private record RawGameFileWritable(FileResource FileResource) : IWritable - { - public bool Valid - => true; - - public byte[] Write() - => FileResource.Data; - } } diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs index 6518ae67..ea64c0bf 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs @@ -1,9 +1,5 @@ -using Dalamud.Plugin.Services; -using OtterGui.Compression; using OtterGui.Services; -using Penumbra.GameData.Files; using Penumbra.Interop.ResourceTree; -using Penumbra.Services; namespace Penumbra.UI.AdvancedWindow; @@ -11,14 +7,8 @@ public class ResourceTreeViewerFactory( Configuration config, ResourceTreeFactory treeFactory, ChangedItemDrawer changedItemDrawer, - IncognitoService incognito, - CommunicatorService communicator, - PcpService pcpService, - IDataManager gameData, - FileDialogService fileDialog, - FileCompactor compactor) : IService + IncognitoService incognito) : IService { - public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action drawActions) - => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator, pcpService, gameData, - fileDialog, compactor); + public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action drawActions) + => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions); } diff --git a/Penumbra/UI/ChangedItemDrawer.cs b/Penumbra/UI/ChangedItemDrawer.cs index db54a8e5..af9782d5 100644 --- a/Penumbra/UI/ChangedItemDrawer.cs +++ b/Penumbra/UI/ChangedItemDrawer.cs @@ -3,13 +3,12 @@ using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin.Services; using Dalamud.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using Lumina.Data.Files; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.GameData.Data; using Penumbra.Services; @@ -87,80 +86,78 @@ public class ChangedItemDrawer : IDisposable, IUiService } /// Check if a changed item should be drawn based on its category. - public bool FilterChangedItem(string name, IIdentifiedObjectData data, LowerString filter) + public bool FilterChangedItem(string name, IIdentifiedObjectData? data, LowerString filter) => (_config.Ephemeral.ChangedItemFilter == ChangedItemFlagExtensions.AllFlags || _config.Ephemeral.ChangedItemFilter.HasFlag(data.GetIcon().ToFlag())) && (filter.IsEmpty || !data.IsFilteredOut(name, filter)); /// Draw the icon corresponding to the category of a changed item. - public void DrawCategoryIcon(IIdentifiedObjectData data, float height) - => DrawCategoryIcon(data.GetIcon().ToFlag(), height); + public void DrawCategoryIcon(IIdentifiedObjectData? data) + => DrawCategoryIcon(data.GetIcon().ToFlag()); public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType) - => DrawCategoryIcon(iconFlagType, ImGui.GetFrameHeight()); - - public void DrawCategoryIcon(ChangedItemIconFlag iconFlagType, float height) { + var height = ImGui.GetFrameHeight(); if (!_icons.TryGetValue(iconFlagType, out var icon)) { ImGui.Dummy(new Vector2(height)); return; } - ImGui.Image(icon.Handle, new Vector2(height)); + ImGui.Image(icon.ImGuiHandle, new Vector2(height)); if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); - ImGui.Image(icon.Handle, new Vector2(_smallestIconWidth)); + ImGui.Image(icon.ImGuiHandle, new Vector2(_smallestIconWidth)); ImGui.SameLine(); ImGuiUtil.DrawTextButton(iconFlagType.ToDescription(), new Vector2(0, _smallestIconWidth), 0); } } - public void ChangedItemHandling(IIdentifiedObjectData data, bool leftClicked) + /// + /// Draw a changed item, invoking the Api-Events for clicks and tooltips. + /// Also draw the item ID in grey if requested. + /// + public void DrawChangedItem(string name, IIdentifiedObjectData? data) { - var ret = leftClicked ? MouseButton.Left : MouseButton.None; - ret = ImGui.IsItemClicked(ImGuiMouseButton.Right) ? MouseButton.Right : ret; - ret = ImGui.IsItemClicked(ImGuiMouseButton.Middle) ? MouseButton.Middle : ret; - if (ret != MouseButton.None) - _communicator.ChangedItemClick.Invoke(ret, data); - if (!ImGui.IsItemHovered()) - return; + name = data?.ToName(name) ?? name; + using (ImRaii.PushStyle(ImGuiStyleVar.SelectableTextAlign, new Vector2(0, 0.5f)) + .Push(ImGuiStyleVar.ItemSpacing, new Vector2(ImGui.GetStyle().ItemSpacing.X, ImGui.GetStyle().CellPadding.Y * 2))) + { + var ret = ImGui.Selectable(name, false, ImGuiSelectableFlags.None, new Vector2(0, ImGui.GetFrameHeight())) + ? MouseButton.Left + : MouseButton.None; + ret = ImGui.IsItemClicked(ImGuiMouseButton.Right) ? MouseButton.Right : ret; + ret = ImGui.IsItemClicked(ImGuiMouseButton.Middle) ? MouseButton.Middle : ret; + if (ret != MouseButton.None) + _communicator.ChangedItemClick.Invoke(ret, data); + } - using var tt = ImUtf8.Tooltip(); - if (data.Count == 1) - ImUtf8.Text("This item is changed through a single effective change.\n"); - else - ImUtf8.Text($"This item is changed through {data.Count} distinct effective changes.\n"); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3 * ImUtf8.GlobalScale); - ImGui.Separator(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3 * ImUtf8.GlobalScale); - _communicator.ChangedItemHover.Invoke(data); + if (_communicator.ChangedItemHover.HasTooltip && ImGui.IsItemHovered()) + { + // We can not be sure that any subscriber actually prints something in any case. + // Circumvent ugly blank tooltip with less-ugly useless tooltip. + using var tt = ImRaii.Tooltip(); + using (ImRaii.Group()) + { + _communicator.ChangedItemHover.Invoke(data); + } + + if (ImGui.GetItemRectSize() == Vector2.Zero) + ImGui.TextUnformatted("No actions available."); + } } /// Draw the model information, right-justified. - public static void DrawModelData(IIdentifiedObjectData data, float height) + public static void DrawModelData(IIdentifiedObjectData? data) { - var additionalData = data.AdditionalData; + var additionalData = data?.AdditionalData ?? string.Empty; if (additionalData.Length == 0) return; - ImGui.SameLine(); - using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (height - ImGui.GetTextLineHeight()) / 2); - ImUtf8.TextRightAligned(additionalData, ImGui.GetStyle().ItemInnerSpacing.X); - } - - /// Draw the model information, right-justified. - public static void DrawModelData(ReadOnlySpan text, float height) - { - if (text.Length == 0) - return; - - ImGui.SameLine(); - using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (height - ImGui.GetTextLineHeight()) / 2); - ImUtf8.TextRightAligned(text, ImGui.GetStyle().ItemInnerSpacing.X); + ImGui.SameLine(ImGui.GetContentRegionAvail().X); + ImGui.AlignTextToFramePadding(); + ImGuiUtil.RightJustify(additionalData, ColorId.ItemId.Value()); } /// Draw a header line with the different icon types to filter them. @@ -193,7 +190,7 @@ public class ChangedItemDrawer : IDisposable, IUiService } ImGui.SetCursorPosX(ImGui.GetContentRegionMax().X - size.X); - ImGui.Image(_icons[ChangedItemFlagExtensions.AllFlags].Handle, size, Vector2.Zero, Vector2.One, + ImGui.Image(_icons[ChangedItemFlagExtensions.AllFlags].ImGuiHandle, size, Vector2.Zero, Vector2.One, typeFilter switch { 0 => new Vector4(0.6f, 0.3f, 0.3f, 1f), @@ -213,7 +210,7 @@ public class ChangedItemDrawer : IDisposable, IUiService var localRet = false; var icon = _icons[type]; var flag = typeFilter.HasFlag(type); - ImGui.Image(icon.Handle, size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f)); + ImGui.Image(icon.ImGuiHandle, size, Vector2.Zero, Vector2.One, flag ? Vector4.One : new Vector4(0.6f, 0.3f, 0.3f, 1f)); if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { typeFilter = flag ? typeFilter & ~type : typeFilter | type; @@ -232,7 +229,7 @@ public class ChangedItemDrawer : IDisposable, IUiService if (ImGui.IsItemHovered()) { using var tt = ImRaii.Tooltip(); - ImGui.Image(icon.Handle, new Vector2(_smallestIconWidth)); + ImGui.Image(icon.ImGuiHandle, new Vector2(_smallestIconWidth)); ImGui.SameLine(); ImGuiUtil.DrawTextButton(type.ToDescription(), new Vector2(0, _smallestIconWidth), 0); } @@ -279,7 +276,7 @@ public class ChangedItemDrawer : IDisposable, IUiService return true; } - private static IDalamudTextureWrap? LoadUnknownTexture(IDataManager gameData, ITextureProvider textureProvider) + private static unsafe IDalamudTextureWrap? LoadUnknownTexture(IDataManager gameData, ITextureProvider textureProvider) { var unk = gameData.GetFile("ui/uld/levelup2_hr1.tex"); if (unk == null) diff --git a/Penumbra/UI/Changelog.cs b/Penumbra/UI/Changelog.cs index 306dcc79..0b0ca81a 100644 --- a/Penumbra/UI/Changelog.cs +++ b/Penumbra/UI/Changelog.cs @@ -55,287 +55,46 @@ public class PenumbraChangelog : IUiService Add1_2_1_0(Changelog); Add1_3_0_0(Changelog); Add1_3_1_0(Changelog); - Add1_3_2_0(Changelog); - Add1_3_3_0(Changelog); - Add1_3_4_0(Changelog); - Add1_3_5_0(Changelog); - Add1_3_6_0(Changelog); - Add1_3_6_4(Changelog); - Add1_4_0_0(Changelog); - Add1_5_0_0(Changelog); - Add1_5_1_0(Changelog); - } - + } + #region Changelogs - private static void Add1_5_1_0(Changelog log) - => log.NextVersion("Version 1.5.1.0") - .RegisterHighlight("Added the option to export a characters current data as a .pcp modpack in the On-Screen tab.") - .RegisterEntry("Other plugins can attach to this functionality and package and interpret their own data.", 1) - .RegisterEntry("When a .pcp modpack is installed, it can create and assign collections for the corresponding character it was created for.", 1) - .RegisterEntry("This basically provides an easier way to manually synchronize other players, but does not contain any automation.", 1) - .RegisterEntry("The settings provide some fine control about what happens when a PCP is installed, as well as buttons to cleanup any PCP-created data.", 1) - .RegisterEntry("Added a warning message when the game's integrity is corrupted to the On-Screen tab.") - .RegisterEntry("Added .kdb files to the On-Screen tab and associated functionality (thanks Ny!).") - .RegisterEntry("Updated the creation of temporary collections to require a passed identity.") - .RegisterEntry("Added the option to change the skin material suffix in models using the stockings shader by adding specific attributes (thanks Ny!).") - .RegisterEntry("Added predefined tag utility to the multi-mod selection.") - .RegisterEntry("Fixed an issue with the automatic collection selection on character login when no mods are assigned.") - .RegisterImportant( - "Fixed issue with new deformer data that makes modded deformers not containing this data work implicitly. Updates are still recommended (1.5.0.5).") - .RegisterEntry("Fixed various issues after patch (1.5.0.1 - 1.5.0.4)."); - - private static void Add1_5_0_0(Changelog log) - => log.NextVersion("Version 1.5.0.0") - .RegisterImportant("Updated for game version 7.30 and Dalamud API13, which uses a new GUI backend. Some things may not work as expected. Please let me know any issues you encounter.") - .RegisterEntry("Added support for exporting models using two vertex color schemes (thanks zeroeightysix!).") - .RegisterEntry("Possibly improved the color accuracy of the basecolor texture created when exporting models (thanks zeroeightysix!).") - .RegisterEntry("Disabled enabling transparency for materials that use the characterstockings shader due to crashes (thanks zeroeightysix!).") - .RegisterEntry("Fixed some issues with model i/o and invalid tangents (thanks PassiveModding!)") - .RegisterEntry("Changed the behavior for default directory names when using the mod normalizer with combining groups.") - .RegisterEntry("Added jumping to specific mods to the HTTP API.") - .RegisterEntry("Fixed an issue with character sound modding (1.4.0.6).") - .RegisterHighlight("Added support for IMC-toggle attributes to accessories beyond the first toggle (1.4.0.5).") - .RegisterEntry("Fixed up some slot-specific attributes and shapes in models when swapping items between slots (1.4.0.5).") - .RegisterEntry("Added handling for human skin materials to the OnScreen tab and similar functionality (thanks Ny!) (1.4.0.5).") - .RegisterEntry("The OS thread ID a resource was loaded from was added to the resource logger (1.4.0.5).") - .RegisterEntry("A button linking to my (Ottermandias') Ko-Fi and Patreon was added in the settings tab. Feel free, but not pressured, to use it! :D ") - .RegisterHighlight("Mod setting combos now support mouse-wheel scrolling with Control and have filters (1.4.0.4).") - .RegisterEntry("Using the middle mouse button to toggle designs now works correctly with temporary settings (1.4.0.4).") - .RegisterEntry("Updated some BNPC associations (1.4.0.3).") - .RegisterEntry("Fixed further issues with shapes and attributes (1.4.0.4).") - .RegisterEntry("Penumbra now handles textures with MipMap offsets broken by TexTools on import and removes unnecessary MipMaps (1.4.0.3).") - .RegisterEntry("Updated the Mod Merger for the new group types (1.4.0.3).") - .RegisterEntry("Added querying Penumbra for supported features via IPC (1.4.0.3).") - .RegisterEntry("Shape names can now be edited in Penumbras model editor (1.4.0.2).") - .RegisterEntry("Attributes and Shapes can be fully toggled (1.4.0.2).") - .RegisterEntry("Fixed several issues with attributes and shapes (1.4.0.1)."); - - private static void Add1_4_0_0(Changelog log) - => log.NextVersion("Version 1.4.0.0") - .RegisterHighlight("Added two types of new Meta Changes, SHP and ATR (Thanks Karou!).") - .RegisterEntry("Those allow mod creators to toggle custom shape keys and attributes for models on and off, respectively.", 1) - .RegisterEntry("Custom shape keys need to have the format 'shpx_*' and custom attributes need 'atrx_*'.", 1) - .RegisterHighlight( - "Shapes of the following formats will automatically be toggled on if both relevant slots contain the same shape key:", 1) - .RegisterEntry("'shpx_wa_*', for the waist seam between the body and leg slot,", 2) - .RegisterEntry("'shpx_wr_*', for the wrist seams between the body and hands slot,", 2) - .RegisterEntry("'shpx_an_*', for the ankle seams between the leg and feet slot.", 2) - .RegisterEntry( - "Custom shape key and attributes can be turned off in the advanced settings section for the moment, but this is not recommended.", - 1) - .RegisterHighlight("The mod selector width is now draggable within certain restrictions that depend on the total window width.") - .RegisterEntry("The current behavior may not be final, let me know if you have any comments.", 1) - .RegisterEntry("Improved the naming of NPCs for identifiers by using Haselnussbombers new naming functionality (Thanks Hasel!).") - .RegisterEntry("Added global EQP entries to always hide Au Ra horns, Viera ears, or Miqo'te ears, respectively.") - .RegisterEntry("This will leave holes in the heads of the respective race if not modded in some way.", 1) - .RegisterEntry("Added a filter for mods that have temporary settings in the mod selector panel (Thanks Caraxi).") - .RegisterEntry("Made the checkbox for toggling Temporary Settings Mode in the mod tab more visible.") - .RegisterEntry("Improved the option select combo in advanced editing.") - .RegisterEntry("Fixed some issues with item identification for EST changes.") - .RegisterEntry("Fixed the sizing of the mod panel being off by 1 pixel sometimes.") - .RegisterEntry("Fixed an issue with redrawing while in GPose when other plugins broke some assumptions about the game state.") - .RegisterEntry("Fixed a clipping issue within the Meta Manipulations tab in advanced editing.") - .RegisterEntry("Fixed an issue with empty and temporary settings.") - .RegisterHighlight( - "In the Item Swap tab, items changed by this mod are now sorted and highlighted before items changed in the current collection before other items for the source, and inversely for the target. (1.3.6.8)") - .RegisterHighlight( - "Default-valued meta edits should now be kept on import and only removed when the option to keep them is not set AND no other options in the mod edit the same entry. (1.3.6.8)") - .RegisterEntry("Added a right-click context menu on file redirections to copy the full file path. (1.3.6.8)") - .RegisterEntry( - "Added a right-click context menu on the mod export button to open the backup directory in your file explorer. (1.3.6.8)") - .RegisterEntry("Fixed some issues when redrawing characters from other plugins. (1.3.6.8)") - .RegisterEntry( - "Added a modifier key separate from the delete modifier key that is used for less important key-checks, specifically toggling incognito mode. (1.3.6.7)") - .RegisterEntry("Fixed some issues with the Material Editor (Thanks Ny). (1.3.6.6)"); - - private static void Add1_3_6_4(Changelog log) - => log.NextVersion("Version 1.3.6.4") - .RegisterEntry("The material editor should be functional again."); - - private static void Add1_3_6_0(Changelog log) - => log.NextVersion("Version 1.3.6.0") - .RegisterImportant("Updated Penumbra for update 7.20 and Dalamud API 12.") - .RegisterEntry( - "This is not thoroughly tested, but I decided to push to stable instead of testing because otherwise a lot of people would just go to testing just for early access again despite having no business doing so.", - 1) - .RegisterEntry( - "I also do not use most of the functionality of Penumbra myself, so I am unable to even encounter most issues myself.", 1) - .RegisterEntry("If you encounter any issues, please report them quickly on the discord.", 1) - .RegisterHighlight( - "The texture editor now has encoding support for Block Compression 1, 4 and 5 and tooltips explaining when to use which format.") - .RegisterEntry("It also is able to use GPU compression and thus has become much faster for BC7 in particular. (Thanks Ny!)", 1) - .RegisterEntry( - "Added the option to import .atch files found in the particular mod via right-click context menu on the import drag & drop button.") - .RegisterEntry("Added a chat command to clear temporary settings done manually in Penumbra.") - .RegisterEntry( - "The changed item star to select the preferred changed item is a bit more noticeable by default, and its color can be configured.") - .RegisterEntry("Some minor fixes for computing changed items. (Thanks Anna!)") - .RegisterEntry("The EQP entry previously named Unknown 4 was renamed to 'Hide Glove Cuffs'.") - .RegisterEntry("Fixed the changed item identification for EST changes.") - .RegisterEntry("Fixed clipping issues in the changed items panel when no grouping was active."); - - - private static void Add1_3_5_0(Changelog log) - => log.NextVersion("Version 1.3.5.0") - .RegisterImportant( - "Redirections of unsupported file types like .atch will now produce warnings when they are enabled. Please update mods still containing them or request updates from their creators.") - .RegisterEntry("You can now import .atch in the Meta section of advanced editing to add their non-default changes to the mod.") - .RegisterHighlight("Added an option in settings and in the collection bar in the mod tab to always use temporary settings.") - .RegisterEntry( - "While this option is enabled, all changes you make in the current collection will be applied as temporary changes, and you have to use Turn Permanent to make them permanent.", - 1) - .RegisterEntry( - "This should be useful for trying out new mods without needing to reset their settings later, or for creating mod associations in Glamourer from them.", - 1) - .RegisterEntry( - "Added a context menu entry on the mod selector blank-space context menu to clear all temporary settings made manually.") - .RegisterHighlight( - "Resource Trees now consider some additional files like decals, and improved the quick-import behaviour for some files that should not generally be modded.") - .RegisterHighlight("The Changed Item display for single mods has been heavily improved.") - .RegisterEntry("Any changed item will now show how many individual edits are affecting it in the mod in its tooltip.", 1) - .RegisterEntry("Equipment pieces are now grouped by their model id, reducing clutter.", 1) - .RegisterEntry( - "The primary equipment piece displayed is the one with the most changes affecting it, but can be configured to a specific item by the mod creator and locally.", - 1) - .RegisterEntry( - "Preferred changed items stored in the mod will be shared when exporting the mod, and used as the default for local preferences, which will not be shared.", - 2) - .RegisterEntry( - "You can configure whether groups are automatically collapsed or expanded, or remove grouping entirely in the settings.", 1) - .RegisterHighlight("Fixed support for model import/export with more than one UV.") - .RegisterEntry("Added some IPC relating to changed items.") - .RegisterEntry("Skeleton and Physics changes should now be identified in Changed Items.") - .RegisterEntry("Item Swaps will now also correctly swap EQP entries of multi-slot pieces.") - .RegisterEntry("Meta edit transmission through IPC should be a lot more efficient than before.") - .RegisterEntry("Fixed an issue with incognito names in some cutscenes.") - .RegisterEntry("Newly extracted mod folders will now try to rename themselves three times before being considered a failure."); - - private static void Add1_3_4_0(Changelog log) - => log.NextVersion("Version 1.3.4.0") - .RegisterHighlight( - "Added HDR functionality to diffuse buffers. This allows more accurate representation of non-standard color values for e.g. skin or hair colors when used with advanced customizations in Glamourer.") - .RegisterEntry( - "This option requires Wait For Plugins On Load to be enabled in Dalamud and to be enabled on start to work. It is on by default but can be turned off.", - 1) - .RegisterHighlight("Added a new option group type: Combining Groups.") - .RegisterEntry( - "A combining group behaves similarly to a multi group for the user, but instead of enabling the different options separately, it results in exactly one option per choice of settings.", - 1) - .RegisterEntry( - "Example: The user sees 2 checkboxes [+25%, +50%], but the 4 different selection states result in +0%, +25%, +50% or +75% if both are toggled on. Every choice of settings can be configured separately by the mod creator.", - 1) - .RegisterEntry( - "Added new functionality to better track copies of the player character in cutscenes if they get forced to specific clothing, like in the Margrat cutscene. Might improve tracking in wedding ceremonies, too, let me know.") - .RegisterEntry("Added a display of the number of selected files and folders to the multi mod selection.") - .RegisterEntry( - "Added cleaning functionality to remove outdated or unused files or backups from the config and mod folders via manual action.") - .RegisterEntry("Updated the Bone and Material limits in the Model Importer.") - .RegisterEntry("Improved handling of IMC and Material files loaded asynchronously.") - .RegisterEntry("Added IPC functionality to query temporary settings.") - .RegisterEntry("Improved some mod setting IPC functions.") - .RegisterEntry("Fixed some path detection issues in the OnScreen tab.") - .RegisterEntry("Fixed some issues with temporary mod settings.") - .RegisterEntry("Fixed issues with IPC calls before the game has finished loading.") - .RegisterEntry("Fixed using the wrong dye channel in the material editor previews.") - .RegisterEntry("Added some log warnings if outdated materials are loaded by the game.") - .RegisterEntry("Added Schemas for some of the json files generated and read by Penumbra to the solution."); - - private static void Add1_3_3_0(Changelog log) - => log.NextVersion("Version 1.3.3.0") - .RegisterHighlight("Added Temporary Settings to collections.") - .RegisterEntry( - "Settings can be manually turned temporary (and turned back) while editing mod settings via right-click context on the mod or buttons in the settings panel.", - 1) - .RegisterEntry( - "This can be used to test mods or changes without saving those changes permanently or having to reinstate the old settings afterwards.", - 1) - .RegisterEntry( - "More importantly, this can be set via IPC by other plugins, allowing Glamourer to only set and reset temporary settings when applying Mod Associations.", - 1) - .RegisterEntry( - "As an extreme example, it would be possible to only enable the consistent mods for your character in the collection, and let Glamourer handle all outfit mods itself via temporary settings only.", - 1) - .RegisterEntry( - "This required some pretty big changes that were in testing for a while now, but nobody talked about it much so it may still have some bugs or usability issues. Let me know!", - 1) - .RegisterHighlight( - "Added an option to automatically select the collection assigned to the current character on login events. This is off by default.") - .RegisterEntry( - "Added partial copying of color tables in material editing via right-click context menu entries on the import buttons.") - .RegisterHighlight( - "Added handling for TMB files cached by the game that should resolve issues of leaky TMBs from animation and VFX mods.") - .RegisterEntry( - "The enabled checkbox, Priority and Inheriting buttons now stick at the top of the Mod Settings panel even when scrolling down for specific settings.") - .RegisterEntry("When creating new mods with Item Swap, the attributed author of the resulting mod was improved.") - .RegisterEntry("Fixed an issue with rings in the On-Screen tab and in the data sent over to other plugins via IPC.") - .RegisterEntry( - "Fixed some issues when writing material files that resulted in technically valid files that still caused some issues with the game for unknown reasons.") - .RegisterEntry("Fixed some ImGui assertions."); - - private static void Add1_3_2_0(Changelog log) - => log.NextVersion("Version 1.3.2.0") - .RegisterHighlight("Added ATCH meta manipulations that allow the composite editing of attachment points across multiple mods.") - .RegisterEntry("Those ATCH manipulations should be shared via Mare Synchronos.", 1) - .RegisterEntry( - "This is an early implementation and might be bug-prone. Let me know of any issues. It was in testing for quite a while without reports.", - 1) - .RegisterEntry( - "Added jumping to identified mods in the On-Screen tab via Control + Right-Click and improved their display slightly.") - .RegisterEntry("Added some right-click context menu copy options in the File Redirections editor for paths.") - .RegisterHighlight("Added the option to change a specific mod's settings via chat commands by using '/penumbra mod settings'.") - .RegisterEntry("Fixed issues with the copy-pasting of meta manipulations.") - .RegisterEntry("Fixed some other issues related to meta manipulations.") - .RegisterEntry( - "Updated available NPC names and fixed an issue with some supposedly invisible characters in names showing in ImGui."); - - private static void Add1_3_1_0(Changelog log) => log.NextVersion("Version 1.3.1.0") .RegisterEntry("Penumbra has been updated for Dalamud API 11 and patch 7.1.") - .RegisterImportant( - "There are some known issues with potential crashes using certain VFX/SFX mods, probably related to sound files.") - .RegisterEntry( - "If you encounter those issues, please report them in the discord and potentially disable the corresponding mods for the time being.", - 1) - .RegisterImportant( - "The modding of .atch files has been disabled. Outdated modded versions of these files cause crashes when loaded.") + .RegisterImportant("There are some known issues with potential crashes using certain VFX/SFX mods, probably related to sound files.") + .RegisterEntry("If you encounter those issues, please report them in the discord and potentially disable the corresponding mods for the time being.", 1) + .RegisterImportant("The modding of .atch files has been disabled. Outdated modded versions of these files cause crashes when loaded.") .RegisterEntry("A better way for modular modding of .atch files via meta changes will release to the testing branch soonish.", 1) .RegisterHighlight("Temporary collections (as created by Mare) will now always respect ownership.") - .RegisterEntry( - "This means that you can toggle this setting off if you do not want it, and Mare will still work for minions and mounts of other players.", - 1) - .RegisterEntry( - "The new physics and animation engine files (.kdb and .bnmb) should now be correctly redirected and respect EST changes.") + .RegisterEntry("This means that you can toggle this setting off if you do not want it, and Mare will still work for minions and mounts of other players.", 1) + .RegisterEntry("The new physics and animation engine files (.kdb and .bnmb) should now be correctly redirected and respect EST changes.") .RegisterEntry("Fixed issues with EQP entries being labeled wrongly and global EQP not changing all required values for earrings.") .RegisterEntry("Fixed an issue with global EQP changes of a mod being reset upon reloading the mod.") .RegisterEntry("Fixed another issue with left rings and mare synchronization / the on-screen tab.") .RegisterEntry("Maybe fixed some issues with characters appearing in the login screen being misidentified.") .RegisterEntry("Some improvements for debug visualization have been made."); - + 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.") + .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("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("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.") diff --git a/Penumbra/UI/Classes/CollectionSelectHeader.cs b/Penumbra/UI/Classes/CollectionSelectHeader.cs index 355a6106..3972e350 100644 --- a/Penumbra/UI/Classes/CollectionSelectHeader.cs +++ b/Penumbra/UI/Classes/CollectionSelectHeader.cs @@ -1,9 +1,7 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using OtterGui; +using ImGuiNET; using OtterGui.Raii; +using OtterGui; using OtterGui.Services; -using OtterGui.Text; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.Interop.PathResolving; @@ -19,17 +17,15 @@ public class CollectionSelectHeader : IUiService private readonly TutorialService _tutorial; private readonly ModSelection _selection; private readonly CollectionResolver _resolver; - private readonly Configuration _config; public CollectionSelectHeader(CollectionManager collectionManager, TutorialService tutorial, ModSelection selection, - CollectionResolver resolver, Configuration config) + CollectionResolver resolver) { _tutorial = tutorial; _selection = selection; _resolver = resolver; - _config = config; _activeCollections = collectionManager.Active; - _collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Identity.Name).ToList()); + _collectionCombo = new CollectionCombo(collectionManager, () => collectionManager.Storage.OrderBy(c => c.Name).ToList()); } /// Draw the header line that can quick switch between collections. @@ -37,8 +33,6 @@ public class CollectionSelectHeader : IUiService { using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 0) .Push(ImGuiStyleVar.ItemSpacing, new Vector2(0, spacing ? ImGui.GetStyle().ItemSpacing.Y : 0)); - DrawTemporaryCheckbox(); - ImGui.SameLine(); var comboWidth = ImGui.GetContentRegionAvail().X / 4f; var buttonSize = new Vector2(comboWidth * 3f / 4f, 0f); using (var _ = ImRaii.Group()) @@ -57,30 +51,6 @@ public class CollectionSelectHeader : IUiService ImGuiUtil.DrawTextButton("The currently selected collection is not used in any way.", -Vector2.UnitX, Colors.PressEnterWarningBg); } - private void DrawTemporaryCheckbox() - { - var hold = _config.IncognitoModifier.IsActive(); - using (ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImUtf8.GlobalScale)) - { - var tint = _config.DefaultTemporaryMode - ? ImGuiCol.Text.Tinted(ColorId.TemporaryModSettingsTint) - : ImGui.GetColorU32(ImGuiCol.TextDisabled); - using var color = ImRaii.PushColor(ImGuiCol.ButtonHovered, ImGui.GetColorU32(ImGuiCol.FrameBg), !hold) - .Push(ImGuiCol.ButtonActive, ImGui.GetColorU32(ImGuiCol.FrameBg), !hold) - .Push(ImGuiCol.Border, tint, _config.DefaultTemporaryMode); - if (ImUtf8.IconButton(FontAwesomeIcon.Stopwatch, ""u8, default, false, tint, ImGui.GetColorU32(ImGuiCol.FrameBg)) && hold) - { - _config.DefaultTemporaryMode = !_config.DefaultTemporaryMode; - _config.Save(); - } - } - - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, - "Toggle the temporary settings mode, where all changes you do create temporary settings first and need to be made permanent if desired."u8); - if (!hold) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {_config.IncognitoModifier} while clicking to toggle."); - } - private enum CollectionState { Empty, @@ -107,10 +77,10 @@ public class CollectionSelectHeader : IUiService return CheckCollection(collection) switch { CollectionState.Empty => (collection, "None", "The base collection is configured to use no mods.", true), - CollectionState.Selected => (collection, collection.Identity.Name, + CollectionState.Selected => (collection, collection.Name, "The configured base collection is already selected as the current collection.", true), - CollectionState.Available => (collection, collection.Identity.Name, - $"Select the configured base collection {collection.Identity.Name} as the current collection.", false), + CollectionState.Available => (collection, collection.Name, + $"Select the configured base collection {collection.Name} as the current collection.", false), _ => throw new Exception("Can not happen."), }; } @@ -121,11 +91,10 @@ public class CollectionSelectHeader : IUiService return CheckCollection(collection) switch { CollectionState.Empty => (collection, "None", "The loaded player character is configured to use no mods.", true), - CollectionState.Selected => (collection, collection.Identity.Name, + CollectionState.Selected => (collection, collection.Name, "The collection configured to apply to the loaded player character is already selected as the current collection.", true), - CollectionState.Available => (collection, collection.Identity.Name, - $"Select the collection {collection.Identity.Name} that applies to the loaded player character as the current collection.", - false), + CollectionState.Available => (collection, collection.Name, + $"Select the collection {collection.Name} that applies to the loaded player character as the current collection.", false), _ => throw new Exception("Can not happen."), }; } @@ -136,10 +105,10 @@ public class CollectionSelectHeader : IUiService return CheckCollection(collection) switch { CollectionState.Empty => (collection, "None", "The interface collection is configured to use no mods.", true), - CollectionState.Selected => (collection, collection.Identity.Name, + CollectionState.Selected => (collection, collection.Name, "The configured interface collection is already selected as the current collection.", true), - CollectionState.Available => (collection, collection.Identity.Name, - $"Select the configured interface collection {collection.Identity.Name} as the current collection.", false), + CollectionState.Available => (collection, collection.Name, + $"Select the configured interface collection {collection.Name} as the current collection.", false), _ => throw new Exception("Can not happen."), }; } @@ -151,8 +120,8 @@ public class CollectionSelectHeader : IUiService { CollectionState.Unavailable => (null, "Not Inherited", "The settings of the selected mod are not inherited from another collection.", true), - CollectionState.Available => (collection, collection!.Identity.Name, - $"Select the collection {collection!.Identity.Name} from which the selected mod inherits its settings as the current collection.", + CollectionState.Available => (collection, collection!.Name, + $"Select the collection {collection!.Name} from which the selected mod inherits its settings as the current collection.", false), _ => throw new Exception("Can not happen."), }; diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index 90ef0591..d135e10c 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -1,9 +1,8 @@ -using Dalamud.Bindings.ImGui; using OtterGui.Custom; namespace Penumbra.UI.Classes; -public enum ColorId : short +public enum ColorId { EnabledMod, DisabledMod, @@ -11,7 +10,6 @@ public enum ColorId : short InheritedMod, InheritedDisabledMod, NewMod, - NewModTint, ConflictingMod, HandledConflictMod, FolderExpanded, @@ -33,9 +31,6 @@ public enum ColorId : short ResTreeNonNetworked, PredefinedTagAdd, PredefinedTagRemove, - TemporaryModSettingsTint, - ChangedItemPreferenceStar, - NoTint, } public static class Colors @@ -53,66 +48,38 @@ public static class Colors public const uint ReniColorHovered = CustomGui.ReniColorHovered; public const uint ReniColorActive = CustomGui.ReniColorActive; - public static uint Tinted(this ColorId color, ColorId tint) - { - var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value()); - var value = ImGui.ColorConvertU32ToFloat4(color.Value()); - return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue)); - } - - public static unsafe uint Tinted(this ImGuiCol color, ColorId tint) - { - var tintValue = ImGui.ColorConvertU32ToFloat4(tint.Value()); - ref var value = ref *ImGui.GetStyleColorVec4(color); - return ImGui.ColorConvertFloat4ToU32(TintColor(value, tintValue)); - } - - private static unsafe Vector4 TintColor(in Vector4 color, in Vector4 tint) - { - var negAlpha = 1 - tint.W; - var newAlpha = negAlpha * color.W + tint.W; - var newR = (negAlpha * color.W * color.X + tint.W * tint.X) / newAlpha; - var newG = (negAlpha * color.W * color.Y + tint.W * tint.Y) / newAlpha; - var newB = (negAlpha * color.W * color.Z + tint.W * tint.Z) / newAlpha; - return new Vector4(newR, newG, newB, newAlpha); - } - public static (uint DefaultColor, string Name, string Description) Data(this ColorId color) => color switch { // @formatter:off - ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ), - ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ), - ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ), - ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ), - ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."), - ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ), - ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ), - ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ), - ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ), - ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ), - ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ), - ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ), - ColorId.IncreasedMetaValue => ( 0x80008000, "Increased Meta Manipulation Value", "An increased meta manipulation value for floats or an enabled toggle where the default is disabled."), - ColorId.DecreasedMetaValue => ( 0x80000080, "Decreased Meta Manipulation Value", "A decreased meta manipulation value for floats or a disabled toggle where the default is enabled."), - ColorId.SelectedCollection => ( 0x6069C056, "Currently Selected Collection", "The collection that is currently selected and being edited."), - ColorId.RedundantAssignment => ( 0x6050D0D0, "Redundant Collection Assignment", "A collection assignment that currently has no effect as it is redundant with more general assignments."), - ColorId.NoModsAssignment => ( 0x50000080, "'Use No Mods' Collection Assignment", "A collection assignment set to not use any mods at all."), - ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."), - ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."), - ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight (Primary)", "An in-game element that has been highlighted for ease of editing."), - ColorId.InGameHighlight2 => ( 0xFF446CC0, "In-Game Highlight (Secondary)", "Another in-game element that has been highlighted for ease of editing."), - ColorId.ResTreeLocalPlayer => ( 0xFFFFE0A0, "On-Screen: You", "You and what you own (mount, minion, accessory, pets and so on), in the On-Screen tab." ), - ColorId.ResTreePlayer => ( 0xFFC0FFC0, "On-Screen: Other Players", "Other players and what they own, in the On-Screen tab." ), - ColorId.ResTreeNetworked => ( 0xFFFFFFFF, "On-Screen: Non-Players (Networked)", "Non-player entities handled by the game server, in the On-Screen tab." ), - ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ), - ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ), - ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ), - ColorId.TemporaryModSettingsTint => ( 0x30FF0000, "Mod with Temporary Settings", "A mod that has temporary settings. This color is used as a tint for the regular state colors." ), - ColorId.NewModTint => ( 0x8000FF00, "New Mod Tint", "A mod that was newly imported or created during this session and has not been enabled yet. This color is used as a tint for the regular state colors."), - ColorId.NoTint => ( 0x00000000, "No Tint", "The default tint for all mods."), - ColorId.ChangedItemPreferenceStar => ( 0x30FFFFFF, "Preferred Changed Item Star", "The color of the star button in the mod panel's changed items tab to prioritize specific items."), - _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), + ColorId.EnabledMod => ( 0xFFFFFFFF, "Enabled Mod", "A mod that is enabled by the currently selected collection." ), + ColorId.DisabledMod => ( 0xFF686880, "Disabled Mod", "A mod that is disabled by the currently selected collection." ), + ColorId.UndefinedMod => ( 0xFF808080, "Mod With No Settings", "A mod that is not configured in the currently selected collection or any of the collections it inherits from, and thus implicitly disabled." ), + ColorId.InheritedMod => ( 0xFFD0FFFF, "Mod Enabled By Inheritance", "A mod that is not configured in the currently selected collection, but enabled in a collection it inherits from." ), + ColorId.InheritedDisabledMod => ( 0xFF688080, "Mod Disabled By Inheritance", "A mod that is not configured in the currently selected collection, but disabled in a collection it inherits from."), + ColorId.NewMod => ( 0xFF66DD66, "New Mod", "A mod that was newly imported or created during this session and has not been enabled yet." ), + ColorId.ConflictingMod => ( 0xFFAAAAFF, "Mod With Unresolved Conflicts", "An enabled mod that has conflicts with another enabled mod on the same priority level." ), + ColorId.HandledConflictMod => ( 0xFFD0FFD0, "Mod With Resolved Conflicts", "An enabled mod that has conflicts with another enabled mod on a different priority level." ), + ColorId.FolderExpanded => ( 0xFFFFF0C0, "Expanded Mod Folder", "A mod folder that is currently expanded." ), + ColorId.FolderCollapsed => ( 0xFFFFF0C0, "Collapsed Mod Folder", "A mod folder that is currently collapsed." ), + ColorId.FolderLine => ( 0xFFFFF0C0, "Expanded Mod Folder Line", "The line signifying which descendants belong to an expanded mod folder." ), + ColorId.ItemId => ( 0xFF808080, "Item Id", "The numeric model id of the given item to the right of changed items." ), + ColorId.IncreasedMetaValue => ( 0x80008000, "Increased Meta Manipulation Value", "An increased meta manipulation value for floats or an enabled toggle where the default is disabled."), + ColorId.DecreasedMetaValue => ( 0x80000080, "Decreased Meta Manipulation Value", "A decreased meta manipulation value for floats or a disabled toggle where the default is enabled."), + ColorId.SelectedCollection => ( 0x6069C056, "Currently Selected Collection", "The collection that is currently selected and being edited."), + ColorId.RedundantAssignment => ( 0x6050D0D0, "Redundant Collection Assignment", "A collection assignment that currently has no effect as it is redundant with more general assignments."), + ColorId.NoModsAssignment => ( 0x50000080, "'Use No Mods' Collection Assignment", "A collection assignment set to not use any mods at all."), + ColorId.NoAssignment => ( 0x00000000, "Unassigned Collection Assignment", "A collection assignment that is not configured to any collection and thus just has no specific treatment."), + ColorId.SelectorPriority => ( 0xFF808080, "Mod Selector Priority", "The priority displayed for non-zero priority mods in the mod selector."), + ColorId.InGameHighlight => ( 0xFFEBCF89, "In-Game Highlight (Primary)", "An in-game element that has been highlighted for ease of editing."), + ColorId.InGameHighlight2 => ( 0xFF446CC0, "In-Game Highlight (Secondary)", "Another in-game element that has been highlighted for ease of editing."), + ColorId.ResTreeLocalPlayer => ( 0xFFFFE0A0, "On-Screen: You", "You and what you own (mount, minion, accessory, pets and so on), in the On-Screen tab." ), + ColorId.ResTreePlayer => ( 0xFFC0FFC0, "On-Screen: Other Players", "Other players and what they own, in the On-Screen tab." ), + ColorId.ResTreeNetworked => ( 0xFFFFFFFF, "On-Screen: Non-Players (Networked)", "Non-player entities handled by the game server, in the On-Screen tab." ), + ColorId.ResTreeNonNetworked => ( 0xFFC0C0FF, "On-Screen: Non-Players (Local)", "Non-player entities handled locally, in the On-Screen tab." ), + ColorId.PredefinedTagAdd => ( 0xFF44AA44, "Predefined Tags: Add Tag", "A predefined tag that is not present on the current mod and can be added." ), + ColorId.PredefinedTagRemove => ( 0xFF2222AA, "Predefined Tags: Remove Tag", "A predefined tag that is already present on the current mod and can be removed." ), + _ => throw new ArgumentOutOfRangeException( nameof( color ), color, null ), // @formatter:on }; diff --git a/Penumbra/UI/Classes/MigrationSectionDrawer.cs b/Penumbra/UI/Classes/MigrationSectionDrawer.cs index 98a59a5b..a3dcd23a 100644 --- a/Penumbra/UI/Classes/MigrationSectionDrawer.cs +++ b/Penumbra/UI/Classes/MigrationSectionDrawer.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.Services; diff --git a/Penumbra/UI/CollectionTab/CollectionCombo.cs b/Penumbra/UI/CollectionTab/CollectionCombo.cs index bf97f178..1670be5e 100644 --- a/Penumbra/UI/CollectionTab/CollectionCombo.cs +++ b/Penumbra/UI/CollectionTab/CollectionCombo.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Widgets; @@ -29,13 +29,13 @@ public sealed class CollectionCombo(CollectionManager manager, Func obj.Identity.Name; + => obj.Name; protected override void DrawCombo(string label, string preview, string tooltip, int currentSelected, float previewWidth, float itemHeight, ImGuiComboFlags flags) diff --git a/Penumbra/UI/CollectionTab/CollectionPanel.cs b/Penumbra/UI/CollectionTab/CollectionPanel.cs index e41ceade..914f10d9 100644 --- a/Penumbra/UI/CollectionTab/CollectionPanel.cs +++ b/Penumbra/UI/CollectionTab/CollectionPanel.cs @@ -6,18 +6,15 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.Utility; using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; -using OtterGui.Text; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Penumbra.Mods.Manager; -using Penumbra.Mods.Settings; using Penumbra.Services; using Penumbra.UI.Classes; @@ -223,34 +220,29 @@ public sealed class CollectionPanel( ImGui.EndGroup(); ImGui.SameLine(); ImGui.BeginGroup(); - var width = ImGui.GetContentRegionAvail().X; - using (ImRaii.Disabled(_collections.DefaultNamed == collection)) + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + var name = _newName ?? collection.Name; + var identifier = collection.Identifier; + var width = ImGui.GetContentRegionAvail().X; + var fileName = saveService.FileNames.CollectionFile(collection); + ImGui.SetNextItemWidth(width); + if (ImGui.InputText("##name", ref name, 128)) + _newName = name; + if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Name) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - var name = _newName ?? collection.Identity.Name; - ImGui.SetNextItemWidth(width); - if (ImGui.InputText("##name", ref name, 128)) - _newName = name; - if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Identity.Name) - { - collection.Identity.Name = _newName; - saveService.QueueSave(new ModCollectionSave(mods, collection)); - selector.RestoreCollections(); - _newName = null; - } - else if (ImGui.IsItemDeactivated()) - { - _newName = null; - } + collection.Name = _newName; + saveService.QueueSave(new ModCollectionSave(mods, collection)); + selector.RestoreCollections(); + _newName = null; + } + else if (ImGui.IsItemDeactivated()) + { + _newName = null; } - if (_collections.DefaultNamed == collection) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The Default collection can not be renamed."u8); - var identifier = collection.Identity.Identifier; - var fileName = saveService.FileNames.CollectionFile(collection); using (ImRaii.PushFont(UiBuilder.MonoFont)) { - if (ImGui.Button(collection.Identity.Identifier, new Vector2(width, 0))) + if (ImGui.Button(collection.Identifier, new Vector2(width, 0))) try { Process.Start(new ProcessStartInfo(fileName) { UseShellExecute = true }); @@ -297,9 +289,9 @@ public sealed class CollectionPanel( _active.SetCollection(null, type, _active.Individuals.GetGroup(identifier)); } - foreach (var coll in _collections.OrderBy(c => c.Identity.Name)) + foreach (var coll in _collections.OrderBy(c => c.Name)) { - if (coll != collection && ImGui.MenuItem($"Use {coll.Identity.Name}.")) + if (coll != collection && ImGui.MenuItem($"Use {coll.Name}.")) _active.SetCollection(coll, type, _active.Individuals.GetGroup(identifier)); } } @@ -352,7 +344,7 @@ public sealed class CollectionPanel( if (!source) return; - ImGui.SetDragDropPayload("DragIndividual", null, 0); + ImGui.SetDragDropPayload("DragIndividual", nint.Zero, 0); ImGui.TextUnformatted($"Re-ordering {text}..."); _draggedIndividualAssignment = _active.Individuals.Index(id); } @@ -381,7 +373,9 @@ public sealed class CollectionPanel( ImGuiUtil.TextWrapped(type.ToDescription()); switch (type) { - case CollectionType.Default: ImGui.TextUnformatted("Overruled by any other Assignment."); break; + case CollectionType.Default: + ImGui.TextUnformatted("Overruled by any other Assignment."); + break; case CollectionType.Yourself: ImGuiUtil.DrawColoredText(("Overruled by ", 0), ("Individual ", ColorId.NewMod.Value()), ("Assignments.", 0)); break; @@ -424,7 +418,7 @@ public sealed class CollectionPanel( private string Name(ModCollection? collection) => collection == null ? "Unassigned" : collection == ModCollection.Empty ? "Use No Mods" : - incognito.IncognitoMode ? collection.Identity.AnonymizedName : collection.Identity.Name; + incognito.IncognitoMode ? collection.AnonymizedName : collection.Name; private void DrawIndividualButton(string intro, Vector2 width, string tooltip, char suffix, params ActorIdentifier[] identifiers) { @@ -503,7 +497,7 @@ public sealed class CollectionPanel( ImGui.Separator(); var buttonHeight = 2 * ImGui.GetTextLineHeightWithSpacing(); - if (_inUseCache.Count == 0 && collection.Inheritance.DirectlyInheritedBy.Count == 0) + if (_inUseCache.Count == 0 && collection.DirectParentOf.Count == 0) { ImGui.Dummy(Vector2.One); using var f = _nameFont.Push(); @@ -565,7 +559,7 @@ public sealed class CollectionPanel( private void DrawInheritanceStatistics(ModCollection collection, Vector2 buttonWidth) { - if (collection.Inheritance.DirectlyInheritedBy.Count <= 0) + if (collection.DirectParentOf.Count <= 0) return; using (var _ = ImRaii.PushStyle(ImGuiStyleVar.FramePadding, Vector2.Zero)) @@ -576,11 +570,11 @@ public sealed class CollectionPanel( using var f = _nameFont.Push(); using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); using var color = ImRaii.PushColor(ImGuiCol.Border, Colors.MetaInfoText); - ImGuiUtil.DrawTextButton(Name(collection.Inheritance.DirectlyInheritedBy[0]), Vector2.Zero, 0); + ImGuiUtil.DrawTextButton(Name(collection.DirectParentOf[0]), Vector2.Zero, 0); var constOffset = (ImGui.GetStyle().FramePadding.X + ImGuiHelpers.GlobalScale) * 2 + ImGui.GetStyle().ItemSpacing.X + ImGui.GetStyle().WindowPadding.X; - foreach (var parent in collection.Inheritance.DirectlyInheritedBy.Skip(1)) + foreach (var parent in collection.DirectParentOf.Skip(1)) { var name = Name(parent); var size = ImGui.CalcTextSize(name).X; @@ -608,7 +602,7 @@ public sealed class CollectionPanel( ImGui.TableSetupColumn("State", ImGuiTableColumnFlags.WidthFixed, 1.75f * ImGui.GetFrameHeight()); ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, 2.5f * ImGui.GetFrameHeight()); ImGui.TableHeadersRow(); - foreach (var (mod, (settings, parent)) in mods.Select(m => (m, collection.GetInheritedSettings(m.Index))) + foreach (var (mod, (settings, parent)) in mods.Select(m => (m, collection[m.Index])) .Where(t => t.Item2.Settings != null) .OrderBy(t => t.m.Name)) { @@ -631,12 +625,12 @@ public sealed class CollectionPanel( private void DrawInactiveSettingsList(ModCollection collection) { - if (collection.Settings.Unused.Count == 0) + if (collection.UnusedSettings.Count == 0) return; ImGui.Dummy(Vector2.One); - var text = collection.Settings.Unused.Count > 1 - ? $"Clear all {collection.Settings.Unused.Count} unused settings from deleted mods." + var text = collection.UnusedSettings.Count > 1 + ? $"Clear all {collection.UnusedSettings.Count} unused settings from deleted mods." : "Clear the currently unused setting from a deleted mods."; if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0))) _collections.CleanUnavailableSettings(collection); @@ -644,7 +638,7 @@ public sealed class CollectionPanel( ImGui.Dummy(Vector2.One); var size = new Vector2(ImGui.GetContentRegionAvail().X, - Math.Min(10, collection.Settings.Unused.Count + 1) * ImGui.GetFrameHeightWithSpacing()); + Math.Min(10, collection.UnusedSettings.Count + 1) * ImGui.GetFrameHeightWithSpacing()); using var table = ImRaii.Table("##inactiveSettings", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, size); if (!table) return; @@ -656,7 +650,7 @@ public sealed class CollectionPanel( ImGui.TableSetupColumn("Priority", ImGuiTableColumnFlags.WidthFixed, 2.5f * ImGui.GetFrameHeight()); ImGui.TableHeadersRow(); string? delete = null; - foreach (var (name, settings) in collection.Settings.Unused.OrderBy(n => n.Key)) + foreach (var (name, settings) in collection.UnusedSettings.OrderBy(n => n.Key)) { using var id = ImRaii.PushId(name); ImGui.TableNextColumn(); diff --git a/Penumbra/UI/CollectionTab/CollectionSelector.cs b/Penumbra/UI/CollectionTab/CollectionSelector.cs index 79254090..024873bf 100644 --- a/Penumbra/UI/CollectionTab/CollectionSelector.cs +++ b/Penumbra/UI/CollectionTab/CollectionSelector.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Collections; @@ -69,7 +69,7 @@ public sealed class CollectionSelector : ItemSelector, IDisposabl } protected override bool Filtered(int idx) - => !Items[idx].Identity.Name.Contains(Filter, StringComparison.OrdinalIgnoreCase); + => !Items[idx].Name.Contains(Filter, StringComparison.OrdinalIgnoreCase); private const string PayloadString = "Collection"; @@ -85,7 +85,7 @@ public sealed class CollectionSelector : ItemSelector, IDisposabl if (source) { _dragging = Items[idx]; - ImGui.SetDragDropPayload(PayloadString, null, 0); + ImGui.SetDragDropPayload(PayloadString, nint.Zero, 0); ImGui.TextUnformatted($"Assigning {Name(_dragging)} to..."); } @@ -111,13 +111,12 @@ public sealed class CollectionSelector : ItemSelector, IDisposabl } private string Name(ModCollection collection) - => _incognito.IncognitoMode || collection.Identity.Name.Length == 0 ? collection.Identity.AnonymizedName : collection.Identity.Name; + => _incognito.IncognitoMode || collection.Name.Length == 0 ? collection.AnonymizedName : collection.Name; public void RestoreCollections() { Items.Clear(); - Items.Add(_storage.DefaultNamed); - foreach (var c in _storage.OrderBy(c => c.Identity.Name).Where(c => c != _storage.DefaultNamed)) + foreach (var c in _storage.OrderBy(c => c.Name)) Items.Add(c); SetFilterDirty(); SetCurrent(_active.Current); diff --git a/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs b/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs index f472e346..fd8f9b25 100644 --- a/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs +++ b/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs @@ -1,5 +1,5 @@ using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Custom; using Penumbra.Collections; using Penumbra.Collections.Manager; diff --git a/Penumbra/UI/CollectionTab/InheritanceUi.cs b/Penumbra/UI/CollectionTab/InheritanceUi.cs index 2053f269..418fe52c 100644 --- a/Penumbra/UI/CollectionTab/InheritanceUi.cs +++ b/Penumbra/UI/CollectionTab/InheritanceUi.cs @@ -1,7 +1,6 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Collections; @@ -94,7 +93,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService /// private void DrawInheritedChildren(ModCollection collection) { - using var id = ImRaii.PushId(collection.Identity.Index); + using var id = ImRaii.PushId(collection.Index); using var indent = ImRaii.PushIndent(); // Get start point for the lines (top of the selector). @@ -108,14 +107,14 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService var lineEnd = lineStart; // Skip the collection itself. - foreach (var inheritance in collection.Inheritance.FlatHierarchy.Skip(1)) + foreach (var inheritance in collection.GetFlattenedInheritance().Skip(1)) { // Draw the child, already seen collections are colored as conflicts. using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), _seenInheritedCollections.Contains(inheritance)); _seenInheritedCollections.Add(inheritance); - ImRaii.TreeNode($"{Name(inheritance)}###{inheritance.Identity.Id}", + ImRaii.TreeNode($"{Name(inheritance)}###{inheritance.Id}", ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.Bullet); var (minRect, maxRect) = (ImGui.GetItemRectMin(), ImGui.GetItemRectMax()); DrawInheritanceTreeClicks(inheritance, false); @@ -141,7 +140,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.HandledConflictMod.Value(), _seenInheritedCollections.Contains(collection)); _seenInheritedCollections.Add(collection); - using var tree = ImRaii.TreeNode($"{Name(collection)}###{collection.Identity.Name}", ImGuiTreeNodeFlags.NoTreePushOnOpen); + using var tree = ImRaii.TreeNode($"{Name(collection)}###{collection.Name}", ImGuiTreeNodeFlags.NoTreePushOnOpen); color.Pop(); DrawInheritanceTreeClicks(collection, true); DrawInheritanceDropSource(collection); @@ -151,7 +150,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService DrawInheritedChildren(collection); else // We still want to keep track of conflicts. - _seenInheritedCollections.UnionWith(collection.Inheritance.FlatHierarchy); + _seenInheritedCollections.UnionWith(collection.GetFlattenedInheritance()); } /// Draw the list box containing the current inheritance information. @@ -164,7 +163,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService _seenInheritedCollections.Clear(); _seenInheritedCollections.Add(_active.Current); - foreach (var collection in _active.Current.Inheritance.DirectlyInheritsFrom.ToList()) + foreach (var collection in _active.Current.DirectlyInheritsFrom.ToList()) DrawInheritance(collection); } @@ -181,7 +180,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService using var target = ImRaii.DragDropTarget(); if (target.Success && ImGuiUtil.IsDropping(InheritanceDragDropLabel)) - _inheritanceAction = (_active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(_movedInheritance!), -1); + _inheritanceAction = (_active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance!), -1); } /// @@ -245,7 +244,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService { ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton); _newInheritance ??= _collections.FirstOrDefault(c - => c != _active.Current && !_active.Current.Inheritance.DirectlyInheritsFrom.Contains(c)) + => c != _active.Current && !_active.Current.DirectlyInheritsFrom.Contains(c)) ?? ModCollection.Empty; using var combo = ImRaii.Combo("##newInheritance", Name(_newInheritance)); if (!combo) @@ -253,7 +252,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService foreach (var collection in _collections .Where(c => InheritanceManager.CheckValidInheritance(_active.Current, c) == InheritanceManager.ValidInheritance.Valid) - .OrderBy(c => c.Identity.Name)) + .OrderBy(c => c.Name)) { if (ImGui.Selectable(Name(collection), _newInheritance == collection)) _newInheritance = collection; @@ -272,8 +271,8 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService if (_movedInheritance != null) { - var idx1 = _active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(_movedInheritance); - var idx2 = _active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(collection); + var idx1 = _active.Current.DirectlyInheritsFrom.IndexOf(_movedInheritance); + var idx2 = _active.Current.DirectlyInheritsFrom.IndexOf(collection); if (idx1 >= 0 && idx2 >= 0) _inheritanceAction = (idx1, idx2); } @@ -288,7 +287,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService if (!source) return; - ImGui.SetDragDropPayload(InheritanceDragDropLabel, null, 0); + ImGui.SetDragDropPayload(InheritanceDragDropLabel, nint.Zero, 0); _movedInheritance = collection; ImGui.TextUnformatted($"Moving {(_movedInheritance != null ? Name(_movedInheritance) : "Unknown")}..."); } @@ -303,7 +302,7 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right)) { if (withDelete && ImGui.GetIO().KeyShift) - _inheritanceAction = (_active.Current.Inheritance.DirectlyInheritsFrom.IndexOf(collection), -1); + _inheritanceAction = (_active.Current.DirectlyInheritsFrom.IndexOf(collection), -1); else _newCurrentCollection = collection; } @@ -313,5 +312,5 @@ public class InheritanceUi(CollectionManager collectionManager, IncognitoService } private string Name(ModCollection collection) - => incognito.IncognitoMode ? collection.Identity.AnonymizedName : collection.Identity.Name; + => incognito.IncognitoMode ? collection.AnonymizedName : collection.Name; } diff --git a/Penumbra/UI/ConfigWindow.cs b/Penumbra/UI/ConfigWindow.cs index 55d0bc19..53fa0b33 100644 --- a/Penumbra/UI/ConfigWindow.cs +++ b/Penumbra/UI/ConfigWindow.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Custom; using OtterGui.Raii; @@ -17,12 +17,12 @@ namespace Penumbra.UI; public sealed class ConfigWindow : Window, IUiService { private readonly IDalamudPluginInterface _pluginInterface; - private readonly Configuration _config; - private readonly PerformanceTracker _tracker; - private readonly ValidityChecker _validityChecker; - private Penumbra? _penumbra; - private ConfigTabBar _configTabs = null!; - private string? _lastException; + private readonly Configuration _config; + private readonly PerformanceTracker _tracker; + private readonly ValidityChecker _validityChecker; + private Penumbra? _penumbra; + private ConfigTabBar _configTabs = null!; + private string? _lastException; public ConfigWindow(PerformanceTracker tracker, IDalamudPluginInterface pi, Configuration config, ValidityChecker checker, TutorialService tutorial) diff --git a/Penumbra/UI/FileDialogService.cs b/Penumbra/UI/FileDialogService.cs index 3bbc4ba8..cc2a7f6a 100644 --- a/Penumbra/UI/FileDialogService.cs +++ b/Penumbra/UI/FileDialogService.cs @@ -1,9 +1,8 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Services; using Penumbra.Communication; using Penumbra.Services; diff --git a/Penumbra/UI/ImportPopup.cs b/Penumbra/UI/ImportPopup.cs index 59ed0308..28767edc 100644 --- a/Penumbra/UI/ImportPopup.cs +++ b/Penumbra/UI/ImportPopup.cs @@ -1,7 +1,7 @@ using Dalamud.Game.ClientState.Keys; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using Penumbra.Import.Structs; diff --git a/Penumbra/UI/IncognitoService.cs b/Penumbra/UI/IncognitoService.cs index 678e072e..d58ea1ec 100644 --- a/Penumbra/UI/IncognitoService.cs +++ b/Penumbra/UI/IncognitoService.cs @@ -1,5 +1,4 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; using Penumbra.UI.Classes; using OtterGui.Raii; using OtterGui.Services; @@ -7,27 +6,19 @@ using OtterGui.Text; namespace Penumbra.UI; -public class IncognitoService(TutorialService tutorial, Configuration config) : IService +public class IncognitoService(TutorialService tutorial) : IService { - public bool IncognitoMode - => config.Ephemeral.IncognitoMode; + public bool IncognitoMode; public void DrawToggle(float width) { - var hold = config.IncognitoModifier.IsActive(); var color = ColorId.FolderExpanded.Value(); using (ImRaii.PushFrameBorder(ImUtf8.GlobalScale, color)) { var tt = IncognitoMode ? "Toggle incognito mode off."u8 : "Toggle incognito mode on."u8; var icon = IncognitoMode ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye; - if (ImUtf8.IconButton(icon, tt, new Vector2(width, ImUtf8.FrameHeight), false, color) && hold) - { - config.Ephemeral.IncognitoMode = !IncognitoMode; - config.Ephemeral.Save(); - } - - if (!hold) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"\nHold {config.IncognitoModifier} while clicking to toggle."); + if (ImUtf8.IconButton(icon, tt, new Vector2(width, ImUtf8.FrameHeight), false, color)) + IncognitoMode = !IncognitoMode; } tutorial.OpenTutorial(BasicTutorialSteps.Incognito); diff --git a/Penumbra/UI/Knowledge/KnowledgeWindow.cs b/Penumbra/UI/Knowledge/KnowledgeWindow.cs index 118ed479..f831975b 100644 --- a/Penumbra/UI/Knowledge/KnowledgeWindow.cs +++ b/Penumbra/UI/Knowledge/KnowledgeWindow.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Windowing; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.String; diff --git a/Penumbra/UI/Knowledge/RaceCodeTab.cs b/Penumbra/UI/Knowledge/RaceCodeTab.cs index 44b544eb..36b048aa 100644 --- a/Penumbra/UI/Knowledge/RaceCodeTab.cs +++ b/Penumbra/UI/Knowledge/RaceCodeTab.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Text; using Penumbra.GameData.Enums; diff --git a/Penumbra/UI/LaunchButton.cs b/Penumbra/UI/LaunchButton.cs index 49161c31..cb533a00 100644 --- a/Penumbra/UI/LaunchButton.cs +++ b/Penumbra/UI/LaunchButton.cs @@ -1,5 +1,4 @@ using Dalamud.Interface; -using Dalamud.Interface.Textures; using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Plugin; using Dalamud.Plugin.Services; @@ -19,6 +18,7 @@ public class LaunchButton : IDisposable, IUiService private readonly string _fileName; private readonly ITextureProvider _textureProvider; + private IDalamudTextureWrap? _icon; private IReadOnlyTitleScreenMenuEntry? _entry; /// @@ -30,6 +30,7 @@ public class LaunchButton : IDisposable, IUiService _configWindow = ui; _textureProvider = textureProvider; _title = title; + _icon = null; _entry = null; _fileName = Path.Combine(pi.AssemblyLocation.DirectoryName!, "tsmLogo.png"); @@ -38,6 +39,7 @@ public class LaunchButton : IDisposable, IUiService public void Dispose() { + _icon?.Dispose(); if (_entry != null) _title.RemoveEntry(_entry); } @@ -50,8 +52,9 @@ public class LaunchButton : IDisposable, IUiService try { // TODO: update when API updated. - var icon = _textureProvider.GetFromFile(_fileName); - _entry = _title.AddEntry("Manage Penumbra", icon, OnTriggered); + _icon = _textureProvider.GetFromFile(_fileName).RentAsync().Result; + if (_icon != null) + _entry = _title.AddEntry("Manage Penumbra", _icon, OnTriggered); _uiBuilder.Draw -= CreateEntry; } diff --git a/Penumbra/UI/ModsTab/DescriptionEditPopup.cs b/Penumbra/UI/ModsTab/DescriptionEditPopup.cs index 7d7a6967..c284afc3 100644 --- a/Penumbra/UI/ModsTab/DescriptionEditPopup.cs +++ b/Penumbra/UI/ModsTab/DescriptionEditPopup.cs @@ -1,5 +1,5 @@ using Dalamud.Interface.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.Mods; diff --git a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs index 1430f17b..c30239bc 100644 --- a/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/AddGroupDrawer.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.Api.Enums; @@ -48,7 +48,6 @@ public class AddGroupDrawer : IUiService DrawSingleGroupButton(mod, buttonWidth); ImUtf8.SameLineInner(); DrawMultiGroupButton(mod, buttonWidth); - DrawCombiningGroupButton(mod, buttonWidth); } private void DrawSingleGroupButton(Mod mod, Vector2 width) @@ -77,18 +76,6 @@ public class AddGroupDrawer : IUiService _groupNameValid = false; } - private void DrawCombiningGroupButton(Mod mod, Vector2 width) - { - if (!ImUtf8.ButtonEx("Add Combining Group"u8, _groupNameValid - ? "Add a new combining option group to this mod."u8 - : "Can not add a new group of this name."u8, - width, !_groupNameValid)) - return; - - _modManager.OptionEditor.AddModGroup(mod, GroupType.Combining, _groupName); - _groupName = string.Empty; - _groupNameValid = false; - } private void DrawImcInput(float width) { var change = ImcMetaDrawer.DrawObjectType(ref _imcIdentifier, width); diff --git a/Penumbra/UI/ModsTab/Groups/CombiningModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/CombiningModGroupEditDrawer.cs deleted file mode 100644 index e9840e6c..00000000 --- a/Penumbra/UI/ModsTab/Groups/CombiningModGroupEditDrawer.cs +++ /dev/null @@ -1,112 +0,0 @@ -using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using OtterGui; -using OtterGui.Extensions; -using OtterGui.Raii; -using OtterGui.Text; -using Penumbra.Mods.Groups; -using Penumbra.Mods.SubMods; - -namespace Penumbra.UI.ModsTab.Groups; - -public readonly struct CombiningModGroupEditDrawer(ModGroupEditDrawer editor, CombiningModGroup group) : IModGroupEditDrawer -{ - public void Draw() - { - foreach (var (option, optionIdx) in group.OptionData.WithIndex()) - { - using var id = ImUtf8.PushId(optionIdx); - editor.DrawOptionPosition(group, option, optionIdx); - - ImUtf8.SameLineInner(); - editor.DrawOptionDefaultMultiBehaviour(group, option, optionIdx); - - ImUtf8.SameLineInner(); - editor.DrawOptionName(option); - - ImUtf8.SameLineInner(); - editor.DrawOptionDescription(option); - - ImUtf8.SameLineInner(); - editor.DrawOptionDelete(option); - } - - DrawNewOption(); - DrawContainerNames(); - } - - private void DrawNewOption() - { - var count = group.OptionData.Count; - if (count >= IModGroup.MaxCombiningOptions) - return; - - var name = editor.DrawNewOptionBase(group, count); - - var validName = name.Length > 0; - if (ImUtf8.IconButton(FontAwesomeIcon.Plus, validName - ? "Add a new option to this group."u8 - : "Please enter a name for the new option."u8, default, !validName)) - { - editor.ModManager.OptionEditor.CombiningEditor.AddOption(group, name); - editor.NewOptionName = null; - } - } - - private unsafe void DrawContainerNames() - { - if (ImUtf8.ButtonEx("Edit Container Names"u8, - "Add optional names to separate data containers of the combining group.\nThose are just for easier identification while editing the mod, and are not generally displayed to the user."u8, - new Vector2(400 * ImUtf8.GlobalScale, 0))) - ImUtf8.OpenPopup("DataContainerNames"u8); - - var sizeX = group.OptionData.Count * (ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight()) + 300 * ImUtf8.GlobalScale; - ImGui.SetNextWindowSize(new Vector2(sizeX, ImGui.GetFrameHeightWithSpacing() * Math.Min(16, group.Data.Count) + 200 * ImUtf8.GlobalScale)); - using var popup = ImUtf8.Popup("DataContainerNames"u8); - if (!popup) - return; - - foreach (var option in group.OptionData) - { - ImUtf8.RotatedText(option.Name, true); - ImUtf8.SameLineInner(); - } - - ImGui.NewLine(); - ImGui.Separator(); - using var child = ImUtf8.Child("##Child"u8, ImGui.GetContentRegionAvail()); - ImGuiClip.ClippedDraw(group.Data, DrawRow, ImGui.GetFrameHeightWithSpacing()); - } - - private void DrawRow(CombinedDataContainer container, int index) - { - using var id = ImUtf8.PushId(index); - using (ImRaii.Disabled()) - { - for (var i = 0; i < group.OptionData.Count; ++i) - { - id.Push(i); - var check = (index & (1 << i)) != 0; - ImUtf8.Checkbox(""u8, ref check); - ImUtf8.SameLineInner(); - id.Pop(); - } - } - - var name = editor.CombiningDisplayIndex == index ? editor.CombiningDisplayName ?? container.Name : container.Name; - if (ImUtf8.InputText("##Nothing"u8, ref name, "Optional Display Name..."u8)) - { - editor.CombiningDisplayIndex = index; - editor.CombiningDisplayName = name; - } - - if (ImGui.IsItemDeactivatedAfterEdit()) - editor.ModManager.OptionEditor.CombiningEditor.SetDisplayName(container, name); - - if (ImGui.IsItemDeactivated()) - { - editor.CombiningDisplayIndex = -1; - editor.CombiningDisplayName = null; - } - } -} diff --git a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs index fa5b0ef6..786bb8ff 100644 --- a/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ImcModGroupEditDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using OtterGui.Text.Widget; diff --git a/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs b/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs index 3d8409ad..dec77430 100644 --- a/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ModGroupDrawer.cs @@ -1,10 +1,8 @@ using Dalamud.Interface.Components; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Collections; using Penumbra.Collections.Manager; @@ -15,66 +13,16 @@ using Penumbra.Mods.SubMods; namespace Penumbra.UI.ModsTab.Groups; -public sealed class ModGroupDrawer : IUiService +public sealed class ModGroupDrawer(Configuration config, CollectionManager collectionManager) : IUiService { private readonly List<(IModGroup, int)> _blockGroupCache = []; - private bool _temporary; - private bool _locked; - private TemporaryModSettings? _tempSettings; - private ModSettings? _settings; - private readonly SingleGroupCombo _combo; - private readonly Configuration _config; - private readonly CollectionManager _collectionManager; - public ModGroupDrawer(Configuration config, CollectionManager collectionManager) - { - _config = config; - _collectionManager = collectionManager; - _combo = new SingleGroupCombo(this); - } - - private sealed class SingleGroupCombo(ModGroupDrawer parent) - : FilterComboCache(() => _group!.Options, MouseWheelType.Control, Penumbra.Log) - { - private static IModGroup? _group; - private static int _groupIdx; - - protected override bool DrawSelectable(int globalIdx, bool selected) - { - var option = _group!.Options[globalIdx]; - var ret = ImUtf8.Selectable(option.Name, globalIdx == CurrentSelectionIdx); - - if (option.Description.Length > 0) - ImUtf8.SelectableHelpMarker(option.Description); - - return ret; - } - - protected override string ToString(IModOption obj) - => obj.Name; - - public void Draw(IModGroup group, int groupIndex, int currentOption) - { - _group = group; - _groupIdx = groupIndex; - CurrentSelectionIdx = currentOption; - CurrentSelection = _group.Options[CurrentSelectionIdx]; - if (Draw(string.Empty, CurrentSelection.Name, string.Empty, ref CurrentSelectionIdx, UiHelpers.InputTextWidth.X * 3 / 4, - ImGui.GetTextLineHeightWithSpacing())) - parent.SetModSetting(_group, _groupIdx, Setting.Single(CurrentSelectionIdx)); - } - } - - public void Draw(Mod mod, ModSettings settings, TemporaryModSettings? tempSettings) + public void Draw(Mod mod, ModSettings settings) { if (mod.Groups.Count <= 0) return; _blockGroupCache.Clear(); - _settings = settings; - _tempSettings = tempSettings; - _temporary = tempSettings != null; - _locked = (tempSettings?.Lock ?? 0) > 0; var useDummy = true; foreach (var (group, idx) in mod.Groups.WithIndex()) { @@ -83,7 +31,7 @@ public sealed class ModGroupDrawer : IUiService switch (group.Behaviour) { - case GroupDrawBehaviour.SingleSelection when group.Options.Count <= _config.SingleGroupRadioMax: + case GroupDrawBehaviour.SingleSelection when group.Options.Count <= config.SingleGroupRadioMax: case GroupDrawBehaviour.MultiSelection: _blockGroupCache.Add((group, idx)); break; @@ -91,7 +39,7 @@ public sealed class ModGroupDrawer : IUiService case GroupDrawBehaviour.SingleSelection: ImGuiUtil.Dummy(UiHelpers.DefaultSpace, useDummy); useDummy = false; - DrawSingleGroupCombo(group, idx, settings.IsEmpty ? group.DefaultSettings : settings.Settings[idx]); + DrawSingleGroupCombo(group, idx, settings == ModSettings.Empty ? group.DefaultSettings : settings.Settings[idx]); break; } } @@ -101,7 +49,7 @@ public sealed class ModGroupDrawer : IUiService { ImGuiUtil.Dummy(UiHelpers.DefaultSpace, useDummy); useDummy = false; - var option = settings.IsEmpty ? group.DefaultSettings : settings.Settings[idx]; + var option = settings == ModSettings.Empty ? group.DefaultSettings : settings.Settings[idx]; if (group.Behaviour is GroupDrawBehaviour.MultiSelection) DrawMultiGroup(group, idx, option); else @@ -115,15 +63,32 @@ public sealed class ModGroupDrawer : IUiService /// private void DrawSingleGroupCombo(IModGroup group, int groupIdx, Setting setting) { - using var id = ImUtf8.PushId(groupIdx); - var selectedOption = setting.AsIndex; - using var disabled = ImRaii.Disabled(_locked); - _combo.Draw(group, groupIdx, selectedOption); + using var id = ImRaii.PushId(groupIdx); + var selectedOption = setting.AsIndex; + ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X * 3 / 4); + var options = group.Options; + using (var combo = ImRaii.Combo(string.Empty, options[selectedOption].Name)) + { + if (combo) + for (var idx2 = 0; idx2 < options.Count; ++idx2) + { + id.Push(idx2); + var option = options[idx2]; + if (ImGui.Selectable(option.Name, idx2 == selectedOption)) + SetModSetting(group, groupIdx, Setting.Single(idx2)); + + if (option.Description.Length > 0) + ImGuiUtil.SelectableHelpMarker(option.Description); + + id.Pop(); + } + } + ImGui.SameLine(); if (group.Description.Length > 0) - ImUtf8.LabeledHelpMarker(group.Name, group.Description); + ImGuiUtil.LabeledHelpMarker(group.Name, group.Description); else - ImUtf8.Text(group.Name); + ImGui.TextUnformatted(group.Name); } /// @@ -132,10 +97,10 @@ public sealed class ModGroupDrawer : IUiService /// private void DrawSingleGroupRadio(IModGroup group, int groupIdx, Setting setting) { - using var id = ImUtf8.PushId(groupIdx); - var selectedOption = setting.AsIndex; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); - var options = group.Options; + using var id = ImRaii.PushId(groupIdx); + var selectedOption = setting.AsIndex; + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var options = group.Options; DrawCollapseHandling(options, minWidth, DrawOptions); Widget.EndFramedGroup(); @@ -143,12 +108,11 @@ public sealed class ModGroupDrawer : IUiService void DrawOptions() { - using var disabled = ImRaii.Disabled(_locked); for (var idx = 0; idx < group.Options.Count; ++idx) { - using var i = ImUtf8.PushId(idx); - var option = options[idx]; - if (ImUtf8.RadioButton(option.Name, selectedOption == idx)) + using var i = ImRaii.PushId(idx); + var option = options[idx]; + if (ImGui.RadioButton(option.Name, selectedOption == idx)) SetModSetting(group, groupIdx, Setting.Single(idx)); if (option.Description.Length <= 0) @@ -166,29 +130,28 @@ public sealed class ModGroupDrawer : IUiService /// private void DrawMultiGroup(IModGroup group, int groupIdx, Setting setting) { - using var id = ImUtf8.PushId(groupIdx); - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); - var options = group.Options; + using var id = ImRaii.PushId(groupIdx); + var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var options = group.Options; DrawCollapseHandling(options, minWidth, DrawOptions); Widget.EndFramedGroup(); var label = $"##multi{groupIdx}"; if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - ImUtf8.OpenPopup($"##multi{groupIdx}"); + ImGui.OpenPopup($"##multi{groupIdx}"); DrawMultiPopup(group, groupIdx, label); return; void DrawOptions() { - using var disabled = ImRaii.Disabled(_locked); for (var idx = 0; idx < options.Count; ++idx) { - using var i = ImUtf8.PushId(idx); - var option = options[idx]; - var enabled = setting.HasFlag(idx); + using var i = ImRaii.PushId(idx); + var option = options[idx]; + var enabled = setting.HasFlag(idx); - if (ImUtf8.Checkbox(option.Name, ref enabled)) + if (ImGui.Checkbox(option.Name, ref enabled)) SetModSetting(group, groupIdx, setting.SetBit(idx, enabled)); if (option.Description.Length > 0) @@ -208,28 +171,27 @@ public sealed class ModGroupDrawer : IUiService return; ImGui.TextUnformatted(group.Name); - using var disabled = ImRaii.Disabled(_locked); ImGui.Separator(); - if (ImUtf8.Selectable("Enable All"u8)) + if (ImGui.Selectable("Enable All")) SetModSetting(group, groupIdx, Setting.AllBits(group.Options.Count)); - if (ImUtf8.Selectable("Disable All"u8)) + if (ImGui.Selectable("Disable All")) SetModSetting(group, groupIdx, Setting.Zero); } private void DrawCollapseHandling(IReadOnlyList options, float minWidth, Action draw) { - if (options.Count <= _config.OptionGroupCollapsibleMin) + if (options.Count <= config.OptionGroupCollapsibleMin) { draw(); } else { - var collapseId = ImUtf8.GetId("Collapse"); - var shown = ImGui.GetStateStorage().GetBool(collapseId, true); + var collapseId = ImGui.GetID("Collapse"); + var shown = ImGui.GetStateStorage().GetBool(collapseId, true); var buttonTextShow = $"Show {options.Count} Options"; var buttonTextHide = $"Hide {options.Count} Options"; - var buttonWidth = Math.Max(ImUtf8.CalcTextSize(buttonTextShow).X, ImUtf8.CalcTextSize(buttonTextHide).X) + var buttonWidth = Math.Max(ImGui.CalcTextSize(buttonTextShow).X, ImGui.CalcTextSize(buttonTextHide).X) + 2 * ImGui.GetStyle().FramePadding.X; minWidth = Math.Max(buttonWidth, minWidth); if (shown) @@ -242,43 +204,30 @@ public sealed class ModGroupDrawer : IUiService } - var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); + var width = Math.Max(ImGui.GetItemRectSize().X, minWidth); var endPos = ImGui.GetCursorPos(); ImGui.SetCursorPos(pos); - if (ImUtf8.Button(buttonTextHide, new Vector2(width, 0))) + if (ImGui.Button(buttonTextHide, new Vector2(width, 0))) ImGui.GetStateStorage().SetBool(collapseId, !shown); ImGui.SetCursorPos(endPos); } else { - var optionWidth = options.Max(o => ImUtf8.CalcTextSize(o.Name).X) + var optionWidth = options.Max(o => ImGui.CalcTextSize(o.Name).X) + ImGui.GetStyle().ItemInnerSpacing.X + ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X; var width = Math.Max(optionWidth, minWidth); - if (ImUtf8.Button(buttonTextShow, new Vector2(width, 0))) + if (ImGui.Button(buttonTextShow, new Vector2(width, 0))) ImGui.GetStateStorage().SetBool(collapseId, !shown); } } } private ModCollection Current - => _collectionManager.Active.Current; + => collectionManager.Active.Current; - [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private void SetModSetting(IModGroup group, int groupIdx, Setting setting) - { - if (_temporary || _config.DefaultTemporaryMode) - { - _tempSettings ??= new TemporaryModSettings(group.Mod, _settings); - _tempSettings!.ForceInherit = false; - _tempSettings!.Settings[groupIdx] = setting; - _collectionManager.Editor.SetTemporarySettings(Current, group.Mod, _tempSettings); - } - else - { - _collectionManager.Editor.SetModSetting(Current, group.Mod, groupIdx, setting); - } - } + => collectionManager.Editor.SetModSetting(Current, group.Mod, groupIdx, setting); } diff --git a/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs index 9610f173..ec5bb920 100644 --- a/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/ModGroupEditDrawer.cs @@ -1,9 +1,8 @@ using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -59,9 +58,6 @@ public sealed class ModGroupEditDrawer( private IModOption? _dragDropOption; private bool _draggingAcross; - internal string? CombiningDisplayName; - internal int CombiningDisplayIndex; - public void Draw(Mod mod) { PrepareStyle(); @@ -279,7 +275,6 @@ public sealed class ModGroupEditDrawer( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal string DrawNewOptionBase(IModGroup group, int count) { - ImGui.AlignTextToFramePadding(); ImUtf8.Selectable($"Option #{count + 1}", false, size: OptionIdxSelectable); Target(group, count); diff --git a/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs index 04ca6c82..f0275853 100644 --- a/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/MultiModGroupEditDrawer.cs @@ -1,5 +1,5 @@ using Dalamud.Interface; -using OtterGui.Extensions; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.Mods.Groups; diff --git a/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs b/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs index 8fa6a377..be2dbd73 100644 --- a/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs +++ b/Penumbra/UI/ModsTab/Groups/SingleModGroupEditDrawer.cs @@ -1,6 +1,6 @@ using Dalamud.Interface; -using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Text; using Penumbra.Mods.Groups; diff --git a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs index 3f3c82aa..0781312c 100644 --- a/Penumbra/UI/ModsTab/ModFileSystemSelector.cs +++ b/Penumbra/UI/ModsTab/ModFileSystemSelector.cs @@ -2,7 +2,7 @@ using Dalamud.Interface; using Dalamud.Interface.DragDrop; using Dalamud.Interface.ImGuiNotification; using Dalamud.Plugin.Services; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Filesystem; @@ -62,9 +62,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector SetQuickMove(f, 1, _config.QuickMoveFolder2, s => { _config.QuickMoveFolder2 = s; _config.Save(); }), 120); SubscribeRightClickFolder(f => SetQuickMove(f, 2, _config.QuickMoveFolder3, s => { _config.QuickMoveFolder3 = s; _config.Save(); }), 130); SubscribeRightClickLeaf(ToggleLeafFavorite); - SubscribeRightClickLeaf(DrawTemporaryOptions); SubscribeRightClickLeaf(l => QuickMove(l, _config.QuickMoveFolder1, _config.QuickMoveFolder2, _config.QuickMoveFolder3)); - SubscribeRightClickMain(ClearTemporarySettings, 105); SubscribeRightClickMain(ClearDefaultImportFolder, 100); SubscribeRightClickMain(() => ClearQuickMove(0, _config.QuickMoveFolder1, () => {_config.QuickMoveFolder1 = string.Empty; _config.Save();}), 110); SubscribeRightClickMain(() => ClearQuickMove(1, _config.QuickMoveFolder2, () => {_config.QuickMoveFolder2 = string.Empty; _config.Save();}), 120); @@ -126,47 +124,23 @@ public sealed class ModFileSystemSelector : FileSystemSelector m.Extensions.Any(e => ValidModExtensions.Contains(e.ToLowerInvariant())), m => { - ImUtf8.Text($"Dragging mods for import:\n\t{string.Join("\n\t", m.Files.Select(Path.GetFileName))}"); + ImGui.TextUnformatted($"Dragging mods for import:\n\t{string.Join("\n\t", m.Files.Select(Path.GetFileName))}"); return true; }); - base.Draw(); + base.Draw(width); if (_dragDrop.CreateImGuiTarget("ModDragDrop", out var files, out _)) _modImportManager.AddUnpack(files.Where(f => ValidModExtensions.Contains(Path.GetExtension(f.ToLowerInvariant())))); } - protected override float CurrentWidth - => _config.Ephemeral.CurrentModSelectorWidth * ImUtf8.GlobalScale; - - protected override float MinimumAbsoluteRemainder - => 550 * ImUtf8.GlobalScale; - - protected override float MinimumScaling - => _config.Ephemeral.ModSelectorMinimumScale; - - protected override float MaximumScaling - => _config.Ephemeral.ModSelectorMaximumScale; - - protected override void SetSize(Vector2 size) - { - base.SetSize(size); - var adaptedSize = MathF.Round(size.X / ImUtf8.GlobalScale); - if (adaptedSize == _config.Ephemeral.CurrentModSelectorWidth) - return; - - _config.Ephemeral.CurrentModSelectorWidth = adaptedSize; - _config.Ephemeral.Save(); - } - public override void Dispose() { base.Dispose(); @@ -220,37 +194,24 @@ public sealed class ModFileSystemSelector : FileSystemSelector.Leaf leaf, in ModState state, bool selected) { var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags; - using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Tinted(state.Tint)) + using var c = ImRaii.PushColor(ImGuiCol.Text, state.Color.Value()) .Push(ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite); - using var id = ImUtf8.PushId(leaf.Value.Index); - ImUtf8.TreeNode(leaf.Value.Name.Text, flags).Dispose(); + using var id = ImRaii.PushId(leaf.Value.Index); + ImRaii.TreeNode(leaf.Value.Name, flags).Dispose(); if (ImGui.IsItemClicked(ImGuiMouseButton.Middle)) { _modManager.SetKnown(leaf.Value); - var (setting, collection) = _collectionManager.Active.Current.GetActualSettings(leaf.Value.Index); + var (setting, collection) = _collectionManager.Active.Current[leaf.Value.Index]; if (_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift)).IsActive()) { - // Delete temporary settings if they exist, regardless of mode, or set to inheriting if none exist. - if (_collectionManager.Active.Current.GetTempSettings(leaf.Value.Index) is not null) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, null); - else - _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, true); + _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, true); } else { - if (_config.DefaultTemporaryMode) - { - var settings = new TemporaryModSettings(leaf.Value, setting) { ForceInherit = false }; - settings.Enabled = !settings.Enabled; - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, leaf.Value, settings); - } - else - { - var inherited = collection != _collectionManager.Active.Current; - if (inherited) - _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, false); - _collectionManager.Editor.SetModState(_collectionManager.Active.Current, leaf.Value, setting is not { Enabled: true }); - } + var inherited = collection != _collectionManager.Active.Current; + if (inherited) + _collectionManager.Editor.SetModInheritance(_collectionManager.Active.Current, leaf.Value, false); + _collectionManager.Editor.SetModState(_collectionManager.Active.Current, leaf.Value, setting is not { Enabled: true }); } } @@ -275,69 +236,37 @@ public sealed class ModFileSystemSelector : FileSystemSelector.Leaf mod) { - if (ImUtf8.MenuItem(mod.Value.Favorite ? "Remove Favorite"u8 : "Mark as Favorite"u8)) + if (ImGui.MenuItem(mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite")) _modManager.DataEditor.ChangeModFavorite(mod.Value, !mod.Value.Favorite); } - private void DrawTemporaryOptions(FileSystem.Leaf mod) - { - var tempSettings = _collectionManager.Active.Current.GetTempSettings(mod.Value.Index); - if (tempSettings is { Lock: > 0 }) - return; - - if (tempSettings is { Lock: <= 0 } && ImUtf8.MenuItem("Remove Temporary Settings"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, null); - var actual = _collectionManager.Active.Current.GetActualSettings(mod.Value.Index).Settings; - if (actual?.Enabled is true && ImUtf8.MenuItem("Disable Temporarily"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, - new TemporaryModSettings(mod.Value, actual) { Enabled = false }); - - if (actual is not { Enabled: true } && ImUtf8.MenuItem("Enable Temporarily"u8)) - { - var newSettings = actual is null - ? TemporaryModSettings.DefaultSettings(mod.Value, TemporaryModSettings.OwnSource, true) - : new TemporaryModSettings(mod.Value, actual) { Enabled = true }; - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, newSettings); - } - - if (tempSettings is null && ImUtf8.MenuItem("Turn Temporary"u8)) - _collectionManager.Editor.SetTemporarySettings(_collectionManager.Active.Current, mod.Value, - new TemporaryModSettings(mod.Value, actual)); - } - private void SetDefaultImportFolder(ModFileSystem.Folder folder) { - if (!ImUtf8.MenuItem("Set As Default Import Folder"u8)) + if (!ImGui.MenuItem("Set As Default Import Folder")) return; var newName = folder.FullName(); @@ -350,7 +279,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector Add an import mods button that opens a file selector. private void AddImportModButton(Vector2 size) { - var button = ImUtf8.IconButton(FontAwesomeIcon.FileImport, - "Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files."u8, size, !_modManager.Valid); + var button = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), size, + "Import one or multiple mods from Tex Tools Mod Pack Files or Penumbra Mod Pack Files.", !_modManager.Valid, true); _tutorial.OpenTutorial(BasicTutorialSteps.ModImport); if (!button) return; @@ -381,7 +311,7 @@ public sealed class ModFileSystemSelector : FileSystemSelector + "Mod Packs{.ttmp,.ttmp2,.pmp},TexTools Mod Packs{.ttmp,.ttmp2},Penumbra Mod Packs{.pmp},Archives{.zip,.7z,.rar}", (s, f) => { if (!s) return; @@ -402,14 +332,14 @@ public sealed class ModFileSystemSelector : FileSystemSelector { ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeight()); - ImUtf8.Text("Mod Management"u8); - ImUtf8.BulletText("You can create empty mods or import mods with the buttons in this row."u8); + ImGui.TextUnformatted("Mod Management"); + ImGui.BulletText("You can create empty mods or import mods with the buttons in this row."); using var indent = ImRaii.PushIndent(); - ImUtf8.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp, .pcp."u8); - ImUtf8.BulletText( - "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."u8); + ImGui.BulletText("Supported formats for import are: .ttmp, .ttmp2, .pmp."); + ImGui.BulletText( + "You can also support .zip, .7z or .rar archives, but only if they already contain Penumbra-styled mods with appropriate metadata."); indent.Pop(1); - ImUtf8.BulletText("You can also create empty mod folders and delete mods."u8); - ImUtf8.BulletText( - "For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."u8); + ImGui.BulletText("You can also create empty mod folders and delete mods."); + ImGui.BulletText("For further editing of mods, select them and use the Edit Mod tab in the panel or the Advanced Editing popup."); ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeight()); - ImUtf8.Text("Mod Selector"u8); - ImUtf8.BulletText("Select a mod to obtain more information or change settings."u8); - ImUtf8.BulletText("Names are colored according to your config and their current state in the collection:"u8); + ImGui.TextUnformatted("Mod Selector"); + ImGui.BulletText("Select a mod to obtain more information or change settings."); + ImGui.BulletText("Names are colored according to your config and their current state in the collection:"); indent.Push(); - ImUtf8.BulletTextColored(ColorId.EnabledMod.Value(), "enabled in the current collection."u8); - ImUtf8.BulletTextColored(ColorId.DisabledMod.Value(), "disabled in the current collection."u8); - ImUtf8.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection."u8); - ImUtf8.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection."u8); - ImUtf8.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections."u8); - ImUtf8.BulletTextColored(ColorId.HandledConflictMod.Value(), - "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."u8); - ImUtf8.BulletTextColored(ColorId.ConflictingMod.Value(), - "enabled and conflicting with another enabled Mod on the same priority."u8); - ImUtf8.BulletTextColored(ColorId.FolderExpanded.Value(), "expanded mod folder."u8); - ImUtf8.BulletTextColored(ColorId.FolderCollapsed.Value(), "collapsed mod folder"u8); + ImGuiUtil.BulletTextColored(ColorId.EnabledMod.Value(), "enabled in the current collection."); + ImGuiUtil.BulletTextColored(ColorId.DisabledMod.Value(), "disabled in the current collection."); + ImGuiUtil.BulletTextColored(ColorId.InheritedMod.Value(), "enabled due to inheritance from another collection."); + ImGuiUtil.BulletTextColored(ColorId.InheritedDisabledMod.Value(), "disabled due to inheritance from another collection."); + ImGuiUtil.BulletTextColored(ColorId.UndefinedMod.Value(), "unconfigured in all inherited collections."); + ImGuiUtil.BulletTextColored(ColorId.NewMod.Value(), + "newly imported during this session. Will go away when first enabling a mod or when Penumbra is reloaded."); + ImGuiUtil.BulletTextColored(ColorId.HandledConflictMod.Value(), + "enabled and conflicting with another enabled Mod, but on different priorities (i.e. the conflict is solved)."); + ImGuiUtil.BulletTextColored(ColorId.ConflictingMod.Value(), + "enabled and conflicting with another enabled Mod on the same priority."); + ImGuiUtil.BulletTextColored(ColorId.FolderExpanded.Value(), "expanded mod folder."); + ImGuiUtil.BulletTextColored(ColorId.FolderCollapsed.Value(), "collapsed mod folder"); indent.Pop(1); - ImUtf8.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."u8); + ImGui.BulletText("Middle-click a mod to disable it if it is enabled or enable it if it is disabled."); indent.Push(); - ImUtf8.BulletText( + ImGui.BulletText( $"Holding {_config.DeleteModModifier.ForcedModifier(new DoubleModifier(ModifierHotkey.Control, ModifierHotkey.Shift))} while middle-clicking lets it inherit, discarding settings."); indent.Pop(1); - ImUtf8.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."u8); + ImGui.BulletText("Right-click a mod to enter its sort order, which is its name by default, possibly with a duplicate number."); indent.Push(); - ImUtf8.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."u8); - ImUtf8.BulletText( - "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."u8); + ImGui.BulletText("A sort order differing from the mods name will not be displayed, it will just be used for ordering."); + ImGui.BulletText( + "If the sort order string contains Forward-Slashes ('/'), the preceding substring will be turned into folders automatically."); indent.Pop(1); - ImUtf8.BulletText( - "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."u8); + ImGui.BulletText( + "You can drag and drop mods and subfolders into existing folders. Dropping them onto mods is the same as dropping them onto the parent of the mod."); indent.Push(); - ImUtf8.BulletText( - "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."u8); - ImUtf8.BulletText( - "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."u8); + ImGui.BulletText( + "You can select multiple mods and folders by holding Control while clicking them, and then drag all of them at once."); + ImGui.BulletText( + "Selected mods inside an also selected folder will be ignored when dragging and move inside their folder instead of directly into the target."); indent.Pop(1); - ImUtf8.BulletText("Right-clicking a folder opens a context menu."u8); - ImUtf8.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."u8); - ImUtf8.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."u8); + ImGui.BulletText("Right-clicking a folder opens a context menu."); + ImGui.BulletText("Right-clicking empty space allows you to expand or collapse all folders at once."); + ImGui.BulletText("Use the Filter Mods... input at the top to filter the list for mods whose name or path contain the text."); indent.Push(); - ImUtf8.BulletText("You can enter n:[string] to filter only for names, without path."u8); - ImUtf8.BulletText("You can enter c:[string] to filter for Changed Items instead."u8); - ImUtf8.BulletText("You can enter a:[string] to filter for Mod Authors instead."u8); + ImGui.BulletText("You can enter n:[string] to filter only for names, without path."); + ImGui.BulletText("You can enter c:[string] to filter for Changed Items instead."); + ImGui.BulletText("You can enter a:[string] to filter for Mod Authors instead."); indent.Pop(1); - ImUtf8.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."u8); + ImGui.BulletText("Use the expandable menu beside the input to filter for mods fulfilling specific criteria."); }); } @@ -570,7 +501,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector !_filter.IsVisible(leaf); /// Only get the text color for a mod if no filters are set. - private (ColorId Color, ColorId Tint) GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) + private ColorId GetTextColor(Mod mod, ModSettings? settings, ModCollection collection) { - var tint = settings.IsTemporary() - ? ColorId.TemporaryModSettingsTint - : _modManager.IsNew(mod) - ? ColorId.NewModTint - : ColorId.NoTint; - if (settings.IsTemporary()) - tint = ColorId.TemporaryModSettingsTint; + if (_modManager.IsNew(mod)) + return ColorId.NewMod; if (settings == null) - return (ColorId.UndefinedMod, tint); + return ColorId.UndefinedMod; if (!settings.Enabled) - return (collection != _collectionManager.Active.Current - ? ColorId.InheritedDisabledMod - : ColorId.DisabledMod, tint); + return collection != _collectionManager.Active.Current ? ColorId.InheritedDisabledMod : ColorId.DisabledMod; var conflicts = _collectionManager.Active.Current.Conflicts(mod); if (conflicts.Count == 0) - return (collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod, tint); + return collection != _collectionManager.Active.Current ? ColorId.InheritedMod : ColorId.EnabledMod; - return (conflicts.Any(c => !c.Solved) + return conflicts.Any(c => !c.Solved) ? ColorId.ConflictingMod - : ColorId.HandledConflictMod, tint); + : ColorId.HandledConflictMod; } private bool CheckStateFilters(Mod mod, ModSettings? settings, ModCollection collection, ref ModState state) @@ -684,16 +607,6 @@ public sealed class ModFileSystemSelector : FileSystemSelector TriStatePairs = [ @@ -40,7 +38,6 @@ public static class ModFilterExtensions (ModFilter.HasFiles, ModFilter.HasNoFiles, "Has Redirections"), (ModFilter.HasMetaManipulations, ModFilter.HasNoMetaManipulations, "Has Meta Manipulations"), (ModFilter.HasFileSwaps, ModFilter.HasNoFileSwaps, "Has File Swaps"), - (ModFilter.Temporary, ModFilter.NotTemporary, "Temporary"), ]; public static IReadOnlyList> Groups = diff --git a/Penumbra/UI/ModsTab/ModPanel.cs b/Penumbra/UI/ModsTab/ModPanel.cs index b7546699..9d6ead62 100644 --- a/Penumbra/UI/ModsTab/ModPanel.cs +++ b/Penumbra/UI/ModsTab/ModPanel.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Services; using Penumbra.Mods; using Penumbra.Services; diff --git a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs index 332b64f0..a7bdadd3 100644 --- a/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelChangedItemsTab.cs @@ -1,348 +1,47 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.Utility.Raii; +using ImGuiNET; using OtterGui; using OtterGui.Classes; +using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using OtterGui.Widgets; using Penumbra.GameData.Data; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; -using Penumbra.Mods; -using Penumbra.Mods.Manager; -using Penumbra.String; -using Penumbra.UI.Classes; namespace Penumbra.UI.ModsTab; -public class ModPanelChangedItemsTab( - ModFileSystemSelector selector, - ChangedItemDrawer drawer, - ImGuiCacheService cacheService, - Configuration config, - ModDataEditor dataEditor) - : ITab, IUiService +public class ModPanelChangedItemsTab(ModFileSystemSelector selector, ChangedItemDrawer drawer) : ITab, IUiService { - private readonly ImGuiCacheService.CacheId _cacheId = cacheService.GetNewId(); - - private class ChangedItemsCache - { - private Mod? _lastSelected; - private ushort _lastUpdate; - private ChangedItemIconFlag _filter = ChangedItemFlagExtensions.DefaultFlags; - private ChangedItemMode _lastMode; - private bool _reset; - public readonly List Data = []; - public bool AnyExpandable { get; private set; } - - public record struct Container - { - public IIdentifiedObjectData Data; - public ByteString Text; - public ByteString ModelData; - public uint Id; - public int Children; - public ChangedItemIconFlag Icon; - public bool Expandable; - public bool Expanded; - public bool Child; - - public static Container Single(string text, IIdentifiedObjectData data) - => new() - { - Child = false, - Text = ByteString.FromStringUnsafe(data.ToName(text), false), - ModelData = ByteString.FromStringUnsafe(data.AdditionalData, false), - Icon = data.GetIcon().ToFlag(), - Expandable = false, - Expanded = false, - Data = data, - Id = 0, - Children = 0, - }; - - public static Container Parent(string text, IIdentifiedObjectData data, uint id, int children, bool expanded) - => new() - { - Child = false, - Text = ByteString.FromStringUnsafe(data.ToName(text), false), - ModelData = ByteString.FromStringUnsafe(data.AdditionalData, false), - Icon = data.GetIcon().ToFlag(), - Expandable = true, - Expanded = expanded, - Data = data, - Id = id, - Children = children, - }; - - public static Container Indent(string text, IIdentifiedObjectData data) - => new() - { - Child = true, - Text = ByteString.FromStringUnsafe(data.ToName(text), false), - ModelData = ByteString.FromStringUnsafe(data.AdditionalData, false), - Icon = data.GetIcon().ToFlag(), - Expandable = false, - Expanded = false, - Data = data, - Id = 0, - Children = 0, - }; - } - - public void Reset() - => _reset = true; - - public void Update(Mod? mod, ChangedItemDrawer drawer, ChangedItemIconFlag filter, ChangedItemMode mode) - { - if (mod == _lastSelected - && _lastSelected!.LastChangedItemsUpdate == _lastUpdate - && _filter == filter - && !_reset - && _lastMode == mode) - return; - - _reset = false; - Data.Clear(); - AnyExpandable = false; - _lastSelected = mod; - _filter = filter; - _lastMode = mode; - if (_lastSelected == null) - return; - - _lastUpdate = _lastSelected.LastChangedItemsUpdate; - - if (mode is ChangedItemMode.Alphabetical) - { - foreach (var (s, i) in _lastSelected.ChangedItems) - { - if (drawer.FilterChangedItem(s, i, LowerString.Empty)) - Data.Add(Container.Single(s, i)); - } - - return; - } - - var tmp = new Dictionary<(PrimaryId, FullEquipType), List>(); - var defaultExpansion = _lastMode is ChangedItemMode.GroupedExpanded; - foreach (var (s, i) in _lastSelected.ChangedItems) - { - if (i is not IdentifiedItem item) - continue; - - if (!drawer.FilterChangedItem(s, item, LowerString.Empty)) - continue; - - if (tmp.TryGetValue((item.Item.PrimaryId, item.Item.Type), out var p)) - p.Add(item); - else - tmp[(item.Item.PrimaryId, item.Item.Type)] = [item]; - } - - foreach (var list in tmp.Values) - { - list.Sort((i1, i2) => - { - // reversed - var preferred = _lastSelected.PreferredChangedItems.Contains(i2.Item.Id) - .CompareTo(_lastSelected.PreferredChangedItems.Contains(i1.Item.Id)); - if (preferred != 0) - return preferred; - - // reversed - var count = i2.Count.CompareTo(i1.Count); - if (count != 0) - return count; - - return string.Compare(i1.Item.Name, i2.Item.Name, StringComparison.Ordinal); - }); - } - - var sortedTmp = tmp.Values.OrderBy(s => s[0].Item.Name).ToArray(); - - var sortedTmpIdx = 0; - foreach (var (s, i) in _lastSelected.ChangedItems) - { - if (i is IdentifiedItem) - continue; - - if (!drawer.FilterChangedItem(s, i, LowerString.Empty)) - continue; - - while (sortedTmpIdx < sortedTmp.Length - && string.Compare(sortedTmp[sortedTmpIdx][0].Item.Name, s, StringComparison.Ordinal) <= 0) - AddList(sortedTmp[sortedTmpIdx++]); - - Data.Add(Container.Single(s, i)); - } - - for (; sortedTmpIdx < sortedTmp.Length; ++sortedTmpIdx) - AddList(sortedTmp[sortedTmpIdx]); - return; - - void AddList(List list) - { - var mainItem = list[0]; - if (list.Count == 1) - { - Data.Add(Container.Single(mainItem.Item.Name, mainItem)); - } - else - { - var id = ImUtf8.GetId($"{mainItem.Item.PrimaryId}{(int)mainItem.Item.Type}"); - var expanded = ImGui.GetStateStorage().GetBool(id, defaultExpansion); - Data.Add(Container.Parent(mainItem.Item.Name, mainItem, id, list.Count - 1, expanded)); - AnyExpandable = true; - if (!expanded) - return; - - foreach (var item in list.Skip(1)) - Data.Add(Container.Indent(item.Item.Name, item)); - } - } - } - } - public ReadOnlySpan Label => "Changed Items"u8; public bool IsVisible => selector.Selected!.ChangedItems.Count > 0; - private ImGuiStoragePtr _stateStorage; - - private Vector2 _buttonSize; - private uint _starColor; - public void DrawContent() { - if (cacheService.Cache(_cacheId, () => (new ChangedItemsCache(), "ModPanelChangedItemsCache")) is not { } cache) - return; - drawer.DrawTypeFilter(); - - _stateStorage = ImGui.GetStateStorage(); - cache.Update(selector.Selected, drawer, config.Ephemeral.ChangedItemFilter, config.ChangedItemDisplay); ImGui.Separator(); - _buttonSize = new Vector2(ImGui.GetStyle().ItemSpacing.Y + ImGui.GetFrameHeight()); - using var style = ImRaii.PushStyle(ImGuiStyleVar.CellPadding, Vector2.Zero) - .Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .Push(ImGuiStyleVar.FramePadding, Vector2.Zero) - .Push(ImGuiStyleVar.SelectableTextAlign, new Vector2(0.01f, 0.5f)); - using var color = ImRaii.PushColor(ImGuiCol.Button, 0); - - using var table = ImUtf8.Table("##changedItems"u8, cache.AnyExpandable ? 2 : 1, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, + using var table = ImRaii.Table("##changedItems", 1, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(ImGui.GetContentRegionAvail().X, -1)); if (!table) return; - _starColor = ColorId.ChangedItemPreferenceStar.Value(); - if (cache.AnyExpandable) - { - ImUtf8.TableSetupColumn("##exp"u8, ImGuiTableColumnFlags.WidthFixed, _buttonSize.Y); - ImUtf8.TableSetupColumn("##text"u8, ImGuiTableColumnFlags.WidthStretch); - ImGuiClip.ClippedDraw(cache.Data, DrawContainerExpandable, _buttonSize.Y); - } - else - { - ImGuiClip.ClippedDraw(cache.Data, DrawContainer, _buttonSize.Y); - } - } - - private void DrawContainerExpandable(ChangedItemsCache.Container obj, int idx) - { - using var id = ImUtf8.PushId(idx); + var zipList = ZipList.FromSortedList(selector.Selected!.ChangedItems); + var height = ImGui.GetFrameHeightWithSpacing(); ImGui.TableNextColumn(); - if (obj.Expandable) - { - if (ImUtf8.IconButton(obj.Expanded ? FontAwesomeIcon.CaretDown : FontAwesomeIcon.CaretRight, - obj.Expanded ? "Hide the other items using the same model." : - obj.Children > 1 ? $"Show {obj.Children} other items using the same model." : - "Show one other item using the same model.", - _buttonSize)) - { - _stateStorage.SetBool(obj.Id, !obj.Expanded); - if (cacheService.TryGetCache(_cacheId, out var cache)) - cache.Reset(); - } - } - else if (obj is { Child: true, Data: IdentifiedItem item }) - { - DrawPreferredButton(item, idx); - } - else - { - ImGui.Dummy(_buttonSize); - } - - DrawBaseContainer(obj, idx); + var skips = ImGuiClip.GetNecessarySkips(height); + var remainder = ImGuiClip.FilteredClippedDraw(zipList, skips, CheckFilter, DrawChangedItem); + ImGuiClip.DrawEndDummy(remainder, height); } - private void DrawContainer(ChangedItemsCache.Container obj, int idx) - { - using var id = ImUtf8.PushId(idx); - DrawBaseContainer(obj, idx); - } + private bool CheckFilter((string Name, IIdentifiedObjectData? Data) kvp) + => drawer.FilterChangedItem(kvp.Name, kvp.Data, LowerString.Empty); - private void DrawPreferredButton(IdentifiedItem item, int idx) - { - if (ImUtf8.IconButton(FontAwesomeIcon.Star, "Prefer displaying this item instead of the current primary item.\n\nRight-click for more options."u8, _buttonSize, - false, _starColor)) - dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, false, true); - using var context = ImUtf8.PopupContextItem("StarContext"u8, ImGuiPopupFlags.MouseButtonRight); - if (!context) - return; - - if (cacheService.TryGetCache(_cacheId, out var cache)) - for (--idx; idx >= 0; --idx) - { - if (!cache.Data[idx].Expanded) - continue; - - if (cache.Data[idx].Data is IdentifiedItem it) - { - if (selector.Selected!.PreferredChangedItems.Contains(it.Item.Id) - && ImUtf8.MenuItem("Remove Parent from Local Preferred Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, false); - if (selector.Selected!.DefaultPreferredItems.Contains(it.Item.Id) - && ImUtf8.MenuItem("Remove Parent from Default Preferred Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, it.Item.Id, true); - } - - break; - } - - var enabled = !selector.Selected!.DefaultPreferredItems.Contains(item.Item.Id); - if (enabled) - { - if (ImUtf8.MenuItem("Add to Local and Default Preferred Changed Items"u8)) - dataEditor.AddPreferredItem(selector.Selected!, item.Item.Id, true, true); - } - else - { - if (ImUtf8.MenuItem("Remove from Default Preferred Changed Items"u8)) - dataEditor.RemovePreferredItem(selector.Selected!, item.Item.Id, true); - } - - if (ImUtf8.MenuItem("Reset Local Preferred Items to Default"u8)) - dataEditor.ResetPreferredItems(selector.Selected!); - - if (ImUtf8.MenuItem("Clear Local and Default Preferred Items not Changed by the Mod"u8)) - dataEditor.ClearInvalidPreferredItems(selector.Selected!); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void DrawBaseContainer(in ChangedItemsCache.Container obj, int idx) + private void DrawChangedItem((string Name, IIdentifiedObjectData? Data) kvp) { ImGui.TableNextColumn(); - using var indent = ImRaii.PushIndent(1, obj.Child); - drawer.DrawCategoryIcon(obj.Icon, _buttonSize.Y); - ImGui.SameLine(0, 0); - var clicked = ImUtf8.Selectable(obj.Text.Span, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 }); - drawer.ChangedItemHandling(obj.Data, clicked); - ChangedItemDrawer.DrawModelData(obj.ModelData.Span, _buttonSize.Y); + drawer.DrawCategoryIcon(kvp.Data); + ImGui.SameLine(); + drawer.DrawChangedItem(kvp.Name, kvp.Data); + ChangedItemDrawer.DrawModelData(kvp.Data); } } diff --git a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs index 70cad148..b7648428 100644 --- a/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelCollectionsTab.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility; -using OtterGui.Extensions; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -56,7 +56,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele foreach (var ((collection, parent, color, state), idx) in _cache.WithIndex()) { using var id = ImUtf8.PushId(idx); - ImUtf8.DrawTableColumn(collection.Identity.Name); + ImUtf8.DrawTableColumn(collection.Name); ImGui.TableNextColumn(); ImUtf8.Text(ToText(state), color); @@ -65,7 +65,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele { if (context) { - ImUtf8.Text(collection.Identity.Name); + ImUtf8.Text(collection.Name); ImGui.Separator(); using (ImRaii.Disabled(state is ModState.Enabled && parent == collection)) { @@ -95,7 +95,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele } } - ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Identity.Name); + ImUtf8.DrawTableColumn(parent == collection ? string.Empty : parent.Name); } } @@ -120,7 +120,7 @@ public class ModPanelCollectionsTab(CollectionManager manager, ModFileSystemSele var inheritedCount = 0; foreach (var collection in manager.Storage) { - var (settings, parent) = collection.GetInheritedSettings(mod.Index); + var (settings, parent) = collection[mod.Index]; var (color, text) = settings == null ? (undefined, ModState.Unconfigured) : settings.Enabled diff --git a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs index 1002d8ca..bc18ac51 100644 --- a/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelConflictsTab.cs @@ -1,8 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Text; @@ -35,7 +34,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy if (conflicts.Mod2.Index < 0) return conflicts.Mod2.Priority; - return collectionManager.Active.Current.GetActualSettings(conflicts.Mod2.Index).Settings?.Priority ?? ModPriority.Default; + return collectionManager.Active.Current[conflicts.Mod2.Index].Settings?.Priority ?? ModPriority.Default; } public void DrawContent() @@ -73,29 +72,24 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy ImGui.TableNextColumn(); using var c = ImRaii.PushColor(ImGuiCol.Text, ColorId.FolderLine.Value()); ImGui.AlignTextToFramePadding(); - ImGui.TextUnformatted(selector.Selected!.Name.Text); + ImGui.TextUnformatted(selector.Selected!.Name); ImGui.TableNextColumn(); - var actualSettings = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!; - var priority = actualSettings.Priority.Value; - // TODO - using (ImRaii.Disabled(actualSettings is TemporaryModSettings)) + var priority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value; + ImGui.SetNextItemWidth(priorityWidth); + if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue)) + _currentPriority = priority; + + if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { - ImGui.SetNextItemWidth(priorityWidth); - if (ImGui.InputInt("##priority", ref priority, 0, 0, flags: ImGuiInputTextFlags.EnterReturnsTrue)) - _currentPriority = priority; + if (_currentPriority != collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value) + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, + new ModPriority(_currentPriority.Value)); - if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) - { - if (_currentPriority != actualSettings.Priority.Value) - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selector.Selected!, - new ModPriority(_currentPriority.Value)); - - _currentPriority = null; - } - else if (ImGui.IsItemDeactivated()) - { - _currentPriority = null; - } + _currentPriority = null; + } + else if (ImGui.IsItemDeactivated()) + { + _currentPriority = null; } ImGui.TableNextColumn(); @@ -104,7 +98,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy private void DrawConflictSelectable(ModConflicts conflict) { ImGui.AlignTextToFramePadding(); - if (ImGui.Selectable(conflict.Mod2.Name.Text) && conflict.Mod2 is Mod otherMod) + if (ImGui.Selectable(conflict.Mod2.Name) && conflict.Mod2 is Mod otherMod) selector.SelectByValue(otherMod); var hovered = ImGui.IsItemHovered(); var rightClicked = ImGui.IsItemClicked(ImGuiMouseButton.Right); @@ -144,7 +138,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy ImGui.TableNextColumn(); var conflictPriority = DrawPriorityInput(conflict, priorityWidth); ImGui.SameLine(); - var selectedPriority = collectionManager.Active.Current.GetActualSettings(selector.Selected!.Index).Settings!.Priority.Value; + var selectedPriority = collectionManager.Active.Current[selector.Selected!.Index].Settings!.Priority.Value; DrawPriorityButtons(conflict.Mod2 as Mod, conflictPriority, selectedPriority, buttonSize); ImGui.TableNextColumn(); DrawExpandButton(conflict.Mod2, expanded, buttonSize); @@ -172,7 +166,7 @@ public class ModPanelConflictsTab(CollectionManager collectionManager, ModFileSy var priority = _currentPriority ?? GetPriority(conflict).Value; ImGui.SetNextItemWidth(priorityWidth); - if (ImGui.InputInt("##priority", ref priority, 0, 0, flags: ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.InputInt("##priority", ref priority, 0, 0, ImGuiInputTextFlags.EnterReturnsTrue)) _currentPriority = priority; if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) diff --git a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs index b8710707..6fe3e4c6 100644 --- a/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelDescriptionTab.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility; +using ImGuiNET; using OtterGui.Raii; using OtterGui; using OtterGui.Services; @@ -30,7 +30,7 @@ public class ModPanelDescriptionTab( ImGui.Dummy(ImGuiHelpers.ScaledVector2(2)); ImGui.Dummy(ImGuiHelpers.ScaledVector2(2)); - var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Enabled + var (predefinedTagsEnabled, predefinedTagButtonOffset) = predefinedTagsConfig.Count > 0 ? (true, ImGui.GetFrameHeight() + ImGui.GetStyle().WindowPadding.X + (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize : 0)) : (false, 0); var tagIdx = _localTags.Draw("Local Tags: ", diff --git a/Penumbra/UI/ModsTab/ModPanelEditTab.cs b/Penumbra/UI/ModsTab/ModPanelEditTab.cs index c3737b40..1ccee2cb 100644 --- a/Penumbra/UI/ModsTab/ModPanelEditTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelEditTab.cs @@ -1,7 +1,7 @@ using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.ImGuiNotification; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Widgets; @@ -65,11 +65,7 @@ public class ModPanelEditTab( } UiHelpers.DefaultLineSpace(); - - FeatureChecker.DrawFeatureFlagInput(modManager.DataEditor, _mod, UiHelpers.InputTextWidth.X); - - UiHelpers.DefaultLineSpace(); - var sharedTagsEnabled = predefinedTagManager.Enabled; + var sharedTagsEnabled = predefinedTagManager.Count > 0; var sharedTagButtonOffset = sharedTagsEnabled ? ImGui.GetFrameHeight() + ImGui.GetStyle().FramePadding.X : 0; var tagIdx = _modTags.Draw("Mod Tags: ", "Edit tags by clicking them, or add new tags. Empty tags are removed.", _mod.ModTags, out var editedTag, rightEndOffset: sharedTagButtonOffset); @@ -80,7 +76,6 @@ public class ModPanelEditTab( predefinedTagManager.DrawAddFromSharedTagsAndUpdateTags(selector.Selected!.LocalTags, selector.Selected!.ModTags, false, selector.Selected!); - UiHelpers.DefaultLineSpace(); addGroupDrawer.Draw(_mod, UiHelpers.InputTextWidth.X); UiHelpers.DefaultLineSpace(); @@ -126,42 +121,32 @@ public class ModPanelEditTab( : backup.Exists ? $"Overwrite current exported mod \"{backup.Name}\" with current mod." : $"Create exported archive of current mod at \"{backup.Name}\"."; - if (ImUtf8.ButtonEx("Export Mod"u8, tt, buttonSize, ModBackup.CreatingBackup)) + if (ImGuiUtil.DrawDisabledButton("Export Mod", buttonSize, tt, ModBackup.CreatingBackup)) backup.CreateAsync(); - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - ImUtf8.OpenPopup("context"u8); - ImGui.SameLine(); tt = backup.Exists ? $"Delete existing mod export \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)." : $"Exported mod \"{backup.Name}\" does not exist."; - if (ImUtf8.ButtonEx("Delete Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive())) + if (ImGuiUtil.DrawDisabledButton("Delete Export", buttonSize, tt, !backup.Exists || !config.DeleteModModifier.IsActive())) backup.Delete(); tt = backup.Exists ? $"Restore mod from exported file \"{backup.Name}\" (hold {config.DeleteModModifier} while clicking)." : $"Exported mod \"{backup.Name}\" does not exist."; ImGui.SameLine(); - if (ImUtf8.ButtonEx("Restore From Export"u8, tt, buttonSize, !backup.Exists || !config.DeleteModModifier.IsActive())) + if (ImGuiUtil.DrawDisabledButton("Restore From Export", buttonSize, tt, !backup.Exists || !config.DeleteModModifier.IsActive())) backup.Restore(modManager); if (backup.Exists) { ImGui.SameLine(); - using (ImRaii.PushFont(UiBuilder.IconFont)) + using (var font = ImRaii.PushFont(UiBuilder.IconFont)) { - ImUtf8.Text(FontAwesomeIcon.CheckCircle.ToIconString()); + ImGui.TextUnformatted(FontAwesomeIcon.CheckCircle.ToIconString()); } - ImUtf8.HoverTooltip($"Export exists in \"{backup.Name}\"."); + ImGuiUtil.HoverTooltip($"Export exists in \"{backup.Name}\"."); } - - using var context = ImUtf8.Popup("context"u8); - if (!context) - return; - - if (ImUtf8.Selectable("Open Backup Directory"u8)) - Process.Start(new ProcessStartInfo(modExportManager.ExportDirectory.FullName) { UseShellExecute = true }); } /// Anything about editing the regular meta information about the mod. @@ -325,7 +310,7 @@ public class ModPanelEditTab( var tmp = field == _currentField && option == _optionIndex ? _currentEdit ?? oldValue : oldValue; ImGui.SetNextItemWidth(width); - if (ImGui.InputText(label, ref tmp)) + if (ImGui.InputText(label, ref tmp, maxLength)) { _currentEdit = tmp; _optionIndex = option; diff --git a/Penumbra/UI/ModsTab/ModPanelHeader.cs b/Penumbra/UI/ModsTab/ModPanelHeader.cs index b42ac680..aafbffa6 100644 --- a/Penumbra/UI/ModsTab/ModPanelHeader.cs +++ b/Penumbra/UI/ModsTab/ModPanelHeader.cs @@ -1,7 +1,7 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.GameFonts; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Plugin; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.Communication; diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index 84f69bcb..8d889c3b 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -1,5 +1,6 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Raii; +using OtterGui; using OtterGui.Services; using OtterGui.Text; using OtterGui.Widgets; @@ -10,7 +11,6 @@ using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.Mods.Settings; using Penumbra.UI.ModsTab.Groups; -using OtterGui.Extensions; namespace Penumbra.UI.ModsTab; @@ -20,13 +20,10 @@ public class ModPanelSettingsTab( ModSelection selection, TutorialService tutorial, CommunicatorService communicator, - ModGroupDrawer modGroupDrawer, - Configuration config) + ModGroupDrawer modGroupDrawer) : ITab, IUiService { private bool _inherited; - private bool _temporary; - private bool _locked; private int? _currentPriority; public ReadOnlySpan Label @@ -40,19 +37,13 @@ public class ModPanelSettingsTab( public void DrawContent() { - using var table = ImUtf8.Table("##settings"u8, 1, ImGuiTableFlags.ScrollY, ImGui.GetContentRegionAvail()); - if (!table) + using var child = ImRaii.Child("##settings"); + if (!child) return; - _inherited = selection.Collection != collectionManager.Active.Current; - _temporary = selection.TemporarySettings != null; - _locked = (selection.TemporarySettings?.Lock ?? 0) > 0; - - ImGui.TableSetupScrollFreeze(0, 1); - ImGui.TableNextColumn(); - DrawTemporaryWarning(); + _inherited = selection.Collection != collectionManager.Active.Current; DrawInheritedWarning(); - ImGui.Dummy(Vector2.Zero); + UiHelpers.DefaultLineSpace(); communicator.PreSettingsPanelDraw.Invoke(selection.Mod!.Identifier); DrawEnabledInput(); tutorial.OpenTutorial(BasicTutorialSteps.EnablingMods); @@ -61,31 +52,13 @@ public class ModPanelSettingsTab( tutorial.OpenTutorial(BasicTutorialSteps.Priority); DrawRemoveSettings(); - ImGui.TableNextColumn(); communicator.PostEnabledDraw.Invoke(selection.Mod!.Identifier); - modGroupDrawer.Draw(selection.Mod!, selection.Settings, selection.TemporarySettings); + modGroupDrawer.Draw(selection.Mod!, selection.Settings); UiHelpers.DefaultLineSpace(); communicator.PostSettingsPanelDraw.Invoke(selection.Mod!.Identifier); } - /// Draw a big tinted bar if the current setting is temporary. - private void DrawTemporaryWarning() - { - if (!_temporary) - return; - - using var color = ImRaii.PushColor(ImGuiCol.Button, ImGuiCol.Button.Tinted(ColorId.TemporaryModSettingsTint)); - var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); - if (ImUtf8.ButtonEx($"These settings are temporarily set by {selection.TemporarySettings!.Source}{(_locked ? " and locked." : ".")}", - width, - _locked)) - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, null); - - ImUtf8.HoverTooltip("Changing settings in temporary settings will not save them across sessions.\n"u8 - + "You can click this button to remove the temporary settings and return to your normal settings."u8); - } - /// Draw a big red bar if the current setting is inherited. private void DrawInheritedWarning() { @@ -94,43 +67,22 @@ public class ModPanelSettingsTab( using var color = ImRaii.PushColor(ImGuiCol.Button, Colors.PressEnterWarningBg); var width = new Vector2(ImGui.GetContentRegionAvail().X, 0); - if (ImUtf8.ButtonEx($"These settings are inherited from {selection.Collection.Identity.Name}.", width, _locked)) - { - if (_temporary) - { - selection.TemporarySettings!.ForceInherit = false; - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, selection.TemporarySettings); - } - else - { - collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, false); - } - } + if (ImGui.Button($"These settings are inherited from {selection.Collection.Name}.", width)) + collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, false); - ImUtf8.HoverTooltip("You can click this button to copy the current settings to the current selection.\n"u8 - + "You can also just change any setting, which will copy the settings with the single setting changed to the current selection."u8); + 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."); } /// Draw a checkbox for the enabled status of the mod. private void DrawEnabledInput() { - var enabled = selection.Settings.Enabled; - using var disabled = ImRaii.Disabled(_locked); - if (!ImUtf8.Checkbox("Enabled"u8, ref enabled)) + var enabled = selection.Settings.Enabled; + if (!ImGui.Checkbox("Enabled", ref enabled)) return; modManager.SetKnown(selection.Mod!); - if (_temporary || config.DefaultTemporaryMode) - { - var temporarySettings = selection.TemporarySettings ?? new TemporaryModSettings(selection.Mod!, selection.Settings); - temporarySettings.ForceInherit = false; - temporarySettings.Enabled = enabled; - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, temporarySettings); - } - else - { - collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod!, enabled); - } + collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod!, enabled); } /// @@ -139,123 +91,45 @@ public class ModPanelSettingsTab( /// private void DrawPriorityInput() { - using var group = ImUtf8.Group(); + using var group = ImRaii.Group(); var settings = selection.Settings; var priority = _currentPriority ?? settings.Priority.Value; ImGui.SetNextItemWidth(50 * UiHelpers.Scale); - using var disabled = ImRaii.Disabled(_locked); - if (ImUtf8.InputScalar("##Priority"u8, ref priority)) + if (ImGui.InputInt("##Priority", ref priority, 0, 0)) _currentPriority = priority; if (new ModPriority(priority).IsHidden) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, - $"This priority is special-cased to hide this mod in conflict tabs ({ModPriority.HiddenMin}, {ModPriority.HiddenMax})."); + ImUtf8.HoverTooltip($"This priority is special-cased to hide this mod in conflict tabs ({ModPriority.HiddenMin}, {ModPriority.HiddenMax})."); if (ImGui.IsItemDeactivatedAfterEdit() && _currentPriority.HasValue) { if (_currentPriority != settings.Priority.Value) - { - if (_temporary || config.DefaultTemporaryMode) - { - var temporarySettings = selection.TemporarySettings ?? new TemporaryModSettings(selection.Mod!, selection.Settings); - temporarySettings.ForceInherit = false; - temporarySettings.Priority = new ModPriority(_currentPriority.Value); - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, - temporarySettings); - } - else - { - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, - new ModPriority(_currentPriority.Value)); - } - } + collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod!, + new ModPriority(_currentPriority.Value)); _currentPriority = null; } - ImUtf8.LabeledHelpMarker("Priority"u8, "Mods with a higher number here take precedence before Mods with a lower number.\n"u8 - + "That means, if Mod A should overwrite changes from Mod B, Mod A should have a higher priority number than Mod B."u8); + ImGuiUtil.LabeledHelpMarker("Priority", "Mods with a higher number here take precedence before Mods with a lower number.\n" + + "That means, if Mod A should overwrite changes from Mod B, Mod A should have a higher priority number than Mod B."); } /// /// Draw a button to remove the current settings and inherit them instead - /// in the top-right corner of the window/tab. + /// on the top-right corner of the window/tab. /// private void DrawRemoveSettings() { - var drawInherited = !_inherited && !selection.Settings.IsEmpty; - var scroll = ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().ItemInnerSpacing.X : 0; - var buttonSize = ImUtf8.CalcTextSize("Turn Permanent_"u8).X; - var offset = drawInherited - ? buttonSize + ImUtf8.CalcTextSize("Inherit Settings"u8).X + ImGui.GetStyle().FramePadding.X * 4 + ImGui.GetStyle().ItemSpacing.X - : buttonSize + ImGui.GetStyle().FramePadding.X * 2; - ImGui.SameLine(ImGui.GetWindowWidth() - offset - scroll); - var enabled = config.DeleteModModifier.IsActive(); - if (drawInherited) - { - var inherit = (enabled, _locked) switch - { - (true, false) => ImUtf8.ButtonEx("Inherit Settings"u8, - "Remove current settings from this collection so that it can inherit them.\n"u8 - + "If no inherited collection has settings for this mod, it will be disabled."u8, default, false), - (false, false) => ImUtf8.ButtonEx("Inherit Settings"u8, - $"Remove current settings from this collection so that it can inherit them.\nHold {config.DeleteModModifier} to inherit.", - default, true), - (_, true) => ImUtf8.ButtonEx("Inherit Settings"u8, - "Remove current settings from this collection so that it can inherit them.\nThe settings are currently locked and can not be changed."u8, - default, true), - }; - if (inherit) - { - if (_temporary || config.DefaultTemporaryMode) - { - var temporarySettings = selection.TemporarySettings ?? new TemporaryModSettings(selection.Mod!, selection.Settings); - temporarySettings.ForceInherit = true; - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, - temporarySettings); - } - else - { - collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod!, true); - } - } + const string text = "Inherit Settings"; + if (_inherited || selection.Settings == ModSettings.Empty) + return; - ImGui.SameLine(); - } + 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, selection.Mod!, true); - if (_temporary) - { - var overwrite = enabled - ? ImUtf8.ButtonEx("Turn Permanent"u8, - "Overwrite the actual settings for this mod in this collection with the current temporary settings."u8, - new Vector2(buttonSize, 0)) - : ImUtf8.ButtonEx("Turn Permanent"u8, - $"Overwrite the actual settings for this mod in this collection with the current temporary settings.\nHold {config.DeleteModModifier} to overwrite.", - new Vector2(buttonSize, 0), true); - if (overwrite) - { - var settings = collectionManager.Active.Current.GetTempSettings(selection.Mod!.Index)!; - if (settings.ForceInherit) - { - collectionManager.Editor.SetModInheritance(collectionManager.Active.Current, selection.Mod, true); - } - else - { - collectionManager.Editor.SetModState(collectionManager.Active.Current, selection.Mod, settings.Enabled); - collectionManager.Editor.SetModPriority(collectionManager.Active.Current, selection.Mod, settings.Priority); - foreach (var (setting, index) in settings.Settings.WithIndex()) - collectionManager.Editor.SetModSetting(collectionManager.Active.Current, selection.Mod, index, setting); - } - - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod, null); - } - } - else - { - var actual = collectionManager.Active.Current.GetActualSettings(selection.Mod!.Index).Settings; - if (ImUtf8.ButtonEx("Turn Temporary"u8, "Copy the current settings over to temporary settings to experiment with them."u8)) - collectionManager.Editor.SetTemporarySettings(collectionManager.Active.Current, selection.Mod!, - new TemporaryModSettings(selection.Mod!, actual)); - } + 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/ModsTab/ModPanelTabBar.cs b/Penumbra/UI/ModsTab/ModPanelTabBar.cs index 5981d979..639118f5 100644 --- a/Penumbra/UI/ModsTab/ModPanelTabBar.cs +++ b/Penumbra/UI/ModsTab/ModPanelTabBar.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/UI/ModsTab/MultiModPanel.cs b/Penumbra/UI/ModsTab/MultiModPanel.cs index 947ede14..4079748e 100644 --- a/Penumbra/UI/ModsTab/MultiModPanel.cs +++ b/Penumbra/UI/ModsTab/MultiModPanel.cs @@ -1,92 +1,68 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Utility; -using OtterGui.Extensions; -using OtterGui.Filesystem; +using ImGuiNET; +using OtterGui; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using Penumbra.Mods; using Penumbra.Mods.Manager; namespace Penumbra.UI.ModsTab; -public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, PredefinedTagManager tagManager) : IUiService +public class MultiModPanel(ModFileSystemSelector _selector, ModDataEditor _editor) : IUiService { public void Draw() { - if (selector.SelectedPaths.Count == 0) + if (_selector.SelectedPaths.Count == 0) return; ImGui.NewLine(); - var treeNodePos = ImGui.GetCursorPos(); - var numLeaves = DrawModList(); - DrawCounts(treeNodePos, numLeaves); + DrawModList(); DrawMultiTagger(); } - private void DrawCounts(Vector2 treeNodePos, int numLeaves) + private void DrawModList() { - var startPos = ImGui.GetCursorPos(); - var numFolders = selector.SelectedPaths.Count - numLeaves; - var text = (numLeaves, numFolders) switch - { - (0, 0) => string.Empty, // should not happen - (> 0, 0) => $"{numLeaves} Mods", - (0, > 0) => $"{numFolders} Folders", - _ => $"{numLeaves} Mods, {numFolders} Folders", - }; - ImGui.SetCursorPos(treeNodePos); - ImUtf8.TextRightAligned(text); - ImGui.SetCursorPos(startPos); - } - - private int DrawModList() - { - using var tree = ImUtf8.TreeNode("Currently Selected Objects###Selected"u8, - ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); + using var tree = ImRaii.TreeNode("Currently Selected Objects", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen); ImGui.Separator(); - - if (!tree) - return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf); + return; - var sizeType = new Vector2(ImGui.GetFrameHeight()); - var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType.X - 4 * ImGui.GetStyle().CellPadding.X) / 100; + var sizeType = ImGui.GetFrameHeight(); + var availableSizePercent = (ImGui.GetContentRegionAvail().X - sizeType - 4 * ImGui.GetStyle().CellPadding.X) / 100; var sizeMods = availableSizePercent * 35; var sizeFolders = availableSizePercent * 65; - var leaves = 0; - using (var table = ImUtf8.Table("mods"u8, 3, ImGuiTableFlags.RowBg)) + using (var table = ImRaii.Table("mods", 3, ImGuiTableFlags.RowBg)) { if (!table) - return selector.SelectedPaths.Count(l => l is ModFileSystem.Leaf); + return; - ImUtf8.TableSetupColumn("type"u8, ImGuiTableColumnFlags.WidthFixed, sizeType.X); - ImUtf8.TableSetupColumn("mod"u8, ImGuiTableColumnFlags.WidthFixed, sizeMods); - ImUtf8.TableSetupColumn("path"u8, ImGuiTableColumnFlags.WidthFixed, sizeFolders); + ImGui.TableSetupColumn("type", ImGuiTableColumnFlags.WidthFixed, sizeType); + ImGui.TableSetupColumn("mod", ImGuiTableColumnFlags.WidthFixed, sizeMods); + ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthFixed, sizeFolders); var i = 0; - foreach (var (fullName, path) in selector.SelectedPaths.Select(p => (p.FullName(), p)) + foreach (var (fullName, path) in _selector.SelectedPaths.Select(p => (p.FullName(), p)) .OrderBy(p => p.Item1, StringComparer.OrdinalIgnoreCase)) { using var id = ImRaii.PushId(i++); - var (icon, text) = path is ModFileSystem.Leaf l - ? (FontAwesomeIcon.FileCircleMinus, l.Value.Name.Text) - : (FontAwesomeIcon.FolderMinus, string.Empty); ImGui.TableNextColumn(); - if (ImUtf8.IconButton(icon, "Remove from selection."u8, sizeType)) - selector.RemovePathFromMultiSelection(path); + var icon = (path is ModFileSystem.Leaf ? FontAwesomeIcon.FileCircleMinus : FontAwesomeIcon.FolderMinus).ToIconString(); + if (ImGuiUtil.DrawDisabledButton(icon, new Vector2(sizeType), "Remove from selection.", false, true)) + _selector.RemovePathFromMultiSelection(path); - ImUtf8.DrawFrameColumn(text); - ImUtf8.DrawFrameColumn(fullName); - if (path is ModFileSystem.Leaf) - ++leaves; + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(path is ModFileSystem.Leaf l ? l.Value.Name : string.Empty); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(fullName); } } ImGui.Separator(); - return leaves; } private string _tag = string.Empty; @@ -96,15 +72,11 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, private void DrawMultiTagger() { var width = ImGuiHelpers.ScaledVector2(150, 0); - ImUtf8.TextFrameAligned("Multi Tagger:"u8); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted("Multi Tagger:"); ImGui.SameLine(); - - var predefinedTagsEnabled = tagManager.Enabled; - var inputWidth = predefinedTagsEnabled - ? ImGui.GetContentRegionAvail().X - 2 * width.X - 3 * ImGui.GetStyle().ItemInnerSpacing.X - ImGui.GetFrameHeight() - : ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemInnerSpacing.X); - ImGui.SetNextItemWidth(inputWidth); - ImUtf8.InputText("##tag"u8, ref _tag, "Local Tag Name..."u8); + ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X - 2 * (width.X + ImGui.GetStyle().ItemSpacing.X)); + ImGui.InputTextWithHint("##tag", "Local Tag Name...", ref _tag, 128); UpdateTagCache(); var label = _addMods.Count > 0 @@ -115,10 +87,10 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, ? "No tag specified." : $"All mods selected already contain the tag \"{_tag}\", either locally or as mod data." : $"Add the tag \"{_tag}\" to {_addMods.Count} mods as a local tag:\n\n\t{string.Join("\n\t", _addMods.Select(m => m.Name.Text))}"; - ImUtf8.SameLineInner(); - if (ImUtf8.ButtonEx(label, tooltip, width, _addMods.Count == 0)) + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _addMods.Count == 0)) foreach (var mod in _addMods) - editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag); + _editor.ChangeLocalTag(mod, mod.LocalTags.Count, _tag); label = _removeMods.Count > 0 ? $"Remove from {_removeMods.Count} Mods" @@ -128,18 +100,10 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, ? "No tag specified." : $"No selected mod contains the tag \"{_tag}\" locally." : $"Remove the local tag \"{_tag}\" from {_removeMods.Count} mods:\n\n\t{string.Join("\n\t", _removeMods.Select(m => m.Item1.Name.Text))}"; - ImUtf8.SameLineInner(); - if (ImUtf8.ButtonEx(label, tooltip, width, _removeMods.Count == 0)) + ImGui.SameLine(); + if (ImGuiUtil.DrawDisabledButton(label, width, tooltip, _removeMods.Count == 0)) foreach (var (mod, index) in _removeMods) - editor.ChangeLocalTag(mod, index, string.Empty); - - if (predefinedTagsEnabled) - { - ImUtf8.SameLineInner(); - tagManager.DrawToggleButton(); - tagManager.DrawListMulti(selector.SelectedPaths.OfType().Select(l => l.Value)); - } - + _editor.ChangeLocalTag(mod, index, string.Empty); ImGui.Separator(); } @@ -150,7 +114,7 @@ public class MultiModPanel(ModFileSystemSelector selector, ModDataEditor editor, if (_tag.Length == 0) return; - foreach (var leaf in selector.SelectedPaths.OfType()) + foreach (var leaf in _selector.SelectedPaths.OfType()) { var index = leaf.Value.LocalTags.IndexOf(_tag); if (index >= 0) diff --git a/Penumbra/UI/PredefinedTagManager.cs b/Penumbra/UI/PredefinedTagManager.cs index 5a3a4b62..8de613d4 100644 --- a/Penumbra/UI/PredefinedTagManager.cs +++ b/Penumbra/UI/PredefinedTagManager.cs @@ -1,15 +1,12 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.ImGuiNotification; +using ImGuiNET; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Mods; using Penumbra.Mods.Manager; using Penumbra.Services; using Penumbra.UI.Classes; @@ -54,9 +51,6 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer jObj.WriteTo(jWriter); } - public bool Enabled - => Count > 0; - public void Save() => _saveService.DelaySave(this, TimeSpan.FromSeconds(5)); @@ -103,9 +97,9 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer } public void DrawAddFromSharedTagsAndUpdateTags(IReadOnlyCollection localTags, IReadOnlyCollection modTags, bool editLocal, - Mod mod) + Mods.Mod mod) { - DrawToggleButtonTopRight(); + DrawToggleButton(); if (!DrawList(localTags, modTags, editLocal, out var changedTag, out var index)) return; @@ -115,20 +109,15 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer _modManager.DataEditor.ChangeModTag(mod, index, changedTag); } - public void DrawToggleButton() - { - using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), _isListOpen); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Tags.ToIconString(), new Vector2(ImGui.GetFrameHeight()), - "Add Predefined Tags...", false, true)) - _isListOpen = !_isListOpen; - } - - private void DrawToggleButtonTopRight() + private void DrawToggleButton() { ImGui.SameLine(ImGui.GetContentRegionMax().X - ImGui.GetFrameHeight() - (ImGui.GetScrollMaxY() > 0 ? ImGui.GetStyle().ItemInnerSpacing.X : 0)); - DrawToggleButton(); + using var color = ImRaii.PushColor(ImGuiCol.Button, ImGui.GetColorU32(ImGuiCol.ButtonActive), _isListOpen); + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Tags.ToIconString(), new Vector2(ImGui.GetFrameHeight()), + "Add Predefined Tags...", false, true)) + _isListOpen = !_isListOpen; } private bool DrawList(IReadOnlyCollection localTags, IReadOnlyCollection modTags, bool editLocal, out string changedTag, @@ -140,7 +129,7 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer if (!_isListOpen) return false; - ImUtf8.Text("Predefined Tags"u8); + ImGui.TextUnformatted("Predefined Tags"); ImGui.Separator(); var ret = false; @@ -165,101 +154,6 @@ public sealed class PredefinedTagManager : ISavable, IReadOnlyList, ISer return ret; } - private readonly List _selectedMods = []; - private readonly List<(int Index, int DataIndex)> _countedMods = []; - - private void PrepareLists(IEnumerable selection) - { - _selectedMods.Clear(); - _selectedMods.AddRange(selection); - _countedMods.EnsureCapacity(_selectedMods.Count); - while (_countedMods.Count < _selectedMods.Count) - _countedMods.Add((-1, -1)); - } - - public void DrawListMulti(IEnumerable selection) - { - if (!_isListOpen) - return; - - ImUtf8.Text("Predefined Tags"u8); - PrepareLists(selection); - - _enabledColor = ColorId.PredefinedTagAdd.Value(); - _disabledColor = ColorId.PredefinedTagRemove.Value(); - using var color = new ImRaii.Color(); - foreach (var (tag, idx) in _predefinedTags.Keys.WithIndex()) - { - var alreadyContained = 0; - var inModData = 0; - var missing = 0; - - foreach (var (modIndex, mod) in _selectedMods.Index()) - { - var tagIdx = mod.LocalTags.IndexOf(tag); - if (tagIdx >= 0) - { - ++alreadyContained; - _countedMods[modIndex] = (tagIdx, -1); - } - else - { - var dataIdx = mod.ModTags.IndexOf(tag); - if (dataIdx >= 0) - { - ++inModData; - _countedMods[modIndex] = (-1, dataIdx); - } - else - { - ++missing; - _countedMods[modIndex] = (-1, -1); - } - } - } - - using var id = ImRaii.PushId(idx); - var buttonWidth = CalcTextButtonWidth(tag); - // Prevent adding a new tag past the right edge of the popup - if (buttonWidth + ImGui.GetStyle().ItemSpacing.X >= ImGui.GetContentRegionAvail().X) - ImGui.NewLine(); - - var (usedColor, disabled, tt) = (missing, alreadyContained) switch - { - (> 0, _) => (_enabledColor, false, - $"Add this tag to {missing} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), - (_, > 0) => (_disabledColor, false, - $"Remove this tag from {alreadyContained} mods.{(inModData > 0 ? $" {inModData} mods contain it in their mod tags and are untouched." : string.Empty)}"), - _ => (_disabledColor, true, "This tag is already present in the mod tags of all selected mods."), - }; - color.Push(ImGuiCol.Button, usedColor); - if (ImUtf8.ButtonEx(tag, tt, new Vector2(buttonWidth, 0), disabled)) - { - if (missing > 0) - foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods)) - { - if (localIdx >= 0) - continue; - - _modManager.DataEditor.ChangeLocalTag(mod, mod.LocalTags.Count, tag); - } - else - foreach (var (mod, (localIdx, _)) in _selectedMods.Zip(_countedMods)) - { - if (localIdx < 0) - continue; - - _modManager.DataEditor.ChangeLocalTag(mod, localIdx, string.Empty); - } - } - ImGui.SameLine(); - - color.Pop(); - } - - ImGui.NewLine(); - } - private bool DrawColoredButton(string buttonLabel, int index, int tagIdx, bool inOther) { using var id = ImRaii.PushId(index); diff --git a/Penumbra/UI/ResourceWatcher/Record.cs b/Penumbra/UI/ResourceWatcher/Record.cs index ba718bc9..7338e5a9 100644 --- a/Penumbra/UI/ResourceWatcher/Record.cs +++ b/Penumbra/UI/ResourceWatcher/Record.cs @@ -1,7 +1,6 @@ using OtterGui.Classes; using Penumbra.Collections; using Penumbra.Enums; -using Penumbra.Interop; using Penumbra.Interop.Structs; using Penumbra.String; using Penumbra.String.Classes; @@ -11,11 +10,10 @@ namespace Penumbra.UI.ResourceWatcher; [Flags] public enum RecordType : byte { - Request = 0x01, - ResourceLoad = 0x02, - FileLoad = 0x04, - Destruction = 0x08, - ResourceComplete = 0x10, + Request = 0x01, + ResourceLoad = 0x02, + FileLoad = 0x04, + Destruction = 0x08, } internal unsafe struct Record @@ -35,7 +33,6 @@ internal unsafe struct Record public OptionalBool ReturnValue; public OptionalBool CustomLoad; public LoadState LoadState; - public uint OsThreadId; public static Record CreateRequest(CiByteString path, bool sync) @@ -56,28 +53,6 @@ internal unsafe struct Record AssociatedGameObject = string.Empty, LoadState = LoadState.None, Crc64 = 0, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), - }; - - public static Record CreateRequest(CiByteString path, bool sync, FullPath fullPath, ResolveData resolve) - => new() - { - Time = DateTime.UtcNow, - Path = fullPath.InternalName.IsOwned ? fullPath.InternalName : fullPath.InternalName.Clone(), - OriginalPath = path.IsOwned ? path : path.Clone(), - Collection = resolve.Valid ? resolve.ModCollection : null, - Handle = null, - ResourceType = ResourceExtensions.Type(path).ToFlag(), - Category = ResourceExtensions.Category(path).ToFlag(), - RefCount = 0, - RecordType = RecordType.Request, - Synchronously = sync, - ReturnValue = OptionalBool.Null, - CustomLoad = fullPath.InternalName != path, - AssociatedGameObject = string.Empty, - LoadState = LoadState.None, - Crc64 = fullPath.Crc64, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), }; public static Record CreateDefaultLoad(CiByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject) @@ -100,7 +75,6 @@ internal unsafe struct Record AssociatedGameObject = associatedGameObject, LoadState = handle->LoadState, Crc64 = 0, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), }; } @@ -123,7 +97,6 @@ internal unsafe struct Record AssociatedGameObject = associatedGameObject, LoadState = handle->LoadState, Crc64 = path.Crc64, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), }; public static Record CreateDestruction(ResourceHandle* handle) @@ -146,7 +119,6 @@ internal unsafe struct Record AssociatedGameObject = string.Empty, LoadState = handle->LoadState, Crc64 = 0, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), }; } @@ -168,40 +140,5 @@ internal unsafe struct Record AssociatedGameObject = string.Empty, LoadState = handle->LoadState, Crc64 = 0, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), }; - - public static Record CreateResourceComplete(CiByteString path, ResourceHandle* handle, Utf8GamePath originalPath, ReadOnlySpan additionalData) - => new() - { - Time = DateTime.UtcNow, - Path = CombinedPath(path, additionalData), - OriginalPath = originalPath.Path.IsOwned ? originalPath.Path : originalPath.Path.Clone(), - Collection = null, - Handle = handle, - ResourceType = handle->FileType.ToFlag(), - Category = handle->Category.ToFlag(), - RefCount = handle->RefCount, - RecordType = RecordType.ResourceComplete, - Synchronously = false, - ReturnValue = OptionalBool.Null, - CustomLoad = OptionalBool.Null, - AssociatedGameObject = string.Empty, - LoadState = handle->LoadState, - Crc64 = 0, - OsThreadId = ProcessThreadApi.GetCurrentThreadId(), - }; - - private static CiByteString CombinedPath(CiByteString path, ReadOnlySpan additionalData) - { - if (additionalData.Length is 0) - return path.IsOwned ? path : path.Clone(); - - fixed (byte* ptr = additionalData) - { - // If a path has additional data and is split, it is always in the form of |{additionalData}|{path}, - // so we can just read from the start of additional data - 1 and sum their length +2 for the pipes. - return new CiByteString(new ReadOnlySpan(ptr - 1, additionalData.Length + 2 + path.Length)).Clone(); - } - } } diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs index ee3613fc..d432e97e 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcher.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.System.Resource; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Widgets; @@ -47,10 +47,9 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService _table = new ResourceWatcherTable(config.Ephemeral, _records); _resources.ResourceRequested += OnResourceRequested; _destructor.Subscribe(OnResourceDestroyed, ResourceHandleDestructor.Priority.ResourceWatcher); - _loader.ResourceLoaded += OnResourceLoaded; - _loader.ResourceComplete += OnResourceComplete; - _loader.FileLoaded += OnFileLoaded; - _loader.PapRequested += OnPapRequested; + _loader.ResourceLoaded += OnResourceLoaded; + _loader.FileLoaded += OnFileLoaded; + _loader.PapRequested += OnPapRequested; UpdateFilter(_ephemeral.ResourceLoggingFilter, false); _newMaxEntries = _config.MaxResourceWatcherRecords; } @@ -58,19 +57,12 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService private void OnPapRequested(Utf8GamePath original, FullPath? _1, ResolveData _2) { if (_ephemeral.EnableResourceLogging && FilterMatch(original.Path, out var match)) - { Penumbra.Log.Information($"[ResourceLoader] [REQ] {match} was requested asynchronously."); - if (_1.HasValue) - Penumbra.Log.Information( - $"[ResourceLoader] [LOAD] Resolved {_1.Value.FullName} for {match} from collection {_2.ModCollection} for object 0x{_2.AssociatedGameObject:X}."); - } if (!_ephemeral.EnableResourceWatcher) return; - var record = _1.HasValue - ? Record.CreateRequest(original.Path, false, _1.Value, _2) - : Record.CreateRequest(original.Path, false); + var record = Record.CreateRequest(original.Path, false); if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record)) _newRecords.Enqueue(record); } @@ -81,10 +73,9 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService _records.TrimExcess(); _resources.ResourceRequested -= OnResourceRequested; _destructor.Unsubscribe(OnResourceDestroyed); - _loader.ResourceLoaded -= OnResourceLoaded; - _loader.ResourceComplete -= OnResourceComplete; - _loader.FileLoaded -= OnFileLoaded; - _loader.PapRequested -= OnPapRequested; + _loader.ResourceLoaded -= OnResourceLoaded; + _loader.FileLoaded -= OnFileLoaded; + _loader.PapRequested -= OnPapRequested; } private void Clear() @@ -250,7 +241,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService { var pathString = manipulatedPath != null ? $"custom file {name2} instead of {name}" : name; Penumbra.Log.Information( - $"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.Identity.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) "); + $"[ResourceLoader] [LOAD] [{handle->FileType}] Loaded {pathString} to 0x{(ulong)handle:X} using collection {data.ModCollection.AnonymizedName} for {Name(data, "no associated object.")} (Refcount {handle->RefCount}) "); } } @@ -264,24 +255,6 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService _newRecords.Enqueue(record); } - private unsafe void OnResourceComplete(ResourceHandle* resource, CiByteString path, Utf8GamePath original, - ReadOnlySpan additionalData, bool isAsync) - { - if (!isAsync) - return; - - if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match)) - Penumbra.Log.Information( - $"[ResourceLoader] [DONE] [{resource->FileType}] Finished loading {match} into 0x{(ulong)resource:X}, state {resource->LoadState}."); - - if (!_ephemeral.EnableResourceWatcher) - return; - - var record = Record.CreateResourceComplete(path, resource, original, additionalData); - if (!_ephemeral.OnlyAddMatchingResources || _table.WouldBeVisible(record)) - _newRecords.Enqueue(record); - } - private unsafe void OnFileLoaded(ResourceHandle* resource, CiByteString path, bool success, bool custom, ReadOnlySpan _) { if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match)) diff --git a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs index 97df095e..88b7120d 100644 --- a/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs +++ b/Penumbra/UI/ResourceWatcher/ResourceWatcherTable.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; @@ -30,8 +30,7 @@ internal sealed class ResourceWatcherTable : Table new LoadStateColumn { Label = "State" }, new RefCountColumn { Label = "#Ref" }, new DateColumn { Label = "Time" }, - new Crc64Column { Label = "Crc64" }, - new OsThreadColumn { Label = "TID" } + new Crc64Column { Label = "Crc64" } ) { } @@ -125,12 +124,11 @@ internal sealed class ResourceWatcherTable : Table { ImGui.TextUnformatted(item.RecordType switch { - RecordType.Request => "REQ", - RecordType.ResourceLoad => "LOAD", - RecordType.FileLoad => "FILE", - RecordType.Destruction => "DEST", - RecordType.ResourceComplete => "DONE", - _ => string.Empty, + RecordType.Request => "REQ", + RecordType.ResourceLoad => "LOAD", + RecordType.FileLoad => "FILE", + RecordType.Destruction => "DEST", + _ => string.Empty, }); } } @@ -169,7 +167,7 @@ internal sealed class ResourceWatcherTable : Table => 80 * UiHelpers.Scale; public override string ToName(Record item) - => (item.Collection != null ? item.Collection.Identity.Name : null) ?? string.Empty; + => item.Collection?.Name ?? string.Empty; } private sealed class ObjectColumn : ColumnString @@ -319,10 +317,10 @@ internal sealed class ResourceWatcherTable : Table { LoadState.None => FilterValue.HasFlag(LoadStateFlag.None), LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Success), + LoadState.Async => FilterValue.HasFlag(LoadStateFlag.Async), + LoadState.Failure => FilterValue.HasFlag(LoadStateFlag.Failed), LoadState.FailedSubResource => FilterValue.HasFlag(LoadStateFlag.FailedSub), - <= LoadState.Constructed => FilterValue.HasFlag(LoadStateFlag.Unknown), - < LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Async), - > LoadState.Success => FilterValue.HasFlag(LoadStateFlag.Failed), + _ => FilterValue.HasFlag(LoadStateFlag.Unknown), }; public override void DrawColumn(Record item, int _) @@ -334,12 +332,12 @@ internal sealed class ResourceWatcherTable : Table { LoadState.Success => (FontAwesomeIcon.CheckCircle, ColorId.IncreasedMetaValue.Value(), $"Successfully loaded ({(byte)item.LoadState})."), + LoadState.Async => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), $"Loading asynchronously ({(byte)item.LoadState})."), + LoadState.Failure => (FontAwesomeIcon.Times, ColorId.DecreasedMetaValue.Value(), + $"Failed to load ({(byte)item.LoadState})."), LoadState.FailedSubResource => (FontAwesomeIcon.ExclamationCircle, ColorId.DecreasedMetaValue.Value(), $"Dependencies failed to load ({(byte)item.LoadState})."), - <= LoadState.Constructed => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(), $"Not yet loaded ({(byte)item.LoadState})."), - < LoadState.Success => (FontAwesomeIcon.Clock, ColorId.FolderLine.Value(), $"Loading asynchronously ({(byte)item.LoadState})."), - > LoadState.Success => (FontAwesomeIcon.Times, ColorId.DecreasedMetaValue.Value(), - $"Failed to load ({(byte)item.LoadState})."), + _ => (FontAwesomeIcon.QuestionCircle, ColorId.UndefinedMod.Value(), $"Unknown state ({(byte)item.LoadState})."), }; using (var font = ImRaii.PushFont(UiBuilder.IconFont)) { @@ -454,19 +452,4 @@ internal sealed class ResourceWatcherTable : Table public override int Compare(Record lhs, Record rhs) => lhs.RefCount.CompareTo(rhs.RefCount); } - - private sealed class OsThreadColumn : ColumnString - { - public override float Width - => 60 * UiHelpers.Scale; - - public override string ToName(Record item) - => item.OsThreadId.ToString(); - - public override void DrawColumn(Record item, int _) - => ImGuiUtil.RightAlign(ToName(item)); - - public override int Compare(Record lhs, Record rhs) - => lhs.OsThreadId.CompareTo(rhs.OsThreadId); - } } diff --git a/Penumbra/UI/Tabs/ChangedItemsTab.cs b/Penumbra/UI/Tabs/ChangedItemsTab.cs index 4dc9474f..256b0d79 100644 --- a/Penumbra/UI/Tabs/ChangedItemsTab.cs +++ b/Penumbra/UI/Tabs/ChangedItemsTab.cs @@ -1,9 +1,8 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api.Enums; using Penumbra.Collections.Manager; @@ -27,36 +26,30 @@ public class ChangedItemsTab( private LowerString _changedItemFilter = LowerString.Empty; private LowerString _changedItemModFilter = LowerString.Empty; - private Vector2 _buttonSize; public void DrawContent() { collectionHeader.Draw(true); drawer.DrawTypeFilter(); var varWidth = DrawFilters(); - using var child = ImUtf8.Child("##changedItemsChild"u8, -Vector2.One); + using var child = ImRaii.Child("##changedItemsChild", -Vector2.One); if (!child) return; - _buttonSize = new Vector2(ImGui.GetStyle().ItemSpacing.Y + ImGui.GetFrameHeight()); - using var style = ImRaii.PushStyle(ImGuiStyleVar.CellPadding, Vector2.Zero) - .Push(ImGuiStyleVar.ItemSpacing, Vector2.Zero) - .Push(ImGuiStyleVar.FramePadding, Vector2.Zero) - .Push(ImGuiStyleVar.SelectableTextAlign, new Vector2(0.01f, 0.5f)); - - var skips = ImGuiClip.GetNecessarySkips(_buttonSize.Y); - using var list = ImUtf8.Table("##changedItems"u8, 3, ImGuiTableFlags.RowBg, -Vector2.One); + var height = ImGui.GetFrameHeight() + 2 * ImGui.GetStyle().CellPadding.Y; + var skips = ImGuiClip.GetNecessarySkips(height); + using var list = ImRaii.Table("##changedItems", 3, ImGuiTableFlags.RowBg, -Vector2.One); if (!list) return; const ImGuiTableColumnFlags flags = ImGuiTableColumnFlags.NoResize | ImGuiTableColumnFlags.WidthFixed; - ImUtf8.TableSetupColumn("items"u8, flags, 450 * UiHelpers.Scale); - ImUtf8.TableSetupColumn("mods"u8, flags, varWidth - 140 * UiHelpers.Scale); - ImUtf8.TableSetupColumn("id"u8, flags, 140 * UiHelpers.Scale); + ImGui.TableSetupColumn("items", flags, 450 * UiHelpers.Scale); + ImGui.TableSetupColumn("mods", flags, varWidth - 130 * UiHelpers.Scale); + ImGui.TableSetupColumn("id", flags, 130 * UiHelpers.Scale); var items = collectionManager.Active.Current.ChangedItems; var rest = ImGuiClip.FilteredClippedDraw(items, skips, FilterChangedItem, DrawChangedItemColumn); - ImGuiClip.DrawEndDummy(rest, _buttonSize.Y); + ImGuiClip.DrawEndDummy(rest, height); } /// Draw a pair of filters and return the variable width of the flexible column. @@ -74,25 +67,22 @@ public class ChangedItemsTab( } /// Apply the current filters. - private bool FilterChangedItem(KeyValuePair, IIdentifiedObjectData)> item) + private bool FilterChangedItem(KeyValuePair, IIdentifiedObjectData?)> item) => drawer.FilterChangedItem(item.Key, item.Value.Item2, _changedItemFilter) && (_changedItemModFilter.IsEmpty || item.Value.Item1.Any(m => m.Name.Contains(_changedItemModFilter))); /// Draw a full column for a changed item. - private void DrawChangedItemColumn(KeyValuePair, IIdentifiedObjectData)> item) + private void DrawChangedItemColumn(KeyValuePair, IIdentifiedObjectData?)> item) { ImGui.TableNextColumn(); - drawer.DrawCategoryIcon(item.Value.Item2, _buttonSize.Y); - ImGui.SameLine(0, 0); - var name = item.Value.Item2.ToName(item.Key); - var clicked = ImUtf8.Selectable(name, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 }); - drawer.ChangedItemHandling(item.Value.Item2, clicked); - + drawer.DrawCategoryIcon(item.Value.Item2); + ImGui.SameLine(); + drawer.DrawChangedItem(item.Key, item.Value.Item2); ImGui.TableNextColumn(); DrawModColumn(item.Value.Item1); ImGui.TableNextColumn(); - ChangedItemDrawer.DrawModelData(item.Value.Item2, _buttonSize.Y); + ChangedItemDrawer.DrawModelData(item.Value.Item2); } private void DrawModColumn(SingleArray mods) @@ -100,18 +90,19 @@ public class ChangedItemsTab( if (mods.Count <= 0) return; - var first = mods[0]; - if (ImUtf8.Selectable(first.Name.Text, false, ImGuiSelectableFlags.None, _buttonSize with { X = 0 }) + var first = mods[0]; + using var style = ImRaii.PushStyle(ImGuiStyleVar.SelectableTextAlign, new Vector2(0, 0.5f)); + if (ImGui.Selectable(first.Name, false, ImGuiSelectableFlags.None, new Vector2(0, ImGui.GetFrameHeight())) && ImGui.GetIO().KeyCtrl && first is Mod mod) communicator.SelectTab.Invoke(TabType.Mods, mod); - if (!ImGui.IsItemHovered()) - return; - - using var _ = ImRaii.Tooltip(); - ImUtf8.Text("Hold Control and click to jump to mod.\n"u8); - if (mods.Count > 1) - ImUtf8.Text("Other mods affecting this item:\n" + string.Join("\n", mods.Skip(1).Select(m => m.Name))); + if (ImGui.IsItemHovered()) + { + using var _ = ImRaii.Tooltip(); + ImGui.TextUnformatted("Hold Control and click to jump to mod.\n"); + if (mods.Count > 1) + ImGui.TextUnformatted("Other mods affecting this item:\n" + string.Join("\n", mods.Skip(1).Select(m => m.Name))); + } } } diff --git a/Penumbra/UI/Tabs/CollectionsTab.cs b/Penumbra/UI/Tabs/CollectionsTab.cs index f2a041eb..05a1f33b 100644 --- a/Penumbra/UI/Tabs/CollectionsTab.cs +++ b/Penumbra/UI/Tabs/CollectionsTab.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Game.ClientState.Objects; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Raii; using OtterGui.Services; using OtterGui.Widgets; diff --git a/Penumbra/UI/Tabs/ConfigTabBar.cs b/Penumbra/UI/Tabs/ConfigTabBar.cs index 43ae2488..28827ad9 100644 --- a/Penumbra/UI/Tabs/ConfigTabBar.cs +++ b/Penumbra/UI/Tabs/ConfigTabBar.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui.Services; using OtterGui.Widgets; using Penumbra.Api.Enums; diff --git a/Penumbra/UI/Tabs/Debug/AtchDrawer.cs b/Penumbra/UI/Tabs/Debug/AtchDrawer.cs index f136bacd..d9058083 100644 --- a/Penumbra/UI/Tabs/Debug/AtchDrawer.cs +++ b/Penumbra/UI/Tabs/Debug/AtchDrawer.cs @@ -1,11 +1,11 @@ -using Dalamud.Bindings.ImGui; -using OtterGui.Extensions; -using OtterGui.Text; +using ImGuiNET; +using OtterGui; +using OtterGui.Text; using Penumbra.GameData.Files; -using Penumbra.GameData.Files.AtchStructs; - -namespace Penumbra.UI.Tabs.Debug; - +using Penumbra.GameData.Files.AtchStructs; + +namespace Penumbra.UI.Tabs.Debug; + public static class AtchDrawer { public static void Draw(AtchFile file) diff --git a/Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs b/Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs index 471d770a..94c6cbd6 100644 --- a/Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs +++ b/Penumbra/UI/Tabs/Debug/CrashDataExtensions.cs @@ -1,4 +1,4 @@ -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.CrashHandler; diff --git a/Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs b/Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs index 672b8c79..c8e7f001 100644 --- a/Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs +++ b/Penumbra/UI/Tabs/Debug/CrashHandlerPanel.cs @@ -1,6 +1,6 @@ using System.Text.Json; -using Dalamud.Bindings.ImGui; using Dalamud.Interface.DragDrop; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/UI/Tabs/Debug/DebugConfigurationDrawer.cs b/Penumbra/UI/Tabs/Debug/DebugConfigurationDrawer.cs deleted file mode 100644 index 087670c1..00000000 --- a/Penumbra/UI/Tabs/Debug/DebugConfigurationDrawer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using OtterGui.Text; - -namespace Penumbra.UI.Tabs.Debug; - -public static class DebugConfigurationDrawer -{ - public static void Draw() - { - using var id = ImUtf8.CollapsingHeaderId("Debugging Options"u8); - if (!id) - return; - - ImUtf8.Checkbox("Log IMC File Replacements"u8, ref DebugConfiguration.WriteImcBytesToLog); - ImUtf8.Checkbox("Scan for Skin Material Attributes"u8, ref DebugConfiguration.UseSkinMaterialProcessing); - } -} diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 05f77e29..fc735d04 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -6,14 +6,13 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Group; using FFXIVClientStructs.FFXIV.Client.Game.Object; +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.UI.Agent; -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Colors; +using ImGuiNET; using Microsoft.Extensions.DependencyInjection; using OtterGui; using OtterGui.Classes; -using OtterGui.Extensions; using OtterGui.Services; using OtterGui.Text; using OtterGui.Widgets; @@ -36,13 +35,13 @@ using Penumbra.UI.Classes; using Penumbra.Util; using static OtterGui.Raii.ImRaii; using CharacterBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase; +using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; +using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; using ImGuiClip = OtterGui.ImGuiClip; using Penumbra.Api.IpcTester; -using Penumbra.GameData.Data; using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.GameData.Files.StainMapStructs; -using Penumbra.Interop; using Penumbra.String.Classes; using Penumbra.UI.AdvancedWindow.Materials; @@ -73,56 +72,50 @@ public class Diagnostics(ServiceManager provider) : IUiService public class DebugTab : Window, ITab, IUiService { - private readonly PerformanceTracker _performance; - private readonly Configuration _config; - private readonly CollectionManager _collectionManager; - private readonly ModManager _modManager; - private readonly ValidityChecker _validityChecker; - private readonly HttpApi _httpApi; - private readonly ActorManager _actors; - private readonly StainService _stains; - private readonly GlobalVariablesDrawer _globalVariablesDrawer; - private readonly ResourceManagerService _resourceManager; - private readonly ResourceLoader _resourceLoader; - private readonly CollectionResolver _collectionResolver; - private readonly DrawObjectState _drawObjectState; - private readonly PathState _pathState; - private readonly SubfileHelper _subfileHelper; - private readonly IdentifiedCollectionCache _identifiedCollectionCache; - private readonly CutsceneService _cutsceneService; - private readonly ModImportManager _modImporter; - private readonly ImportPopup _importPopup; - private readonly FrameworkManager _framework; - private readonly TextureManager _textureManager; - private readonly ShaderReplacementFixer _shaderReplacementFixer; - private readonly RedrawService _redraws; - private readonly DictEmote _emotes; - private readonly Diagnostics _diagnostics; - private readonly ObjectManager _objects; - private readonly IClientState _clientState; - private readonly IDataManager _dataManager; - private readonly IpcTester _ipcTester; - private readonly CrashHandlerPanel _crashHandlerPanel; - private readonly TexHeaderDrawer _texHeaderDrawer; - private readonly HookOverrideDrawer _hookOverrides; - private readonly RsfService _rsfService; - private readonly SchedulerResourceManagementService _schedulerService; - private readonly ObjectIdentification _objectIdentification; - private readonly RenderTargetDrawer _renderTargetDrawer; - private readonly ModMigratorDebug _modMigratorDebug; - private readonly ShapeInspector _shapeInspector; + private readonly PerformanceTracker _performance; + private readonly Configuration _config; + private readonly CollectionManager _collectionManager; + private readonly ModManager _modManager; + private readonly ValidityChecker _validityChecker; + private readonly HttpApi _httpApi; + private readonly ActorManager _actors; + private readonly StainService _stains; + private readonly CharacterUtility _characterUtility; + private readonly ResidentResourceManager _residentResources; + private readonly ResourceManagerService _resourceManager; + private readonly CollectionResolver _collectionResolver; + private readonly DrawObjectState _drawObjectState; + private readonly PathState _pathState; + private readonly SubfileHelper _subfileHelper; + private readonly IdentifiedCollectionCache _identifiedCollectionCache; + private readonly CutsceneService _cutsceneService; + private readonly ModImportManager _modImporter; + private readonly ImportPopup _importPopup; + private readonly FrameworkManager _framework; + private readonly TextureManager _textureManager; + private readonly ShaderReplacementFixer _shaderReplacementFixer; + private readonly RedrawService _redraws; + private readonly DictEmote _emotes; + private readonly Diagnostics _diagnostics; + private readonly ObjectManager _objects; + private readonly IClientState _clientState; + private readonly IDataManager _dataManager; + private readonly IpcTester _ipcTester; + private readonly CrashHandlerPanel _crashHandlerPanel; + private readonly TexHeaderDrawer _texHeaderDrawer; + private readonly HookOverrideDrawer _hookOverrides; + private readonly RsfService _rsfService; public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects, IClientState clientState, IDataManager dataManager, ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains, - ResourceManagerService resourceManager, ResourceLoader resourceLoader, CollectionResolver collectionResolver, + CharacterUtility characterUtility, ResidentResourceManager residentResources, + ResourceManagerService resourceManager, CollectionResolver collectionResolver, DrawObjectState drawObjectState, PathState pathState, SubfileHelper subfileHelper, IdentifiedCollectionCache identifiedCollectionCache, CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework, TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes, Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer, - HookOverrideDrawer hookOverrides, RsfService rsfService, GlobalVariablesDrawer globalVariablesDrawer, - SchedulerResourceManagementService schedulerService, ObjectIdentification objectIdentification, RenderTargetDrawer renderTargetDrawer, - ModMigratorDebug modMigratorDebug, ShapeInspector shapeInspector) + HookOverrideDrawer hookOverrides, RsfService rsfService) : base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse) { IsOpen = true; @@ -139,8 +132,9 @@ public class DebugTab : Window, ITab, IUiService _httpApi = httpApi; _actors = actors; _stains = stains; + _characterUtility = characterUtility; + _residentResources = residentResources; _resourceManager = resourceManager; - _resourceLoader = resourceLoader; _collectionResolver = collectionResolver; _drawObjectState = drawObjectState; _pathState = pathState; @@ -159,13 +153,7 @@ public class DebugTab : Window, ITab, IUiService _crashHandlerPanel = crashHandlerPanel; _texHeaderDrawer = texHeaderDrawer; _hookOverrides = hookOverrides; - _rsfService = rsfService; - _globalVariablesDrawer = globalVariablesDrawer; - _schedulerService = schedulerService; - _objectIdentification = objectIdentification; - _renderTargetDrawer = renderTargetDrawer; - _modMigratorDebug = modMigratorDebug; - _shapeInspector = shapeInspector; + _rsfService = rsfService; _objects = objects; _clientState = clientState; _dataManager = dataManager; @@ -191,24 +179,20 @@ public class DebugTab : Window, ITab, IUiService DrawDebugTabGeneral(); _crashHandlerPanel.Draw(); - DebugConfigurationDrawer.Draw(); _diagnostics.DrawDiagnostics(); DrawPerformanceTab(); DrawPathResolverDebug(); DrawActorsDebug(); DrawCollectionCaches(); _texHeaderDrawer.Draw(); - _modMigratorDebug.Draw(); + DrawDebugCharacterUtility(); DrawShaderReplacementFixer(); DrawData(); DrawCrcCache(); - DrawResourceLoader(); DrawResourceProblems(); - _renderTargetDrawer.Draw(); _hookOverrides.Draw(); DrawPlayerModelInfo(); - _globalVariablesDrawer.Draw(); - DrawCloudApi(); + DrawGlobalVariableInfo(); DrawDebugTabIpc(); } @@ -224,53 +208,17 @@ public class DebugTab : Window, ITab, IUiService if (collection.HasCache) { using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value()); - using var node = - TreeNode($"{collection.Identity.Name} (Change Counter {collection.Counters.Change})###{collection.Identity.Name}"); + using var node = TreeNode($"{collection.Name} (Change Counter {collection.ChangeCounter})###{collection.Name}"); if (!node) continue; color.Pop(); - using (var inheritanceNode = ImUtf8.TreeNode("Inheritance"u8)) - { - if (inheritanceNode) - { - using var table = ImUtf8.Table("table"u8, 3, - ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInnerV); - if (table) - { - var max = Math.Max( - Math.Max(collection.Inheritance.DirectlyInheritedBy.Count, collection.Inheritance.DirectlyInheritsFrom.Count), - collection.Inheritance.FlatHierarchy.Count); - for (var i = 0; i < max; ++i) - { - ImGui.TableNextColumn(); - if (i < collection.Inheritance.DirectlyInheritsFrom.Count) - ImUtf8.Text(collection.Inheritance.DirectlyInheritsFrom[i].Identity.Name); - else - ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight())); - ImGui.TableNextColumn(); - if (i < collection.Inheritance.DirectlyInheritedBy.Count) - ImUtf8.Text(collection.Inheritance.DirectlyInheritedBy[i].Identity.Name); - else - ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight())); - ImGui.TableNextColumn(); - if (i < collection.Inheritance.FlatHierarchy.Count) - ImUtf8.Text(collection.Inheritance.FlatHierarchy[i].Identity.Name); - else - ImGui.Dummy(new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetTextLineHeight())); - } - } - } - } - using (var resourceNode = ImUtf8.TreeNode("Custom Resources"u8)) { if (resourceNode) foreach (var (path, resource) in collection._cache!.CustomResources) - { ImUtf8.TreeNode($"{path} -> 0x{(ulong)resource.ResourceHandle:X}", ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose(); - } } using var modNode = ImUtf8.TreeNode("Enabled Mods"u8); @@ -293,7 +241,7 @@ public class DebugTab : Window, ITab, IUiService else { using var color = PushColor(ImGuiCol.Text, ColorId.UndefinedMod.Value()); - TreeNode($"{collection.Identity.Name} (Change Counter {collection.Counters.Change})", + TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})", ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose(); } } @@ -319,9 +267,9 @@ public class DebugTab : Window, ITab, IUiService { PrintValue("Penumbra Version", $"{_validityChecker.Version} {DebugVersionString}"); PrintValue("Git Commit Hash", _validityChecker.CommitHash); - PrintValue(TutorialService.SelectedCollection, _collectionManager.Active.Current.Identity.Name); + PrintValue(TutorialService.SelectedCollection, _collectionManager.Active.Current.Name); PrintValue(" has Cache", _collectionManager.Active.Current.HasCache.ToString()); - PrintValue(TutorialService.DefaultCollection, _collectionManager.Active.Default.Identity.Name); + PrintValue(TutorialService.DefaultCollection, _collectionManager.Active.Default.Name); PrintValue(" has Cache", _collectionManager.Active.Default.HasCache.ToString()); PrintValue("Mod Manager BasePath", _modManager.BasePath.Name); PrintValue("Mod Manager BasePath-Full", _modManager.BasePath.FullName); @@ -513,50 +461,30 @@ public class DebugTab : Window, ITab, IUiService if (!ImGui.CollapsingHeader("Actors")) return; - using (var objectTree = ImUtf8.TreeNode("Object Manager"u8)) + using var table = Table("##actors", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX); + if (!table) + return; + + DrawSpecial("Current Player", _actors.GetCurrentPlayer()); + DrawSpecial("Current Inspect", _actors.GetInspectPlayer()); + DrawSpecial("Current Card", _actors.GetCardPlayer()); + DrawSpecial("Current Glamour", _actors.GetGlamourPlayer()); + + foreach (var obj in _objects) { - if (objectTree) - { - _objects.DrawDebug(); - - using var table = Table("##actors", 8, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, - -Vector2.UnitX); - if (!table) - return; - - DrawSpecial("Current Player", _actors.GetCurrentPlayer()); - DrawSpecial("Current Inspect", _actors.GetInspectPlayer()); - DrawSpecial("Current Card", _actors.GetCardPlayer()); - DrawSpecial("Current Glamour", _actors.GetGlamourPlayer()); - - foreach (var obj in _objects) - { - ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL"); - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(obj.Address); - ImGui.TableNextColumn(); - if (obj.Address != nint.Zero) - Penumbra.Dynamis.DrawPointer((nint)((Character*)obj.Address)->GameObject.GetDrawObject()); - var identifier = _actors.FromObject(obj, out _, false, true, false); - ImGuiUtil.DrawTableColumn(_actors.ToString(identifier)); - var id = obj.AsObject->ObjectKind is ObjectKind.BattleNpc - ? $"{identifier.DataId} | {obj.AsObject->BaseId}" - : identifier.DataId.ToString(); - ImGuiUtil.DrawTableColumn(id); - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(obj.Address != nint.Zero ? *(nint*)obj.Address : nint.Zero); - ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"0x{obj.AsObject->EntityId:X}" : "NULL"); - ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero - ? obj.AsObject->IsCharacter() ? $"Character: {obj.AsCharacter->ObjectKind}" : "No Character" - : "NULL"); - } - } - } - - using (var shapeTree = ImUtf8.TreeNode("Shape Inspector"u8)) - { - if (shapeTree) - _shapeInspector.Draw(); + ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL"); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable($"0x{obj.Address:X}"); + ImGui.TableNextColumn(); + if (obj.Address != nint.Zero) + ImGuiUtil.CopyOnClickSelectable($"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}"); + var identifier = _actors.FromObject(obj, out _, false, true, false); + ImGuiUtil.DrawTableColumn(_actors.ToString(identifier)); + var id = obj.AsObject->ObjectKind is ObjectKind.BattleNpc + ? $"{identifier.DataId} | {obj.AsObject->BaseId}" + : identifier.DataId.ToString(); + ImGuiUtil.DrawTableColumn(id); } return; @@ -571,9 +499,6 @@ public class DebugTab : Window, ITab, IUiService ImGuiUtil.DrawTableColumn(string.Empty); ImGuiUtil.DrawTableColumn(_actors.ToString(id)); ImGuiUtil.DrawTableColumn(string.Empty); - ImGuiUtil.DrawTableColumn(string.Empty); - ImGuiUtil.DrawTableColumn(string.Empty); - ImGuiUtil.DrawTableColumn(string.Empty); } } @@ -587,37 +512,34 @@ public class DebugTab : Window, ITab, IUiService return; ImGui.TextUnformatted( - $"Last Game Object: 0x{_collectionResolver.IdentifyLastGameObjectCollection(true).AssociatedGameObject:X} ({_collectionResolver.IdentifyLastGameObjectCollection(true).ModCollection.Identity.Name})"); + $"Last Game Object: 0x{_collectionResolver.IdentifyLastGameObjectCollection(true).AssociatedGameObject:X} ({_collectionResolver.IdentifyLastGameObjectCollection(true).ModCollection.Name})"); using (var drawTree = TreeNode("Draw Object to Object")) { if (drawTree) { - using var table = Table("###DrawObjectResolverTable", 8, ImGuiTableFlags.SizingFixedFit); + using var table = Table("###DrawObjectResolverTable", 6, ImGuiTableFlags.SizingFixedFit); if (table) - foreach (var (drawObject, (gameObjectPtr, idx, child)) in _drawObjectState - .OrderBy(kvp => kvp.Value.Item2.Index) - .ThenBy(kvp => kvp.Value.Item3) - .ThenBy(kvp => kvp.Key.Address)) + foreach (var (drawObject, (gameObjectPtr, child)) in _drawObjectState + .OrderBy(kvp => ((GameObject*)kvp.Value.Item1)->ObjectIndex) + .ThenBy(kvp => kvp.Value.Item2) + .ThenBy(kvp => kvp.Key)) { + var gameObject = (GameObject*)gameObjectPtr; ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable($"{drawObject}"); - ImUtf8.DrawTableColumn($"{gameObjectPtr.Index}"); - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF, gameObjectPtr.Index != idx)) - { - ImUtf8.DrawTableColumn($"{idx}"); - } - ImUtf8.DrawTableColumn(child ? "Child"u8 : "Main"u8); + ImGuiUtil.CopyOnClickSelectable($"0x{drawObject:X}"); ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable($"{gameObjectPtr}"); - using (ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF, _objects[idx] != gameObjectPtr)) - { - ImUtf8.DrawTableColumn($"{_objects[idx]}"); - } - - ImUtf8.DrawTableColumn(gameObjectPtr.Utf8Name.Span); - var collection = _collectionResolver.IdentifyCollection(gameObjectPtr.AsObject, true); - ImUtf8.DrawTableColumn(collection.ModCollection.Identity.Name); + ImGui.TextUnformatted(gameObject->ObjectIndex.ToString()); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(child ? "Child" : "Main"); + ImGui.TableNextColumn(); + var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString()); + ImGuiUtil.CopyOnClickSelectable(address); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(name); + ImGui.TableNextColumn(); + var collection = _collectionResolver.IdentifyCollection(gameObject, true); + ImGui.TextUnformatted(collection.ModCollection.Name); } } } @@ -633,7 +555,7 @@ public class DebugTab : Window, ITab, IUiService ImGui.TableNextColumn(); ImGui.TextUnformatted($"{data.AssociatedGameObject:X}"); ImGui.TableNextColumn(); - ImGui.TextUnformatted(data.ModCollection.Identity.Name); + ImGui.TextUnformatted(data.ModCollection.Name); } } } @@ -646,12 +568,12 @@ public class DebugTab : Window, ITab, IUiService if (table) { ImGuiUtil.DrawTableColumn("Current Mtrl Data"); - ImGuiUtil.DrawTableColumn(_subfileHelper.MtrlData.ModCollection.Identity.Name); + ImGuiUtil.DrawTableColumn(_subfileHelper.MtrlData.ModCollection.Name); ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.MtrlData.AssociatedGameObject:X}"); ImGui.TableNextColumn(); ImGuiUtil.DrawTableColumn("Current Avfx Data"); - ImGuiUtil.DrawTableColumn(_subfileHelper.AvfxData.ModCollection.Identity.Name); + ImGuiUtil.DrawTableColumn(_subfileHelper.AvfxData.ModCollection.Name); ImGuiUtil.DrawTableColumn($"0x{_subfileHelper.AvfxData.AssociatedGameObject:X}"); ImGui.TableNextColumn(); @@ -663,7 +585,7 @@ public class DebugTab : Window, ITab, IUiService foreach (var (resource, resolve) in _subfileHelper) { ImGuiUtil.DrawTableColumn($"0x{resource:X}"); - ImGuiUtil.DrawTableColumn(resolve.ModCollection.Identity.Name); + ImGuiUtil.DrawTableColumn(resolve.ModCollection.Name); ImGuiUtil.DrawTableColumn($"0x{resolve.AssociatedGameObject:X}"); ImGuiUtil.DrawTableColumn($"{((ResourceHandle*)resource)->FileName()}"); } @@ -683,7 +605,7 @@ public class DebugTab : Window, ITab, IUiService ImGuiUtil.DrawTableColumn($"{((GameObject*)address)->ObjectIndex}"); ImGuiUtil.DrawTableColumn($"0x{address:X}"); ImGuiUtil.DrawTableColumn(identifier.ToString()); - ImGuiUtil.DrawTableColumn(collection.Identity.Name); + ImGuiUtil.DrawTableColumn(collection.Name); } } } @@ -729,9 +651,6 @@ public class DebugTab : Window, ITab, IUiService if (agent->Data == null) agent = &AgentBannerMIP.Instance()->AgentBannerInterface; - ImUtf8.Text("Agent: "); - ImGui.SameLine(0, 0); - Penumbra.Dynamis.DrawPointer((nint)agent); if (agent->Data != null) { using var table = Table("###PBannerTable", 2, ImGuiTableFlags.SizingFixedFit); @@ -750,20 +669,6 @@ public class DebugTab : Window, ITab, IUiService } } } - - using (var tmbCache = TreeNode("TMB Cache")) - { - if (tmbCache) - { - using var table = Table("###TmbTable", 2, ImGuiTableFlags.SizingFixedFit); - if (table) - foreach (var (id, name) in _schedulerService.ListedTmbs.OrderBy(kvp => kvp.Key)) - { - ImUtf8.DrawTableColumn($"{id:D6}"); - ImUtf8.DrawTableColumn(name.Span); - } - } - } } private void DrawData() @@ -772,40 +677,10 @@ public class DebugTab : Window, ITab, IUiService return; DrawEmotes(); - DrawActionTmbs(); DrawStainTemplates(); DrawAtch(); - DrawChangedItemTest(); } - private string _changedItemPath = string.Empty; - private readonly Dictionary _changedItems = []; - - private void DrawChangedItemTest() - { - using var node = TreeNode("Changed Item Test"); - if (!node) - return; - - if (ImUtf8.InputText("##ChangedItemTest"u8, ref _changedItemPath, "Changed Item File Path..."u8)) - { - _changedItems.Clear(); - _objectIdentification.Identify(_changedItems, _changedItemPath); - } - - if (_changedItems.Count == 0) - return; - - using var list = ImUtf8.ListBox("##ChangedItemList"u8, - new Vector2(ImGui.GetContentRegionAvail().X, 8 * ImGui.GetTextLineHeightWithSpacing())); - if (!list) - return; - - foreach (var item in _changedItems) - ImUtf8.Selectable(item.Key); - } - - private string _emoteSearchFile = string.Empty; private string _emoteSearchName = string.Empty; @@ -861,33 +736,6 @@ public class DebugTab : Window, ITab, IUiService ImGuiClip.DrawEndDummy(dummy, ImGui.GetTextLineHeightWithSpacing()); } - private string _tmbKeyFilter = string.Empty; - private CiByteString _tmbKeyFilterU8 = CiByteString.Empty; - - private void DrawActionTmbs() - { - using var mainTree = TreeNode("Action TMBs"); - if (!mainTree) - return; - - if (ImGui.InputText("Key", ref _tmbKeyFilter, 256)) - _tmbKeyFilterU8 = CiByteString.FromString(_tmbKeyFilter, out var r, MetaDataComputation.All) ? r : CiByteString.Empty; - using var table = Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, - new Vector2(-1, 12 * ImGui.GetTextLineHeightWithSpacing())); - if (!table) - return; - - var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeightWithSpacing()); - var dummy = ImGuiClip.FilteredClippedDraw(_schedulerService.ActionTmbs.OrderBy(r => r.Value), skips, - kvp => kvp.Key.Contains(_tmbKeyFilterU8), - p => - { - ImUtf8.DrawTableColumn($"{p.Value}"); - ImUtf8.DrawTableColumn(p.Key.Span); - }); - ImGuiClip.DrawEndDummy(dummy, ImGui.GetTextLineHeightWithSpacing()); - } - private void DrawStainTemplates() { using var mainTree = TreeNode("Staining Templates"); @@ -941,6 +789,68 @@ public class DebugTab : Window, ITab, IUiService } } + /// + /// Draw information about the character utility class from SE, + /// displaying all files, their sizes, the default files and the default sizes. + /// + private unsafe void DrawDebugCharacterUtility() + { + if (!ImGui.CollapsingHeader("Character Utility")) + return; + + using var table = Table("##CharacterUtility", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX); + if (!table) + return; + + for (var idx = 0; idx < CharacterUtility.ReverseIndices.Length; ++idx) + { + var intern = CharacterUtility.ReverseIndices[idx]; + var resource = _characterUtility.Address->Resource(idx); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"[{idx}]"); + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"0x{(ulong)resource:X}"); + ImGui.TableNextColumn(); + if (resource == null) + { + ImGui.TableNextRow(); + continue; + } + + UiHelpers.Text(resource); + ImGui.TableNextColumn(); + var data = (nint)resource->CsHandle.GetData(); + var length = resource->CsHandle.GetLength(); + if (ImGui.Selectable($"0x{data:X}")) + if (data != nint.Zero && length > 0) + ImGui.SetClipboardText(string.Join("\n", + new ReadOnlySpan((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2")))); + + ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard."); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(length.ToString()); + + ImGui.TableNextColumn(); + if (intern.Value != -1) + { + ImGui.Selectable($"0x{_characterUtility.DefaultResource(intern).Address:X}"); + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText(string.Join("\n", + new ReadOnlySpan((byte*)_characterUtility.DefaultResource(intern).Address, + _characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2")))); + + ImGuiUtil.HoverTooltip("Click to copy bytes to clipboard."); + + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"{_characterUtility.DefaultResource(intern).Size}"); + } + else + { + ImGui.TableNextColumn(); + } + } + } private void DrawShaderReplacementFixer() { @@ -1030,6 +940,45 @@ public class DebugTab : Window, ITab, IUiService ImGui.TextUnformatted($"{slowPathCallDeltas.Skin}"); } + /// Draw information about the resident resource files. + private unsafe void DrawDebugResidentResources() + { + using var tree = TreeNode("Resident Resources"); + if (!tree) + return; + + if (_residentResources.Address == null || _residentResources.Address->NumResources == 0) + return; + + using var table = Table("##ResidentResources", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, + -Vector2.UnitX); + if (!table) + return; + + for (var i = 0; i < _residentResources.Address->NumResources; ++i) + { + var resource = _residentResources.Address->ResourceList[i]; + ImGui.TableNextColumn(); + ImGui.TextUnformatted($"0x{(ulong)resource:X}"); + ImGui.TableNextColumn(); + UiHelpers.Text(resource); + } + } + + private static void DrawCopyableAddress(string label, nint address) + { + using (var _ = PushFont(UiBuilder.MonoFont)) + { + if (ImGui.Selectable($"0x{address:X16} {label}")) + ImGui.SetClipboardText($"{address:X16}"); + } + + ImGuiUtil.HoverTooltip("Click to copy address to clipboard."); + } + + private static unsafe void DrawCopyableAddress(string label, void* address) + => DrawCopyableAddress(label, (nint)address); + /// Draw information about the models, materials and resources currently loaded by the local player. private unsafe void DrawPlayerModelInfo() { @@ -1038,20 +987,20 @@ public class DebugTab : Window, ITab, IUiService if (!ImGui.CollapsingHeader($"Player Model Info: {name}##Draw") || player == null) return; - DrawCopyableAddress("PlayerCharacter"u8, player.Address); + DrawCopyableAddress("PlayerCharacter", player.Address); var model = (CharacterBase*)((Character*)player.Address)->GameObject.GetDrawObject(); if (model == null) return; - DrawCopyableAddress("CharacterBase"u8, model); + DrawCopyableAddress("CharacterBase", model); using (var t1 = Table("##table", 2, ImGuiTableFlags.SizingFixedFit)) { if (t1) { ImGuiUtil.DrawTableColumn("Flags"); - ImGuiUtil.DrawTableColumn($"{model->StateFlags}"); + ImGuiUtil.DrawTableColumn($"{model->UnkFlags_01:X2}"); ImGuiUtil.DrawTableColumn("Has Model In Slot Loaded"); ImGuiUtil.DrawTableColumn($"{model->HasModelInSlotLoaded:X8}"); ImGuiUtil.DrawTableColumn("Has Model Files In Slot Loaded"); @@ -1081,14 +1030,14 @@ public class DebugTab : Window, ITab, IUiService ImGui.TableNextColumn(); ImGui.TextUnformatted($"Slot {i}"); ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer((nint)imc); + ImGui.TextUnformatted(imc == null ? "NULL" : $"0x{(ulong)imc:X}"); ImGui.TableNextColumn(); if (imc != null) UiHelpers.Text(imc); var mdl = (RenderModel*)model->Models[i]; ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer((nint)mdl); + ImGui.TextUnformatted(mdl == null ? "NULL" : $"0x{(ulong)mdl:X}"); if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara) continue; @@ -1099,6 +1048,20 @@ public class DebugTab : Window, ITab, IUiService } } + /// Draw information about some game global variables. + private unsafe void DrawGlobalVariableInfo() + { + var header = ImGui.CollapsingHeader("Global Variables"); + ImGuiUtil.HoverTooltip("Draw information about global variables. Can provide useful starting points for a memory viewer."); + if (!header) + return; + + DrawCopyableAddress("CharacterUtility", _characterUtility.Address); + DrawCopyableAddress("ResidentResourceManager", _residentResources.Address); + DrawCopyableAddress("Device", Device.Instance()); + DrawDebugResidentResources(); + } + private string _crcInput = string.Empty; private FullPath _crcPath = FullPath.Empty; @@ -1133,35 +1096,6 @@ public class DebugTab : Window, ITab, IUiService } } - private unsafe void DrawResourceLoader() - { - if (!ImUtf8.CollapsingHeader("Resource Loader"u8)) - return; - - var ongoingLoads = _resourceLoader.OngoingLoads; - var ongoingLoadCount = ongoingLoads.Count; - ImUtf8.Text($"Ongoing Loads: {ongoingLoadCount}"); - - if (ongoingLoadCount == 0) - return; - - using var table = ImUtf8.Table("ongoingLoadTable"u8, 3); - if (!table) - return; - - ImUtf8.TableSetupColumn("Resource Handle"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); - ImUtf8.TableSetupColumn("Actual Path"u8, ImGuiTableColumnFlags.WidthStretch, 0.4f); - ImUtf8.TableSetupColumn("Original Path"u8, ImGuiTableColumnFlags.WidthStretch, 0.4f); - ImGui.TableHeadersRow(); - - foreach (var (handle, original) in ongoingLoads) - { - ImUtf8.DrawTableColumn($"0x{handle:X}"); - ImUtf8.DrawTableColumn(((ResourceHandle*)handle)->CsHandle.FileName); - ImUtf8.DrawTableColumn(original.Path.Span); - } - } - /// Draw resources with unusual reference count. private unsafe void DrawResourceProblems() { @@ -1202,55 +1136,11 @@ public class DebugTab : Window, ITab, IUiService } - private string _cloudTesterPath = string.Empty; - private bool? _cloudTesterReturn; - private Exception? _cloudTesterError; - - private void DrawCloudApi() - { - if (!ImUtf8.CollapsingHeader("Cloud API"u8)) - return; - - using var id = ImRaii.PushId("CloudApiTester"u8); - - if (ImUtf8.InputText("Path"u8, ref _cloudTesterPath, flags: ImGuiInputTextFlags.EnterReturnsTrue)) - { - try - { - _cloudTesterReturn = CloudApi.IsCloudSynced(_cloudTesterPath); - _cloudTesterError = null; - } - catch (Exception e) - { - _cloudTesterReturn = null; - _cloudTesterError = e; - } - } - - if (_cloudTesterReturn.HasValue) - ImUtf8.Text($"Is Cloud Synced? {_cloudTesterReturn}"); - - if (_cloudTesterError is not null) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImUtf8.Text($"{_cloudTesterError}"); - } - } - - /// Draw information about IPC options and availability. private void DrawDebugTabIpc() { - if (!ImUtf8.CollapsingHeader("IPC"u8)) - return; - - using (var tree = ImUtf8.TreeNode("Dynamis"u8)) - { - if (tree) - Penumbra.Dynamis.DrawDebugInfo(); - } - - _ipcTester.Draw(); + if (ImGui.CollapsingHeader("IPC")) + _ipcTester.Draw(); } /// Helper to print a property and its value in a 2-column table. @@ -1273,14 +1163,4 @@ public class DebugTab : Window, ITab, IUiService _config.Ephemeral.DebugSeparateWindow = false; _config.Ephemeral.Save(); } - - public static unsafe void DrawCopyableAddress(ReadOnlySpan label, void* address) - => DrawCopyableAddress(label, (nint)address); - - public static unsafe void DrawCopyableAddress(ReadOnlySpan label, nint address) - { - Penumbra.Dynamis.DrawPointer(address); - ImUtf8.SameLineInner(); - ImUtf8.Text(label); - } } diff --git a/Penumbra/UI/Tabs/Debug/GlobalVariablesDrawer.cs b/Penumbra/UI/Tabs/Debug/GlobalVariablesDrawer.cs deleted file mode 100644 index f0ab1125..00000000 --- a/Penumbra/UI/Tabs/Debug/GlobalVariablesDrawer.cs +++ /dev/null @@ -1,252 +0,0 @@ -using Dalamud.Bindings.ImGui; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.System.Scheduler; -using FFXIVClientStructs.FFXIV.Client.System.Scheduler.Resource; -using FFXIVClientStructs.Interop; -using FFXIVClientStructs.STD; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Interop.Services; -using Penumbra.Interop.Structs; -using Penumbra.String; -using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; - -namespace Penumbra.UI.Tabs.Debug; - -public unsafe class GlobalVariablesDrawer( - CharacterUtility characterUtility, - ResidentResourceManager residentResources, - SchedulerResourceManagementService scheduler) : IUiService -{ - /// Draw information about some game global variables. - public void Draw() - { - var header = ImUtf8.CollapsingHeader("Global Variables"u8); - ImUtf8.HoverTooltip("Draw information about global variables. Can provide useful starting points for a memory viewer."u8); - if (!header) - return; - - var actionManager = (ActionTimelineManager**)ActionTimelineManager.Instance(); - using (ImUtf8.Group()) - { - Penumbra.Dynamis.DrawPointer(characterUtility.Address); - Penumbra.Dynamis.DrawPointer(residentResources.Address); - Penumbra.Dynamis.DrawPointer(ScheduleManagement.Instance()); - Penumbra.Dynamis.DrawPointer(actionManager); - Penumbra.Dynamis.DrawPointer(actionManager != null ? *actionManager : null); - Penumbra.Dynamis.DrawPointer(scheduler.Address); - Penumbra.Dynamis.DrawPointer(scheduler.Address != null ? *scheduler.Address : null); - Penumbra.Dynamis.DrawPointer(Device.Instance()); - } - - ImGui.SameLine(); - using (ImUtf8.Group()) - { - ImUtf8.Text("CharacterUtility"u8); - ImUtf8.Text("ResidentResourceManager"u8); - ImUtf8.Text("ScheduleManagement"u8); - ImUtf8.Text("ActionTimelineManager*"u8); - ImUtf8.Text("ActionTimelineManager"u8); - ImUtf8.Text("SchedulerResourceManagement*"u8); - ImUtf8.Text("SchedulerResourceManagement"u8); - ImUtf8.Text("Device"u8); - } - - DrawCharacterUtility(); - DrawResidentResources(); - DrawSchedulerResourcesMap(); - DrawSchedulerResourcesList(); - } - - - /// - /// Draw information about the character utility class from SE, - /// displaying all files, their sizes, the default files and the default sizes. - /// - private void DrawCharacterUtility() - { - using var tree = ImUtf8.TreeNode("Character Utility"u8); - if (!tree) - return; - - using var table = ImUtf8.Table("##CharacterUtility"u8, 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, - -Vector2.UnitX); - if (!table) - return; - - for (var idx = 0; idx < CharacterUtility.ReverseIndices.Length; ++idx) - { - var intern = CharacterUtility.ReverseIndices[idx]; - var resource = characterUtility.Address->Resource(idx); - ImUtf8.DrawTableColumn($"[{idx}]"); - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(resource); - if (resource == null) - { - ImGui.TableNextRow(); - continue; - } - - ImUtf8.DrawTableColumn(resource->CsHandle.FileName.AsSpan()); - ImGui.TableNextColumn(); - var data = (nint)resource->CsHandle.GetData(); - var length = resource->CsHandle.GetLength(); - Penumbra.Dynamis.DrawPointer(data); - ImUtf8.DrawTableColumn(length.ToString()); - ImGui.TableNextColumn(); - if (intern.Value != -1) - { - Penumbra.Dynamis.DrawPointer(characterUtility.DefaultResource(intern).Address); - ImUtf8.DrawTableColumn($"{characterUtility.DefaultResource(intern).Size}"); - } - else - { - ImGui.TableNextColumn(); - } - } - } - - /// Draw information about the resident resource files. - private void DrawResidentResources() - { - using var tree = ImUtf8.TreeNode("Resident Resources"u8); - if (!tree) - return; - - if (residentResources.Address == null || residentResources.Address->NumResources == 0) - return; - - using var table = ImUtf8.Table("##ResidentResources"u8, 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, - -Vector2.UnitX); - if (!table) - return; - - for (var idx = 0; idx < residentResources.Address->NumResources; ++idx) - { - var resource = residentResources.Address->ResourceList[idx]; - ImUtf8.DrawTableColumn($"[{idx}]"); - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(resource); - if (resource == null) - { - ImGui.TableNextRow(); - continue; - } - - ImUtf8.DrawTableColumn(resource->CsHandle.FileName.AsSpan()); - ImGui.TableNextColumn(); - var data = (nint)resource->CsHandle.GetData(); - var length = resource->CsHandle.GetLength(); - Penumbra.Dynamis.DrawPointer(data); - ImUtf8.DrawTableColumn(length.ToString()); - } - } - - private string _schedulerFilterList = string.Empty; - private string _schedulerFilterMap = string.Empty; - private CiByteString _schedulerFilterListU8 = CiByteString.Empty; - private CiByteString _schedulerFilterMapU8 = CiByteString.Empty; - private int _shownResourcesList = 0; - private int _shownResourcesMap = 0; - - private void DrawSchedulerResourcesMap() - { - using var tree = ImUtf8.TreeNode("Scheduler Resources (Map)"u8); - if (!tree) - return; - - if (scheduler.Address == null || scheduler.Scheduler == null) - return; - - if (ImUtf8.InputText("##SchedulerMapFilter"u8, ref _schedulerFilterMap, "Filter..."u8)) - _schedulerFilterMapU8 = CiByteString.FromString(_schedulerFilterMap, out var t, MetaDataComputation.All, false) - ? t - : CiByteString.Empty; - ImUtf8.Text($"{_shownResourcesMap} / {scheduler.Scheduler->Resources.LongCount}"); - using var table = ImUtf8.Table("##SchedulerMapResources"u8, 10, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, - -Vector2.UnitX); - if (!table) - return; - - // TODO Remove cast when it'll have the right type in CS. - var map = (StdMap>*)&scheduler.Scheduler->Resources; - var total = 0; - _shownResourcesMap = 0; - foreach (var (key, resourcePtr) in *map) - { - var resource = resourcePtr.Value; - if (_schedulerFilterMap.Length is 0 || resource->Name.Buffer.IndexOf(_schedulerFilterMapU8.Span) >= 0) - { - ImUtf8.DrawTableColumn($"[{total:D4}]"); - ImUtf8.DrawTableColumn($"{resource->Name.Unk1}"); - ImUtf8.DrawTableColumn(new CiByteString(resource->Name.Buffer, MetaDataComputation.None).Span); - ImUtf8.DrawTableColumn($"{resource->Consumers}"); - ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(resource); - ImGui.TableNextColumn(); - var resourceHandle = *((ResourceHandle**)resource + 3); - Penumbra.Dynamis.DrawPointer(resourceHandle); - ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span); - ImGui.TableNextColumn(); - uint dataLength = 0; - Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength)); - ImUtf8.DrawTableColumn($"{dataLength}"); - ++_shownResourcesMap; - } - - ++total; - } - } - - private void DrawSchedulerResourcesList() - { - using var tree = ImUtf8.TreeNode("Scheduler Resources (List)"u8); - if (!tree) - return; - - if (scheduler.Address == null || scheduler.Scheduler == null) - return; - - if (ImUtf8.InputText("##SchedulerListFilter"u8, ref _schedulerFilterList, "Filter..."u8)) - _schedulerFilterListU8 = CiByteString.FromString(_schedulerFilterList, out var t, MetaDataComputation.All, false) - ? t - : CiByteString.Empty; - ImUtf8.Text($"{_shownResourcesList} / {scheduler.Scheduler->Resources.LongCount}"); - using var table = ImUtf8.Table("##SchedulerListResources"u8, 10, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit, - -Vector2.UnitX); - if (!table) - return; - - var resource = scheduler.Scheduler->Begin; - var total = 0; - _shownResourcesList = 0; - while (resource != null && total < scheduler.Scheduler->Resources.Count) - { - if (_schedulerFilterList.Length is 0 || resource->Name.Buffer.IndexOf(_schedulerFilterListU8.Span) >= 0) - { - ImUtf8.DrawTableColumn($"[{total:D4}]"); - ImUtf8.DrawTableColumn($"{resource->Name.Unk1}"); - ImUtf8.DrawTableColumn(new CiByteString(resource->Name.Buffer, MetaDataComputation.None).Span); - ImUtf8.DrawTableColumn($"{resource->Consumers}"); - ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key - ImGui.TableNextColumn(); - Penumbra.Dynamis.DrawPointer(resource); - ImGui.TableNextColumn(); - var resourceHandle = *((ResourceHandle**)resource + 3); - Penumbra.Dynamis.DrawPointer(resourceHandle); - ImGui.TableNextColumn(); - ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span); - ImGui.TableNextColumn(); - uint dataLength = 0; - Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength)); - ImUtf8.DrawTableColumn($"{dataLength}"); - ++_shownResourcesList; - } - - resource = resource->Previous; - ++total; - } - } -} diff --git a/Penumbra/UI/Tabs/Debug/HookOverrideDrawer.cs b/Penumbra/UI/Tabs/Debug/HookOverrideDrawer.cs index f1024950..e8ff9b9c 100644 --- a/Penumbra/UI/Tabs/Debug/HookOverrideDrawer.cs +++ b/Penumbra/UI/Tabs/Debug/HookOverrideDrawer.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Plugin; +using ImGuiNET; using OtterGui.Services; using OtterGui.Text; using Penumbra.Interop.Hooks; diff --git a/Penumbra/UI/Tabs/Debug/ModMigratorDebug.cs b/Penumbra/UI/Tabs/Debug/ModMigratorDebug.cs deleted file mode 100644 index e6e01107..00000000 --- a/Penumbra/UI/Tabs/Debug/ModMigratorDebug.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Dalamud.Bindings.ImGui; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Services; - -namespace Penumbra.UI.Tabs.Debug; - -public class ModMigratorDebug(ModMigrator migrator) : IUiService -{ - private string _inputPath = string.Empty; - private string _outputPath = string.Empty; - private Task? _indexTask; - private Task? _mdlTask; - - public void Draw() - { - if (!ImUtf8.CollapsingHeaderId("Mod Migrator"u8)) - return; - - ImUtf8.InputText("##input"u8, ref _inputPath, "Input Path..."u8); - ImUtf8.InputText("##output"u8, ref _outputPath, "Output Path..."u8); - - if (ImUtf8.ButtonEx("Create Index Texture"u8, "Requires input to be a path to a normal texture."u8, default, _inputPath.Length == 0 - || _outputPath.Length == 0 - || _indexTask is - { - IsCompleted: false, - })) - _indexTask = migrator.CreateIndexFile(_inputPath, _outputPath); - - if (_indexTask is not null) - { - ImGui.SameLine(); - ImUtf8.TextFrameAligned($"{_indexTask.Status}"); - } - - if (ImUtf8.ButtonEx("Update Model File"u8, "Requires input to be a path to a mdl."u8, default, _inputPath.Length == 0 - || _outputPath.Length == 0 - || _mdlTask is - { - IsCompleted: false, - })) - _mdlTask = Task.Run(() => - { - File.Copy(_inputPath, _outputPath, true); - MigrationManager.TryMigrateSingleModel(_outputPath, false); - }); - - if (_mdlTask is not null) - { - ImGui.SameLine(); - ImUtf8.TextFrameAligned($"{_mdlTask.Status}"); - } - } -} diff --git a/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs b/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs deleted file mode 100644 index d497f90a..00000000 --- a/Penumbra/UI/Tabs/Debug/RenderTargetDrawer.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Dalamud.Bindings.ImGui; -using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; -using FFXIVClientStructs.FFXIV.Client.Graphics.Render; -using OtterGui; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Interop.Hooks; -using Penumbra.Interop.Hooks.PostProcessing; -using Penumbra.Services; - -namespace Penumbra.UI.Tabs.Debug; - -public class RenderTargetDrawer(RenderTargetHdrEnabler renderTargetHdrEnabler, DalamudConfigService dalamudConfig, Configuration config) : IUiService -{ - private void DrawStatistics() - { - using (ImUtf8.Group()) - { - ImUtf8.Text("Wait For Plugins (Now)"); - ImUtf8.Text("Wait For Plugins (First Launch)"); - - ImUtf8.Text("HDR Enabled (Now)"); - ImUtf8.Text("HDR Enabled (First Launch)"); - - ImUtf8.Text("HDR Hook Overriden (Now)"); - ImUtf8.Text("HDR Hook Overriden (First Launch)"); - - ImUtf8.Text("HDR Detour Called"); - ImUtf8.Text("Penumbra Reload Count"); - } - ImGui.SameLine(); - using (ImUtf8.Group()) - { - ImUtf8.Text($"{(dalamudConfig.GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool w) ? w.ToString() : "Unknown")}"); - ImUtf8.Text($"{renderTargetHdrEnabler.FirstLaunchWaitForPluginsState?.ToString() ?? "Unknown"}"); - - ImUtf8.Text($"{config.HdrRenderTargets}"); - ImUtf8.Text($"{renderTargetHdrEnabler.FirstLaunchHdrState}"); - - ImUtf8.Text($"{HookOverrides.Instance.PostProcessing.RenderTargetManagerInitialize}"); - ImUtf8.Text($"{!renderTargetHdrEnabler.FirstLaunchHdrHookOverrideState}"); - - ImUtf8.Text($"{renderTargetHdrEnabler.HdrEnabledSuccess}"); - ImUtf8.Text($"{renderTargetHdrEnabler.PenumbraReloadCount}"); - } - } - - /// Draw information about render targets. - public unsafe void Draw() - { - if (!ImUtf8.CollapsingHeader("Render Targets"u8)) - return; - - DrawStatistics(); - ImUtf8.Dummy(0); - ImGui.Separator(); - ImUtf8.Dummy(0); - var report = renderTargetHdrEnabler.TextureReport; - if (report == null) - { - ImUtf8.Text("The RenderTargetManager report has not been gathered."u8); - ImUtf8.Text("Please restart the game with Debug Mode and Wait for Plugins on Startup enabled to fill this section."u8); - return; - } - - using var table = ImUtf8.Table("##RenderTargetTable"u8, 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); - if (!table) - return; - - ImUtf8.TableSetupColumn("Offset"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); - ImUtf8.TableSetupColumn("Creation Order"u8, ImGuiTableColumnFlags.WidthStretch, 0.15f); - ImUtf8.TableSetupColumn("Original Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); - ImUtf8.TableSetupColumn("Current Texture Format"u8, ImGuiTableColumnFlags.WidthStretch, 0.2f); - ImUtf8.TableSetupColumn("Comment"u8, ImGuiTableColumnFlags.WidthStretch, 0.3f); - ImGui.TableHeadersRow(); - - foreach (var record in report) - { - ImUtf8.DrawTableColumn($"0x{record.Offset:X}"); - ImUtf8.DrawTableColumn($"{record.CreationOrder}"); - ImUtf8.DrawTableColumn($"{record.OriginalTextureFormat}"); - ImGui.TableNextColumn(); - var texture = *(Texture**)((nint)RenderTargetManager.Instance() - + record.Offset); - if (texture != null) - { - using var color = Dalamud.Interface.Utility.Raii.ImRaii.PushColor(ImGuiCol.Text, ImGuiUtil.HalfBlendText(0xFF), - texture->TextureFormat != record.OriginalTextureFormat); - ImUtf8.Text($"{texture->TextureFormat}"); - } - - ImGui.TableNextColumn(); - var forcedConfig = RenderTargetHdrEnabler.GetForcedTextureConfig(record.CreationOrder); - if (forcedConfig.HasValue) - ImUtf8.Text(forcedConfig.Value.Comment); - } - } -} diff --git a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs b/Penumbra/UI/Tabs/Debug/ShapeInspector.cs deleted file mode 100644 index 4c3b43bf..00000000 --- a/Penumbra/UI/Tabs/Debug/ShapeInspector.cs +++ /dev/null @@ -1,284 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface; -using Dalamud.Interface.Utility.Raii; -using OtterGui.Extensions; -using OtterGui.Services; -using OtterGui.Text; -using Penumbra.Collections.Cache; -using Penumbra.GameData.Enums; -using Penumbra.GameData.Interop; -using Penumbra.Interop.PathResolving; -using Penumbra.Meta; -using Penumbra.Meta.Manipulations; - -namespace Penumbra.UI.Tabs.Debug; - -public class ShapeInspector(ObjectManager objects, CollectionResolver resolver) : IUiService -{ - private int _objectIndex; - - public void Draw() - { - ImUtf8.InputScalar("Object Index"u8, ref _objectIndex); - var actor = objects[0]; - if (!actor.IsCharacter) - { - ImUtf8.Text("No valid character."u8); - return; - } - - var human = actor.Model; - if (!human.IsHuman) - { - ImUtf8.Text("No valid character."u8); - return; - } - - DrawCollectionShapeCache(actor); - DrawCharacterShapes(human); - DrawCollectionAttributeCache(actor); - DrawCharacterAttributes(human); - } - - private unsafe void DrawCollectionAttributeCache(Actor actor) - { - var data = resolver.IdentifyCollection(actor.AsObject, true); - using var treeNode1 = ImUtf8.TreeNode($"Collection Attribute Cache ({data.ModCollection})"); - if (!treeNode1.Success || !data.ModCollection.HasCache) - return; - - using var table = ImUtf8.Table("##aCache"u8, 2, ImGuiTableFlags.RowBg); - if (!table) - return; - - ImUtf8.TableSetupColumn("Attribute"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthStretch); - - ImGui.TableHeadersRow(); - foreach (var (attribute, set) in data.ModCollection.MetaCache!.Atr.Data.OrderBy(a => a.Key)) - { - ImUtf8.DrawTableColumn(attribute.AsSpan); - DrawValues(attribute, set); - } - } - - private unsafe void DrawCollectionShapeCache(Actor actor) - { - var data = resolver.IdentifyCollection(actor.AsObject, true); - using var treeNode1 = ImUtf8.TreeNode($"Collection Shape Cache ({data.ModCollection})"); - if (!treeNode1.Success || !data.ModCollection.HasCache) - return; - - using var table = ImUtf8.Table("##sCache"u8, 3, ImGuiTableFlags.RowBg); - if (!table) - return; - - ImUtf8.TableSetupColumn("Condition"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Shape"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("State"u8, ImGuiTableColumnFlags.WidthStretch); - - ImGui.TableHeadersRow(); - foreach (var condition in Enum.GetValues()) - { - foreach (var (shape, set) in data.ModCollection.MetaCache!.Shp.State(condition).OrderBy(shp => shp.Key)) - { - ImUtf8.DrawTableColumn(condition.ToString()); - ImUtf8.DrawTableColumn(shape.AsSpan); - DrawValues(shape, set); - } - } - } - - private static void DrawValues(in ShapeAttributeString shapeAttribute, ShapeAttributeHashSet set) - { - ImGui.TableNextColumn(); - - if (set.All is { } value) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value); - ImUtf8.Text("All, "u8); - ImGui.SameLine(0, 0); - } - - foreach (var slot in ShapeAttributeManager.UsedModels) - { - if (set[slot] is not { } value2) - continue; - - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value2); - ImUtf8.Text($"All {slot.ToName()}, "); - ImGui.SameLine(0, 0); - } - - foreach (var gr in ShapeAttributeHashSet.GenderRaceValues.Skip(1)) - { - if (set[gr] is { } value3) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value3); - ImUtf8.Text($"All {gr.ToName()}, "); - ImGui.SameLine(0, 0); - } - else - { - foreach (var slot in ShapeAttributeManager.UsedModels) - { - if (set[slot, gr] is not { } value4) - continue; - - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !value4); - ImUtf8.Text($"All {gr.ToName()} {slot.ToName()}, "); - ImGui.SameLine(0, 0); - } - } - } - - foreach (var ((slot, id), flags) in set) - { - if ((flags & 3) is not 0) - { - var enabled = (flags & 1) is 1; - - if (set[slot, GenderRace.Unknown] != enabled) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !enabled); - ImUtf8.Text($"{slot.ToName()} {id.Id:D4}, "); - ImGui.SameLine(0, 0); - } - } - else - { - var currentIndex = BitOperations.TrailingZeroCount(flags) / 2; - var currentFlags = flags >> (2 * currentIndex); - while (currentIndex < ShapeAttributeHashSet.GenderRaceValues.Count) - { - var enabled = (currentFlags & 1) is 1; - var gr = ShapeAttributeHashSet.GenderRaceValues[currentIndex]; - if (set[slot, gr] != enabled) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled), !enabled); - ImUtf8.Text($"{gr.ToName()} {slot.ToName()} #{id.Id:D4}, "); - ImGui.SameLine(0, 0); - } - - currentFlags &= ~0x3u; - currentIndex += BitOperations.TrailingZeroCount(currentFlags) / 2; - currentFlags = flags >> (2 * currentIndex); - } - } - } - } - - private unsafe void DrawCharacterShapes(Model human) - { - using var treeNode2 = ImUtf8.TreeNode("Character Model Shapes"u8); - if (!treeNode2) - return; - - using var table = ImUtf8.Table("##shapes"u8, 7, ImGuiTableFlags.RowBg); - if (!table) - return; - - ImUtf8.TableSetupColumn("#"u8, ImGuiTableColumnFlags.WidthFixed, 25 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Slot"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Address"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14); - ImUtf8.TableSetupColumn("Mask"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8); - ImUtf8.TableSetupColumn("ID"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4); - ImUtf8.TableSetupColumn("Count"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Shapes"u8, ImGuiTableColumnFlags.WidthStretch); - - ImGui.TableHeadersRow(); - - var disabledColor = ImGui.GetColorU32(ImGuiCol.TextDisabled); - for (var i = 0; i < human.AsHuman->SlotCount; ++i) - { - ImUtf8.DrawTableColumn($"{(uint)i:D2}"); - ImUtf8.DrawTableColumn(((HumanSlot)i).ToName()); - - ImGui.TableNextColumn(); - var model = human.AsHuman->Models[i]; - Penumbra.Dynamis.DrawPointer((nint)model); - if (model is not null) - { - var mask = model->EnabledShapeKeyIndexMask; - ImUtf8.DrawTableColumn($"{mask:X8}"); - ImUtf8.DrawTableColumn($"{human.GetModelId((HumanSlot)i):D4}"); - ImUtf8.DrawTableColumn($"{model->ModelResourceHandle->Shapes.Count}"); - ImGui.TableNextColumn(); - foreach (var ((shape, flag), idx) in model->ModelResourceHandle->Shapes.WithIndex()) - { - var disabled = (mask & (1u << flag)) is 0; - using var color = ImRaii.PushColor(ImGuiCol.Text, disabledColor, disabled); - ImUtf8.Text(shape.AsSpan()); - ImGui.SameLine(0, 0); - ImUtf8.Text(", "u8); - if (idx % 8 < 7) - ImGui.SameLine(0, 0); - } - } - else - { - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - } - } - } - - private unsafe void DrawCharacterAttributes(Model human) - { - using var treeNode2 = ImUtf8.TreeNode("Character Model Attributes"u8); - if (!treeNode2) - return; - - using var table = ImUtf8.Table("##attributes"u8, 7, ImGuiTableFlags.RowBg); - if (!table) - return; - - ImUtf8.TableSetupColumn("#"u8, ImGuiTableColumnFlags.WidthFixed, 25 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Slot"u8, ImGuiTableColumnFlags.WidthFixed, 150 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Address"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 14); - ImUtf8.TableSetupColumn("Mask"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 8); - ImUtf8.TableSetupColumn("ID"u8, ImGuiTableColumnFlags.WidthFixed, UiBuilder.MonoFont.GetCharAdvance('0') * 4); - ImUtf8.TableSetupColumn("Count"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImUtf8.GlobalScale); - ImUtf8.TableSetupColumn("Attributes"u8, ImGuiTableColumnFlags.WidthStretch); - - ImGui.TableHeadersRow(); - - var disabledColor = ImGui.GetColorU32(ImGuiCol.TextDisabled); - for (var i = 0; i < human.AsHuman->SlotCount; ++i) - { - ImUtf8.DrawTableColumn($"{(uint)i:D2}"); - ImUtf8.DrawTableColumn(((HumanSlot)i).ToName()); - - ImGui.TableNextColumn(); - var model = human.AsHuman->Models[i]; - Penumbra.Dynamis.DrawPointer((nint)model); - if (model is not null) - { - var mask = model->EnabledAttributeIndexMask; - ImUtf8.DrawTableColumn($"{mask:X8}"); - ImUtf8.DrawTableColumn($"{human.GetModelId((HumanSlot)i):D4}"); - ImUtf8.DrawTableColumn($"{model->ModelResourceHandle->Attributes.Count}"); - ImGui.TableNextColumn(); - foreach (var ((attribute, flag), idx) in model->ModelResourceHandle->Attributes.WithIndex()) - { - var disabled = (mask & (1u << flag)) is 0; - using var color = ImRaii.PushColor(ImGuiCol.Text, disabledColor, disabled); - ImUtf8.Text(attribute.AsSpan()); - ImGui.SameLine(0, 0); - ImUtf8.Text(", "u8); - if (idx % 8 < 7) - ImGui.SameLine(0, 0); - } - } - else - { - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - ImGui.TableNextColumn(); - } - } - } -} diff --git a/Penumbra/UI/Tabs/Debug/TexHeaderDrawer.cs b/Penumbra/UI/Tabs/Debug/TexHeaderDrawer.cs index 4244e455..08d51184 100644 --- a/Penumbra/UI/Tabs/Debug/TexHeaderDrawer.cs +++ b/Penumbra/UI/Tabs/Debug/TexHeaderDrawer.cs @@ -1,6 +1,6 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface.DragDrop; using Dalamud.Interface.Utility.Raii; +using ImGuiNET; using Lumina.Data.Files; using OtterGui.Services; using OtterGui.Text; diff --git a/Penumbra/UI/Tabs/EffectiveTab.cs b/Penumbra/UI/Tabs/EffectiveTab.cs index 5691f821..ecf9a886 100644 --- a/Penumbra/UI/Tabs/EffectiveTab.cs +++ b/Penumbra/UI/Tabs/EffectiveTab.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; diff --git a/Penumbra/UI/Tabs/ModsTab.cs b/Penumbra/UI/Tabs/ModsTab.cs index 79dcbb9e..87338bdb 100644 --- a/Penumbra/UI/Tabs/ModsTab.cs +++ b/Penumbra/UI/Tabs/ModsTab.cs @@ -1,5 +1,5 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Game.ClientState.Objects; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using Penumbra.UI.Classes; @@ -53,14 +53,14 @@ public class ModsTab( { try { - selector.Draw(); + selector.Draw(GetModSelectorSize(config)); ImGui.SameLine(); - ImGui.SetCursorPosX(MathF.Round(ImGui.GetCursorPosX())); using var group = ImRaii.Group(); collectionHeader.Draw(false); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - using (var child = ImRaii.Child("##ModsTabMod", new Vector2(ImGui.GetContentRegionAvail().X, config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()), + + using (var child = ImRaii.Child("##ModsTabMod", new Vector2(-1, config.HideRedrawBar ? 0 : -ImGui.GetFrameHeight()), true, ImGuiWindowFlags.HorizontalScrollbar)) { style.Pop(); @@ -77,15 +77,28 @@ public class ModsTab( { Penumbra.Log.Error($"Exception thrown during ModPanel Render:\n{e}"); Penumbra.Log.Error($"{modManager.Count} Mods\n" - + $"{_activeCollections.Current.Identity.AnonymizedName} Current Collection\n" + + $"{_activeCollections.Current.AnonymizedName} Current Collection\n" + $"{_activeCollections.Current.Settings.Count} Settings\n" + $"{selector.SortMode.Name} Sort Mode\n" + $"{selector.SelectedLeaf?.Name ?? "NULL"} Selected Leaf\n" + $"{selector.Selected?.Name ?? "NULL"} Selected Mod\n" - + $"{string.Join(", ", _activeCollections.Current.Inheritance.DirectlyInheritsFrom.Select(c => c.Identity.AnonymizedName))} Inheritances\n"); + + $"{string.Join(", ", _activeCollections.Current.DirectlyInheritsFrom.Select(c => c.AnonymizedName))} Inheritances\n"); } } + /// Get the correct size for the mod selector based on current config. + public static float GetModSelectorSize(Configuration config) + { + var absoluteSize = Math.Clamp(config.ModSelectorAbsoluteSize, Configuration.Constants.MinAbsoluteSize, + Math.Min(Configuration.Constants.MaxAbsoluteSize, ImGui.GetContentRegionAvail().X - 100)); + var relativeSize = config.ScaleModSelector + ? Math.Clamp(config.ModSelectorScaledSize, Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize) + : 0; + return !config.ScaleModSelector + ? absoluteSize + : Math.Max(absoluteSize, relativeSize * ImGui.GetContentRegionAvail().X / 100); + } + private void DrawRedrawLine() { if (config.HideRedrawBar) diff --git a/Penumbra/UI/Tabs/ResourceTab.cs b/Penumbra/UI/Tabs/ResourceTab.cs index 593adde1..c54e3433 100644 --- a/Penumbra/UI/Tabs/ResourceTab.cs +++ b/Penumbra/UI/Tabs/ResourceTab.cs @@ -1,9 +1,9 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Game; using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using FFXIVClientStructs.STD; +using ImGuiNET; using OtterGui; using OtterGui.Raii; using OtterGui.Services; diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 86c01cb2..9d8ea21c 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -1,21 +1,17 @@ -using Dalamud.Bindings.ImGui; using Dalamud.Interface; using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Dalamud.Utility; +using ImGuiNET; using OtterGui; using OtterGui.Compression; using OtterGui.Custom; using OtterGui.Raii; using OtterGui.Services; -using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api; -using Penumbra.Collections; -using Penumbra.Interop; -using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Services; using Penumbra.Mods.Manager; using Penumbra.Services; @@ -37,7 +33,6 @@ public class SettingsTab : ITab, IUiService private readonly Penumbra _penumbra; private readonly FileDialogService _fileDialog; private readonly ModManager _modManager; - private readonly FileWatcher _fileWatcher; private readonly ModExportManager _modExportManager; private readonly ModFileSystemSelector _selector; private readonly CharacterUtility _characterUtility; @@ -51,27 +46,18 @@ public class SettingsTab : ITab, IUiService private readonly PredefinedTagManager _predefinedTagManager; private readonly CrashHandlerService _crashService; private readonly MigrationSectionDrawer _migrationDrawer; - private readonly CollectionAutoSelector _autoSelector; - private readonly CleanupService _cleanupService; - private readonly AttributeHook _attributeHook; - private readonly PcpService _pcpService; private int _minimumX = int.MaxValue; private int _minimumY = int.MaxValue; private readonly TagButtons _sharedTags = new(); - private string _lastCloudSyncTestedPath = string.Empty; - private bool _lastCloudSyncTestResult = false; - public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial, Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, - CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, - FileWatcher fileWatcher, HttpApi httpApi, + CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, - MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService, - AttributeHook attributeHook, PcpService pcpService) + MigrationSectionDrawer migrationDrawer) { _pluginInterface = pluginInterface; _config = config; @@ -84,7 +70,6 @@ public class SettingsTab : ITab, IUiService _characterUtility = characterUtility; _residentResources = residentResources; _modExportManager = modExportManager; - _fileWatcher = fileWatcher; _httpApi = httpApi; _dalamudSubstitutionProvider = dalamudSubstitutionProvider; _compactor = compactor; @@ -95,10 +80,6 @@ public class SettingsTab : ITab, IUiService _predefinedTagManager = predefinedTagConfig; _crashService = crashService; _migrationDrawer = migrationDrawer; - _autoSelector = autoSelector; - _cleanupService = cleanupService; - _attributeHook = attributeHook; - _pcpService = pcpService; } public void DrawHeader() @@ -122,7 +103,6 @@ public class SettingsTab : ITab, IUiService DrawRootFolder(); DrawDirectoryButtons(); ImGui.NewLine(); - ImGui.NewLine(); DrawGeneralSettings(); _migrationDrawer.Draw(); @@ -215,15 +195,6 @@ public class SettingsTab : ITab, IUiService if (IsSubPathOf(gameDir, newName)) return ("Path is not allowed to be inside your game folder.", false); - if (_lastCloudSyncTestedPath != newName) - { - _lastCloudSyncTestResult = CloudApi.IsCloudSynced(newName); - _lastCloudSyncTestedPath = newName; - } - - if (_lastCloudSyncTestResult) - return ("Path is not allowed to be cloud-synced.", false); - return selected ? ($"Press Enter or Click Here to Save (Current Directory: {old})", true) : ($"Click Here to Save (Current Directory: {old})", true); @@ -450,9 +421,6 @@ public class SettingsTab : ITab, IUiService /// Draw all settings that do not fit into other categories. private void DrawMiscSettings() { - Checkbox("Automatically Select Character-Associated Collection", - "On every login, automatically select the collection associated with the current character as the current collection for editing.", - _config.AutoSelectCollection, _autoSelector.SetAutomaticSelection); Checkbox("Print Chat Command Success Messages to Chat", "Chat Commands usually print messages on failure but also on success to confirm your action. You can disable this here.", _config.PrintSuccessfulCommandsToChat, v => _config.PrintSuccessfulCommandsToChat = v); @@ -468,15 +436,6 @@ public class SettingsTab : ITab, IUiService _config.Ephemeral.Save(); } }); - - ChangedItemModeExtensions.DrawCombo("##ChangedItemMode"u8, _config.ChangedItemDisplay, UiHelpers.InputTextWidth.X, v => - { - _config.ChangedItemDisplay = v; - _config.Save(); - }); - ImUtf8.LabeledHelpMarker("Mod Changed Item Display"u8, - "Configure how to display the changed items of a single mod in the mods info panel."u8); - Checkbox("Omit Machinist Offhands in Changed Items", "Omits all Aetherotransformers (machinist offhands) in the changed items tabs because any change on them changes all of them at the moment.\n\n" + "Changing this triggers a rediscovery of your mods so all changed items can be updated.", @@ -525,25 +484,74 @@ public class SettingsTab : ITab, IUiService { var sortMode = _config.SortMode; ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); - using (var combo = ImUtf8.Combo("##sortMode", sortMode.Name)) + using (var combo = ImRaii.Combo("##sortMode", sortMode.Name)) { if (combo) foreach (var val in Configuration.Constants.ValidSortModes) { - if (ImUtf8.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) + if (ImGui.Selectable(val.Name, val.GetType() == sortMode.GetType()) && val.GetType() != sortMode.GetType()) { _config.SortMode = val; _selector.SetFilterDirty(); _config.Save(); } - ImUtf8.HoverTooltip(val.Description); + ImGuiUtil.HoverTooltip(val.Description); } } ImGuiUtil.LabeledHelpMarker("Sort Mode", "Choose the sort mode for the mod selector in the mods tab."); } + private float _absoluteSelectorSize = float.NaN; + + /// Draw a selector for the absolute size of the mod selector in pixels. + private void DrawAbsoluteSizeSelector() + { + if (float.IsNaN(_absoluteSelectorSize)) + _absoluteSelectorSize = _config.ModSelectorAbsoluteSize; + + if (ImGuiUtil.DragFloat("##absoluteSize", ref _absoluteSelectorSize, UiHelpers.InputTextWidth.X, 1, + Configuration.Constants.MinAbsoluteSize, Configuration.Constants.MaxAbsoluteSize, "%.0f") + && _absoluteSelectorSize != _config.ModSelectorAbsoluteSize) + { + _config.ModSelectorAbsoluteSize = _absoluteSelectorSize; + _config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker("Mod Selector Absolute Size", + "The minimal absolute size of the mod selector in the mod tab in pixels."); + } + + private int _relativeSelectorSize = int.MaxValue; + + /// Draw a selector for the relative size of the mod selector as a percentage and a toggle to enable relative sizing. + private void DrawRelativeSizeSelector() + { + var scaleModSelector = _config.ScaleModSelector; + if (ImGui.Checkbox("Scale Mod Selector With Window Size", ref scaleModSelector)) + { + _config.ScaleModSelector = scaleModSelector; + _config.Save(); + } + + ImGui.SameLine(); + if (_relativeSelectorSize == int.MaxValue) + _relativeSelectorSize = _config.ModSelectorScaledSize; + if (ImGuiUtil.DragInt("##relativeSize", ref _relativeSelectorSize, UiHelpers.InputTextWidth.X - ImGui.GetCursorPosX(), 0.1f, + Configuration.Constants.MinScaledSize, Configuration.Constants.MaxScaledSize, "%i%%") + && _relativeSelectorSize != _config.ModSelectorScaledSize) + { + _config.ModSelectorScaledSize = _relativeSelectorSize; + _config.Save(); + } + + ImGui.SameLine(); + ImGuiUtil.LabeledHelpMarker("Mod Selector Relative Size", + "Instead of keeping the mod-selector in the Installed Mods tab a fixed width, this will let it scale with the total size of the Penumbra window."); + } + private void DrawRenameSettings() { ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); @@ -577,6 +585,8 @@ public class SettingsTab : ITab, IUiService private void DrawModSelectorSettings() { DrawFolderSortType(); + DrawAbsoluteSizeSelector(); + DrawRelativeSizeSelector(); DrawRenameSettings(); Checkbox("Open Folders by Default", "Whether to start with all folders collapsed or expanded in the mod selector.", _config.OpenFoldersByDefault, v => @@ -593,70 +603,21 @@ public class SettingsTab : ITab, IUiService _config.DeleteModModifier = v; _config.Save(); }); - Widget.DoubleModifierSelector("Incognito Modifier", - "A modifier you need to hold while clicking the Incognito or Temporary Settings Mode button for it to take effect.", - UiHelpers.InputTextWidth.X, - _config.IncognitoModifier, - v => - { - _config.IncognitoModifier = v; - _config.Save(); - }); } /// Draw all settings pertaining to import and export of mods. private void DrawModHandlingSettings() { - Checkbox("Use Temporary Settings Per Default", - "When you make any changes to your collection, apply them as temporary changes first and require a click to 'turn permanent' if you want to keep them.", - _config.DefaultTemporaryMode, v => _config.DefaultTemporaryMode = v); Checkbox("Replace Non-Standard Symbols On Import", "Replace all non-ASCII symbols in mod and option names with underscores when importing mods.", _config.ReplaceNonAsciiOnImport, v => _config.ReplaceNonAsciiOnImport = v); Checkbox("Always Open Import at Default Directory", "Open the import window at the location specified here every time, forgetting your previous path.", _config.AlwaysOpenDefaultImport, v => _config.AlwaysOpenDefaultImport = v); - Checkbox("Handle PCP Files", - "When encountering specific mods, usually but not necessarily denoted by a .pcp file ending, Penumbra will automatically try to create an associated collection and assign it to a specific character for this mod package. This can turn this behaviour off if unwanted.", - !_config.PcpSettings.DisableHandling, v => _config.PcpSettings.DisableHandling = !v); - - var active = _config.DeleteModModifier.IsActive(); - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Delete all PCP Mods"u8, "Deletes all mods tagged with 'PCP' from the mod list."u8, disabled: !active)) - _pcpService.CleanPcpMods(); - if (!active) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); - - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Delete all PCP Collections"u8, "Deletes all collections whose name starts with 'PCP/' from the collection list."u8, - disabled: !active)) - _pcpService.CleanPcpCollections(); - if (!active) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking."); - - Checkbox("Allow Other Plugins Access to PCP Handling", - "When creating or importing PCP files, other plugins can add and interpret their own data to the character.json file.", - _config.PcpSettings.AllowIpc, v => _config.PcpSettings.AllowIpc = v); - - Checkbox("Create PCP Collections", - "When importing PCP files, create the associated collection.", - _config.PcpSettings.CreateCollection, v => _config.PcpSettings.CreateCollection = v); - - Checkbox("Assign PCP Collections", - "When importing PCP files and creating the associated collection, assign it to the associated character.", - _config.PcpSettings.AssignCollection, v => _config.PcpSettings.AssignCollection = v); DrawDefaultModImportPath(); DrawDefaultModAuthor(); DrawDefaultModImportFolder(); - DrawPcpFolder(); DrawDefaultModExportPath(); - Checkbox("Enable Directory Watcher", - "Enables a File Watcher that automatically listens for Mod files that enter a specified directory, causing Penumbra to open a popup to import these mods.", - _config.EnableDirectoryWatch, _fileWatcher.Toggle); - Checkbox("Enable Fully Automatic Import", - "Uses the File Watcher in order to skip the query popup and automatically import any new mods.", - _config.EnableAutomaticModImport, v => _config.EnableAutomaticModImport = v); - DrawFileWatcherPath(); } @@ -736,46 +697,6 @@ public class SettingsTab : ITab, IUiService + "Keep this empty to use the root directory."); } - private string? _tempWatchDirectory; - - /// Draw input for the Automatic Mod import path. - private void DrawFileWatcherPath() - { - var tmp = _tempWatchDirectory ?? _config.WatchDirectory; - var spacing = new Vector2(UiHelpers.ScaleX3); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); - ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3); - if (ImGui.InputText("##fileWatchPath", ref tmp, 256)) - _tempWatchDirectory = tmp; - - if (ImGui.IsItemDeactivated() && _tempWatchDirectory is not null) - { - if (ImGui.IsItemDeactivatedAfterEdit()) - _fileWatcher.UpdateDirectory(_tempWatchDirectory); - _tempWatchDirectory = null; - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##fileWatch", UiHelpers.IconButtonSize, - "Select a directory via dialog.", false, true)) - { - var startDir = _config.WatchDirectory.Length > 0 && Directory.Exists(_config.WatchDirectory) - ? _config.WatchDirectory - : Directory.Exists(_config.ModDirectory) - ? _config.ModDirectory - : null; - _fileDialog.OpenFolderPicker("Choose Automatic Import Directory", (b, s) => - { - if (b) - _fileWatcher.UpdateDirectory(s); - }, startDir, false); - } - - style.Pop(); - ImGuiUtil.LabeledHelpMarker("Automatic Import Director", - "Choose the Directory the File Watcher listens to."); - } - /// Draw input for the default name to input as author into newly generated mods. private void DrawDefaultModAuthor() { @@ -805,21 +726,6 @@ public class SettingsTab : ITab, IUiService "Set the default Penumbra mod folder to place newly imported mods into.\nLeave blank to import into Root."); } - /// Draw input for the default folder to sort put newly imported mods into. - private void DrawPcpFolder() - { - var tmp = _config.PcpSettings.FolderName; - ImGui.SetNextItemWidth(UiHelpers.InputTextWidth.X); - if (ImUtf8.InputText("##pcpFolder"u8, ref tmp)) - _config.PcpSettings.FolderName = tmp; - - if (ImGui.IsItemDeactivatedAfterEdit()) - _config.Save(); - - ImGuiUtil.LabeledHelpMarker("Default PCP Organizational Folder", - "The folder any penumbra character packs are moved to on import.\nLeave blank to import into Root."); - } - /// Draw all settings pertaining to advanced editing of mods. private void DrawModEditorSettings() @@ -860,7 +766,6 @@ public class SettingsTab : ITab, IUiService DrawCrashHandler(); DrawMinimumDimensionConfig(); - DrawHdrRenderTargets(); Checkbox("Auto Deduplicate on Import", "Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.", _config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v); @@ -872,17 +777,11 @@ public class SettingsTab : ITab, IUiService "Normally, metadata changes that equal their default values, which are sometimes exported by TexTools, are discarded. " + "Toggle this to keep them, for example if an option in a mod is supposed to disable a metadata change from a prior option.", _config.KeepDefaultMetaChanges, v => _config.KeepDefaultMetaChanges = v); - Checkbox("Enable Custom Shape and Attribute Support", - "Penumbra will allow for custom shape keys and attributes for modded models to be considered and combined.", - _config.EnableCustomShapes, _attributeHook.SetState); DrawWaitForPluginsReflection(); DrawEnableHttpApiBox(); DrawEnableDebugModeBox(); - ImGui.Separator(); DrawReloadResourceButton(); DrawReloadFontsButton(); - ImGui.Separator(); - DrawCleanupButtons(); ImGui.NewLine(); } @@ -917,15 +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, - true); + _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, - true); + _compactor.StartMassCompact(_modManager.BasePath.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.None, true); if (_compactor.MassCompactRunning) { @@ -996,33 +893,6 @@ public class SettingsTab : ITab, IUiService _config.Save(); } - private void DrawHdrRenderTargets() - { - ImGui.SetNextItemWidth(ImUtf8.CalcTextSize("M"u8).X * 5.0f + ImGui.GetFrameHeight()); - using (var combo = ImUtf8.Combo("##hdrRenderTarget"u8, _config.HdrRenderTargets ? "HDR"u8 : "SDR"u8)) - { - if (combo) - { - if (ImUtf8.Selectable("HDR"u8, _config.HdrRenderTargets) && !_config.HdrRenderTargets) - { - _config.HdrRenderTargets = true; - _config.Save(); - } - - if (ImUtf8.Selectable("SDR"u8, !_config.HdrRenderTargets) && _config.HdrRenderTargets) - { - _config.HdrRenderTargets = false; - _config.Save(); - } - } - } - - ImGui.SameLine(); - ImUtf8.LabeledHelpMarker("Diffuse Dynamic Range"u8, - "Set the dynamic range that can be used for diffuse colors in materials without causing visual artifacts.\n"u8 - + "Changing this setting requires a game restart. It also only works if Wait for Plugins on Startup is enabled."u8); - } - /// Draw a checkbox for the HTTP API that creates and destroys the web server when toggled. private void DrawEnableHttpApiBox() { @@ -1074,44 +944,6 @@ public class SettingsTab : ITab, IUiService _fontReloader.Reload(); } - private void DrawCleanupButtons() - { - var enabled = _config.DeleteModModifier.IsActive(); - if (_cleanupService.Progress is not 0.0 and not 1.0) - { - ImUtf8.ProgressBar((float)_cleanupService.Progress, new Vector2(200 * ImUtf8.GlobalScale, ImGui.GetFrameHeight()), - $"{_cleanupService.Progress * 100}%"); - ImGui.SameLine(); - if (ImUtf8.Button("Cancel##FileCleanup"u8)) - _cleanupService.Cancel(); - } - else - { - ImGui.NewLine(); - } - - if (ImUtf8.ButtonEx("Clear Unused Local Mod Data Files"u8, - "Delete all local mod data files that do not correspond to currently installed mods."u8, default, - !enabled || _cleanupService.IsRunning)) - _cleanupService.CleanUnusedLocalData(); - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking to delete files."); - - if (ImUtf8.ButtonEx("Clear Backup Files"u8, - "Delete all backups of .json configuration files in your configuration folder and all backups of mod group files in your mod directory."u8, - default, !enabled || _cleanupService.IsRunning)) - _cleanupService.CleanBackupFiles(); - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking to delete files."); - - if (ImUtf8.ButtonEx("Clear All Unused Settings"u8, - "Remove all mod settings in all of your collections that do not correspond to currently installed mods."u8, default, - !enabled || _cleanupService.IsRunning)) - _cleanupService.CleanupAllUnusedSettings(); - if (!enabled) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, $"Hold {_config.DeleteModModifier} while clicking to remove settings."); - } - /// Draw a checkbox that toggles the dalamud setting to wait for plugins on open. private void DrawWaitForPluginsReflection() { @@ -1162,9 +994,6 @@ public class SettingsTab : ITab, IUiService ImGui.SetCursorPos(new Vector2(xPos, 4 * ImGui.GetFrameHeightWithSpacing())); if (ImGui.Button("Show Changelogs", new Vector2(width, 0))) _penumbra.ForceChangelogOpen(); - - ImGui.SetCursorPos(new Vector2(xPos, 5 * ImGui.GetFrameHeightWithSpacing())); - CustomGui.DrawKofiPatreonButton(Penumbra.Messager, new Vector2(width, 0)); } private void DrawPredefinedTagsSection() diff --git a/Penumbra/UI/TutorialService.cs b/Penumbra/UI/TutorialService.cs index 69f2b616..7d2a0d2a 100644 --- a/Penumbra/UI/TutorialService.cs +++ b/Penumbra/UI/TutorialService.cs @@ -83,14 +83,14 @@ public class TutorialService : IUiService + "Go here after setting up your root folder to continue the tutorial!") .Register("Initial Setup, Step 4: Managing Collections", "On the left, we have the collection selector. Here, we can create new collections - either empty ones or by duplicating existing ones - and delete any collections not needed anymore.\n" - + $"There will always be one collection called {ModCollectionIdentity.DefaultCollectionName} that can not be deleted.") + + $"There will always be one collection called {ModCollection.DefaultCollectionName} that can not be deleted.") .Register($"Initial Setup, Step 5: {SelectedCollection}", $"The {SelectedCollection} is the one we highlighted in the selector. It is the collection we are currently looking at and editing.\nAny changes we make in our mod settings later in the next tab will edit this collection.\n" - + $"We should already have the collection named {ModCollectionIdentity.DefaultCollectionName} selected, and for our simple setup, we do not need to do anything here.\n\n") + + $"We should already have the collection named {ModCollection.DefaultCollectionName} selected, and for our simple setup, we do not need to do anything here.\n\n") .Register("Initial Setup, Step 6: Simple Assignments", "Aside from being a collection of settings, we can also assign collections to different functions. This is used to make different mods apply to different characters.\n" + "The Simple Assignments panel shows you the possible assignments that are enough for most people along with descriptions.\n" - + $"If you are just starting, you can see that the {ModCollectionIdentity.DefaultCollectionName} is currently assigned to {CollectionType.Default.ToName()} and {CollectionType.Interface.ToName()}.\n" + + $"If you are just starting, you can see that the {ModCollection.DefaultCollectionName} is currently assigned to {CollectionType.Default.ToName()} and {CollectionType.Interface.ToName()}.\n" + "You can also assign 'Use No Mods' instead of a collection by clicking on the function buttons.") .Register("Individual Assignments", "In the Individual Assignments panel, you can manually create assignments for very specific characters or monsters, not just yourself or ones you can currently target.") diff --git a/Penumbra/UI/UiHelpers.cs b/Penumbra/UI/UiHelpers.cs index 9fe90ee8..deba7023 100644 --- a/Penumbra/UI/UiHelpers.cs +++ b/Penumbra/UI/UiHelpers.cs @@ -1,6 +1,6 @@ using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; -using Dalamud.Bindings.ImGui; +using ImGuiNET; using OtterGui; using OtterGui.Classes; using OtterGui.Raii; @@ -13,11 +13,11 @@ public static class UiHelpers { /// Draw text given by a ByteString. public static unsafe void Text(ByteString s) - => ImGuiNative.TextUnformatted(s.Path, s.Path + s.Length); + => ImGuiNative.igTextUnformatted(s.Path, s.Path + s.Length); /// Draw text given by a byte pointer and length. public static unsafe void Text(byte* s, int length) - => ImGuiNative.TextUnformatted(s, s + length); + => ImGuiNative.igTextUnformatted(s, s + length); /// Draw text given by a byte span. public static unsafe void Text(ReadOnlySpan s) @@ -36,7 +36,7 @@ public static class UiHelpers public static unsafe bool Selectable(ByteString s, bool selected) { var tmp = (byte)(selected ? 1 : 0); - return ImGuiNative.Selectable(s.Path, tmp, ImGuiSelectableFlags.None, Vector2.Zero) != 0; + return ImGuiNative.igSelectable_Bool(s.Path, tmp, ImGuiSelectableFlags.None, Vector2.Zero) != 0; } /// @@ -45,8 +45,8 @@ public static class UiHelpers /// public static unsafe void CopyOnClickSelectable(ByteString text) { - if (ImGuiNative.Selectable(text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) != 0) - ImGuiNative.SetClipboardText(text.Path); + if (ImGuiNative.igSelectable_Bool(text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero) != 0) + ImGuiNative.igSetClipboardText(text.Path); if (ImGui.IsItemHovered()) ImGui.SetTooltip("Click to copy to clipboard."); diff --git a/Penumbra/Util/IdentifierExtensions.cs b/Penumbra/Util/IdentifierExtensions.cs index f744e940..5bd3f77c 100644 --- a/Penumbra/Util/IdentifierExtensions.cs +++ b/Penumbra/Util/IdentifierExtensions.cs @@ -10,7 +10,7 @@ namespace Penumbra.Util; public static class IdentifierExtensions { public static void AddChangedItems(this ObjectIdentification identifier, IModDataContainer container, - IDictionary changedItems) + IDictionary changedItems) { foreach (var gamePath in container.Files.Keys.Concat(container.FileSwaps.Keys)) identifier.Identify(changedItems, gamePath.ToString()); @@ -19,7 +19,7 @@ public static class IdentifierExtensions manip.AddChangedItems(identifier, changedItems); } - public static void RemoveMachinistOffhands(this SortedList changedItems) + public static void RemoveMachinistOffhands(this SortedList changedItems) { for (var i = 0; i < changedItems.Count; i++) { @@ -31,7 +31,7 @@ public static class IdentifierExtensions } } - public static void RemoveMachinistOffhands(this SortedList, IIdentifiedObjectData)> changedItems) + public static void RemoveMachinistOffhands(this SortedList, IIdentifiedObjectData?)> changedItems) { for (var i = 0; i < changedItems.Count; i++) { diff --git a/Penumbra/lib/OtterTex.dll b/Penumbra/lib/OtterTex.dll index c137aee1..29912e62 100644 Binary files a/Penumbra/lib/OtterTex.dll and b/Penumbra/lib/OtterTex.dll differ diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json index 7499bffa..5b868212 100644 --- a/Penumbra/packages.lock.json +++ b/Penumbra/packages.lock.json @@ -1,13 +1,7 @@ { "version": 1, "dependencies": { - "net9.0-windows7.0": { - "DotNet.ReproducibleBuilds": { - "type": "Direct", - "requested": "[1.2.25, )", - "resolved": "1.2.25", - "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" - }, + "net8.0-windows7.0": { "EmbedIO": { "type": "Direct", "requested": "[3.5.2, )", @@ -19,75 +13,67 @@ }, "PeNet": { "type": "Direct", - "requested": "[5.1.0, )", - "resolved": "5.1.0", - "contentHash": "XSd1PUwWo5uI8iqVHk7Mm02RT1bjndtAYsaRwLmdYZoHOAmb4ohkvRcZiqxJ7iLfBfdiwm+PHKQIMqDmOavBtw==", + "requested": "[4.0.5, )", + "resolved": "4.0.5", + "contentHash": "/OUfRnMG8STVuK8kTpdfm+WQGTDoUiJnI845kFw4QrDmv2gYourmSnH84pqVjHT1YHBSuRfCzfioIpHGjFJrGA==", "dependencies": { "PeNet.Asn1": "2.0.1", - "System.Security.Cryptography.Pkcs": "8.0.1" + "System.Security.Cryptography.Pkcs": "8.0.0" } }, "SharpCompress": { "type": "Direct", - "requested": "[0.40.0, )", - "resolved": "0.40.0", - "contentHash": "yP/aFX1jqGikVF7u2f05VEaWN4aCaKNLxSas82UgA2GGVECxq/BcqZx3STHCJ78qilo1azEOk1XpBglIuGMb7w==", + "requested": "[0.37.2, )", + "resolved": "0.37.2", + "contentHash": "cFBpTct57aubLQXkdqMmgP8GGTFRh7fnRWP53lgE/EYUpDZJ27SSvTkdjB4OYQRZ20SJFpzczUquKLbt/9xkhw==", "dependencies": { - "System.Buffers": "4.6.0", - "ZstdSharp.Port": "0.8.5" + "ZstdSharp.Port": "0.8.0" } }, "SharpGLTF.Core": { "type": "Direct", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "HNHKPqaHXm7R1nlXZ764K5UI02IeDOQ5DQKLjwYUVNTsSW27jJpw+wLGQx6ZFoiFYqUlyZjmsu+WfEak2GmJAg==" + "requested": "[1.0.1, )", + "resolved": "1.0.1", + "contentHash": "ykeV1oNHcJrEJE7s0pGAsf/nYGYY7wqF9nxCMxJUjp/WdW+UUgR1cGdbAa2lVZPkiXEwLzWenZ5wPz7yS0Gj9w==" }, "SharpGLTF.Toolkit": { "type": "Direct", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "piQKk7PH2pSWQSQmCSd8cYPaDtAy/ppAD+Mrh2RUhhHI8awl81HqqLyAauwQhJwea3LNaiJ6f4ehZuOGk89TlA==", + "requested": "[1.0.1, )", + "resolved": "1.0.1", + "contentHash": "LYBjHdHW5Z8R1oT1iI04si3559tWdZ3jTdHfDEu0jqhuyU8w3oJRLFUoDfVeCOI5zWXlVQPtlpjhH9XTfFFAcA==", "dependencies": { - "SharpGLTF.Runtime": "1.0.5" + "SharpGLTF.Runtime": "1.0.1" } }, "SixLabors.ImageSharp": { "type": "Direct", - "requested": "[3.1.11, )", - "resolved": "3.1.11", - "contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw==" + "requested": "[3.1.5, )", + "resolved": "3.1.5", + "contentHash": "lNtlq7dSI/QEbYey+A0xn48z5w4XHSffF8222cC4F4YwTXfEImuiBavQcWjr49LThT/pRmtWJRcqA/PlL+eJ6g==" }, - "FlatSharp.Compiler": { - "type": "Transitive", - "resolved": "7.9.0", - "contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A==" - }, - "FlatSharp.Runtime": { - "type": "Transitive", - "resolved": "7.9.0", - "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==", - "dependencies": { - "System.Memory": "4.5.5" - } + "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.3.0", - "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + "resolved": "2024.2.0", + "contentHash": "GNnqCFW/163p1fOehKx0CnAqjmpPrUSqrgfHM6qca+P+RN39C9rhlfZHQpJhxmQG/dkOYe/b3Z0P8b6Kv5m1qw==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "ZffbJrskOZ40JTzcTyKwFHS5eACSWp2bUQBBApIgGV+es8RaTD4OxUG7XxFr3RIPLXtYQ1jQzF2DjKB5fZn7Qg==", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "9.0.2", - "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg==" + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" }, "PeNet.Asn1": { "type": "Transitive", @@ -96,26 +82,19 @@ }, "SharpGLTF.Runtime": { "type": "Transitive", - "resolved": "1.0.5", - "contentHash": "EVP32k4LqERxSVICiupT8xQvhHSHJCiXajBjNpqdfdajtREHayuVhH0Jmk6uSjTLX8/IIH9XfT34sw3TwvCziw==", + "resolved": "1.0.1", + "contentHash": "KsgEBKLfsEnu2IPeKaWp4Ih97+kby17IohrAB6Ev8gET18iS80nKMW/APytQWpenMmcWU06utInpANqyrwRlDg==", "dependencies": { - "SharpGLTF.Core": "1.0.5" + "SharpGLTF.Core": "1.0.1" } }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA==" - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" - }, "System.Security.Cryptography.Pkcs": { "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA==" + "resolved": "8.0.0", + "contentHash": "ULmp3xoOwNYjOYp4JZ2NK/6NdTgiN1GQXzVVN1njQ7LOZ0d0B9vyMnhyqbIi9Qw4JXj1JgCsitkTShboHRx7Eg==", + "dependencies": { + "System.Formats.Asn1": "8.0.0" + } }, "System.ValueTuple": { "type": "Transitive", @@ -132,14 +111,14 @@ }, "ZstdSharp.Port": { "type": "Transitive", - "resolved": "0.8.5", - "contentHash": "TR4j17WeVSEb3ncgL2NqlXEqcy04I+Kk9CaebNDplUeL8XOgjkZ7fP4Wg4grBdPLIqsV86p2QaXTkZoRMVOsew==" + "resolved": "0.8.0", + "contentHash": "Z62eNBIu8E8YtbqlMy57tK3dV1+m2b9NhPeaYovB5exmLKvrGCqOhJTzrEUH5VyUWU6vwX3c1XHJGhW5HVs8dA==" }, "ottergui": { "type": "Project", "dependencies": { - "JetBrains.Annotations": "[2024.3.0, )", - "Microsoft.Extensions.DependencyInjection": "[9.0.2, )" + "JetBrains.Annotations": "[2024.2.0, )", + "Microsoft.Extensions.DependencyInjection": "[8.0.0, )" } }, "penumbra.api": { @@ -151,11 +130,9 @@ "penumbra.gamedata": { "type": "Project", "dependencies": { - "FlatSharp.Compiler": "[7.9.0, )", - "FlatSharp.Runtime": "[7.9.0, )", "OtterGui": "[1.0.0, )", - "Penumbra.Api": "[5.10.0, )", - "Penumbra.String": "[1.0.6, )" + "Penumbra.Api": "[5.3.0, )", + "Penumbra.String": "[1.0.4, )" } }, "penumbra.string": { diff --git a/repo.json b/repo.json index 7ddffd7c..f5a8e2f1 100644 --- a/repo.json +++ b/repo.json @@ -1,16 +1,16 @@ [ { - "Author": "Ottermandias, Nylfae, Adam, Wintermute", + "Author": "Ottermandias, Adam, Wintermute", "Name": "Penumbra", "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", - "AssemblyVersion": "1.5.1.8", - "TestingAssemblyVersion": "1.5.1.8", + "AssemblyVersion": "1.3.1.3", + "TestingAssemblyVersion": "1.3.1.3", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", - "DalamudApiLevel": 13, - "TestingDalamudApiLevel": 13, + "DalamudApiLevel": 11, + "TestingDalamudApiLevel": 11, "IsHide": "False", "IsTestingExclusive": "False", "DownloadCount": 0, @@ -18,9 +18,9 @@ "LoadPriority": 69420, "LoadRequiredState": 2, "LoadSync": true, - "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", - "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", + "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.3.1.3/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.3.1.3/Penumbra.zip", + "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.3.1.3/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } ] diff --git a/schemas/default_mod.json b/schemas/default_mod.json deleted file mode 100644 index 8f50c5db..00000000 --- a/schemas/default_mod.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "allOf": [ - { - "type": "object", - "properties": { - "Version": { - "description": "Mod Container version, currently unused.", - "type": "integer", - "minimum": 0, - "maximum": 0 - } - } - }, - { - "$ref": "structs/container.json" - } - ] -} diff --git a/schemas/group.json b/schemas/group.json deleted file mode 100644 index 4c37b631..00000000 --- a/schemas/group.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Version": { - "description": "Mod Container version, currently unused.", - "type": "integer" - }, - "Name": { - "description": "Name of the group.", - "type": "string", - "minLength": 1 - }, - "Description": { - "description": "Description of the group.", - "type": [ "string", "null" ] - }, - "Image": { - "description": "Relative path to a preview image for the group. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.", - "type": ["string", "null" ] - }, - "Page": { - "description": "TexTools page of the group. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.", - "type": "integer" - }, - "Priority": { - "description": "Priority of the group. If several groups define conflicting files or manipulations, the highest priority wins.", - "type": "integer" - }, - "Type": { - "description": "Group type. Single groups have one and only one of their options active at any point. Multi groups can have zero, one or many of their options active. Combining groups have n options, 2^n containers, and will have one and only one container active depending on the selected options.", - "enum": [ "Single", "Multi", "Imc", "Combining" ] - }, - "DefaultSettings": { - "description": "Default configuration for the group.", - "type": "integer" - } - }, - "required": [ - "Name", - "Type" - ], - "oneOf": [ - { - "$ref": "structs/group_combining.json" - }, - { - "$ref": "structs/group_imc.json" - }, - { - "$ref": "structs/group_multi.json" - }, - { - "$ref": "structs/group_single.json" - } - ] -} diff --git a/schemas/local_mod_data-v3.json b/schemas/local_mod_data-v3.json deleted file mode 100644 index c50e130e..00000000 --- a/schemas/local_mod_data-v3.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Local Penumbra Mod Data", - "description": "The locally stored data for an installed mod in Penumbra", - "type": "object", - "properties": { - "FileVersion": { - "description": "Major version of the local data schema used.", - "type": "integer", - "minimum": 3, - "maximum": 3 - }, - "ImportDate": { - "description": "The date and time of the installation of the mod as a Unix Epoch millisecond timestamp.", - "type": "integer" - }, - "LocalTags": { - "description": "User-defined local tags for the mod.", - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "Favorite": { - "description": "Whether the mod is favourited by the user.", - "type": "boolean" - }, - "PreferredChangedItems": { - "description": "Preferred items to list as the main item of a group.", - "type": "array", - "items": { - "minimum": 0, - "type": "integer" - }, - "uniqueItems": true - } - }, - "required": [ "FileVersion" ] -} diff --git a/schemas/mod_meta-v3.json b/schemas/mod_meta-v3.json deleted file mode 100644 index 6fc68714..00000000 --- a/schemas/mod_meta-v3.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "title": "Penumbra Mod Metadata", - "description": "Metadata of a Penumbra mod.", - "type": "object", - "properties": { - "FileVersion": { - "description": "Major version of the metadata schema used.", - "type": "integer", - "minimum": 3, - "maximum": 3 - }, - "Name": { - "description": "Name of the mod.", - "type": "string", - "minLength": 1 - }, - "Author": { - "description": "Author of the mod.", - "type": [ "string", "null" ] - }, - "Description": { - "description": "Description of the mod. Can span multiple paragraphs.", - "type": [ "string", "null" ] - }, - "Image": { - "description": "Relative path to a preview image for the mod. Unused by Penumbra, present for round-trip import/export of TexTools-generated mods.", - "type": [ "string", "null" ] - }, - "Version": { - "description": "Version of the mod. Can be an arbitrary string.", - "type": [ "string", "null" ] - }, - "Website": { - "description": "URL of the web page of the mod.", - "type": [ "string", "null" ] - }, - "ModTags": { - "description": "Author-defined tags for the mod.", - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "DefaultPreferredItems": { - "description": "Default preferred items to list as the main item of a group managed by the mod creator.", - "type": "array", - "items": { - "minimum": 0, - "type": "integer" - }, - "uniqueItems": true - }, - "RequiredFeatures": { - "description": "A list of required features by name.", - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - } - }, - "required": [ - "FileVersion", - "Name" - ] -} diff --git a/schemas/shpk_devkit.json b/schemas/shpk_devkit.json deleted file mode 100644 index f03fbb05..00000000 --- a/schemas/shpk_devkit.json +++ /dev/null @@ -1,499 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "ShaderKeys": { - "type": "object", - "patternProperties": { - "^\\d+$": { - "$ref": "#/$defs/ShaderKey" - } - }, - "additionalProperties": false - }, - "Comment": { - "$ref": "#/$defs/MayVary" - }, - "Samplers": { - "type": "object", - "patternProperties": { - "^\\d+$": { - "$ref": "#/$defs/MayVary" - } - }, - "additionalProperties": false - }, - "Constants": { - "type": "object", - "patternProperties": { - "^\\d+$": { - "$ref": "#/$defs/MayVary" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false, - "$defs": { - "ShaderKeyValue": { - "type": "object", - "properties": { - "Label": { - "type": "string" - }, - "Description": { - "type": "string" - } - }, - "additionalProperties": false - }, - "ShaderKey": { - "type": "object", - "properties": { - "Label": { - "type": "string" - }, - "Description": { - "type": "string" - }, - "Values": { - "type": "object", - "patternProperties": { - "^\\d+$": { - "$ref": "#/$defs/ShaderKeyValue" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "Varying": { - "type": "object", - "properties": { - "Vary": { - "type": "array", - "items": { - "$ref": "#/$defs/LaxInteger" - } - }, - "Selectors": { - "description": "Keys are Σ 31^i shaderKey(Vary[i]).", - "type": "object", - "patternProperties": { - "^\\d+$": { - "type": "integer" - } - }, - "additionalProperties": false - }, - "Items": { - "type": "array", - "$comment": "Varying is defined by constraining this array's items to T" - } - }, - "required": [ - "Vary", - "Selectors", - "Items" - ], - "additionalProperties": false - }, - "MayVary": { - "oneOf": [ - { - "type": ["string", "null"] - }, { - "allOf": [ - { - "$ref": "#/$defs/Varying" - }, { - "type": "object", - "properties": { - "Items": { - "type": "array", - "items": { - "type": ["string", "null"] - } - } - } - } - ] - } - ] - }, - "Sampler": { - "type": ["object", "null"], - "properties": { - "Label": { - "type": "string" - }, - "Description": { - "type": "string" - }, - "DefaultTexture": { - "type": "string", - "pattern": "^[^/\\\\][^\\\\]*$" - } - }, - "additionalProperties": false - }, - "MayVary": { - "oneOf": [ - { - "$ref": "#/$defs/Sampler" - }, { - "allOf": [ - { - "$ref": "#/$defs/Varying" - }, { - "type": "object", - "properties": { - "Items": { - "type": "array", - "items": { - "$ref": "#/$defs/Sampler" - } - } - } - } - ] - } - ] - }, - "ConstantBase": { - "type": "object", - "properties": { - "Offset": { - "description": "Defaults to 0. Mutually exclusive with ByteOffset.", - "type": "integer", - "minimum": 0 - }, - "Length": { - "description": "Defaults to up to the end. Mutually exclusive with ByteLength.", - "type": "integer", - "minimum": 0 - }, - "ByteOffset": { - "description": "Defaults to 0. Mutually exclusive with Offset.", - "type": "integer", - "minimum": 0 - }, - "ByteLength": { - "description": "Defaults to up to the end. Mutually exclusive with Length.", - "type": "integer", - "minimum": 0 - }, - "Group": { - "description": "Defaults to \"Further Constants\".", - "type": "string" - }, - "Label": { - "type": "string" - }, - "Description": { - "description": "Defaults to empty.", - "type": "string" - }, - "Type": { - "description": "Defaults to Float.", - "enum": ["Hidden", "Float", "Integer", "Color", "Enum", "Int32", "Int32Enum", "Int8", "Int8Enum", "Int16", "Int16Enum", "Int64", "Int64Enum", "Half", "Double", "TileIndex", "SphereMapIndex"] - } - }, - "not": { - "anyOf": [ - { - "required": ["Offset", "ByteOffset"] - }, { - "required": ["Length", "ByteLenngth"] - } - ] - } - }, - "HiddenConstant": { - "type": "object", - "properties": { - "Type": { - "const": "Hidden" - } - }, - "required": [ - "Type" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "FloatConstant": { - "type": "object", - "properties": { - "Type": { - "enum": ["Float", "Half", "Double"] - }, - "Minimum": { - "description": "Defaults to -∞.", - "type": "number" - }, - "Maximum": { - "description": "Defaults to ∞.", - "type": "number" - }, - "Speed": { - "description": "Defaults to 0.1.", - "type": "number", - "minimum": 0 - }, - "RelativeSpeed": { - "description": "Defaults to 0.", - "type": "number", - "minimum": 0 - }, - "Exponent": { - "description": "Defaults to 1. Uses an odd pseudo-power function, f(x) = sgn(x) |x|^n.", - "type": "number" - }, - "Factor": { - "description": "Defaults to 1.", - "type": "number" - }, - "Bias": { - "description": "Defaults to 0.", - "type": "number" - }, - "Precision": { - "description": "Defaults to 3.", - "type": "integer", - "minimum": 0, - "maximum": 9 - }, - "Slider": { - "description": "Defaults to true. Drag has priority over this.", - "type": "boolean" - }, - "Drag": { - "description": "Defaults to true. Has priority over Slider.", - "type": "boolean" - }, - "Unit": { - "description": "Defaults to no unit.", - "type": "string" - } - }, - "required": [ - "Label" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "IntConstant": { - "type": "object", - "properties": { - "Type": { - "enum": ["Integer", "Int32", "Int8", "Int16", "Int64"] - }, - "Minimum": { - "description": "Defaults to -2^N, N being the explicit integer width specified in the type, or 32 for Int.", - "type": "number" - }, - "Maximum": { - "description": "Defaults to 2^N - 1, N being the explicit integer width specified in the type, or 32 for Int.", - "type": "number" - }, - "Speed": { - "description": "Defaults to 0.25.", - "type": "number", - "minimum": 0 - }, - "RelativeSpeed": { - "description": "Defaults to 0.", - "type": "number", - "minimum": 0 - }, - "Factor": { - "description": "Defaults to 1.", - "type": "number" - }, - "Bias": { - "description": "Defaults to 0.", - "type": "number" - }, - "Hex": { - "description": "Defaults to false. Has priority over Slider and Drag.", - "type": "boolean" - }, - "Slider": { - "description": "Defaults to true. Hex and Drag have priority over this.", - "type": "boolean" - }, - "Drag": { - "description": "Defaults to true. Has priority over Slider, but Hex has priority over this.", - "type": "boolean" - }, - "Unit": { - "description": "Defaults to no unit.", - "type": "string" - } - }, - "required": [ - "Label", - "Type" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "ColorConstant": { - "type": "object", - "properties": { - "Type": { - "const": "Color" - }, - "SquaredRgb": { - "description": "Defaults to false. Uses an odd pseudo-square function, f(x) = sgn(x) |x|².", - "type": "boolean" - }, - "Clamped": { - "description": "Defaults to false.", - "type": "boolean" - } - }, - "required": [ - "Label", - "Type" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "EnumValue": { - "type": "object", - "properties": { - "Label": { - "type": "string" - }, - "Description": { - "type": "string" - }, - "Value": { - "type": "number" - } - }, - "required": [ - "Label", - "Value" - ], - "additionalProperties": false - }, - "EnumConstant": { - "type": "object", - "properties": { - "Type": { - "enum": ["Enum", "Int32Enum", "Int8Enum", "Int16Enum", "Int64Enum"] - }, - "Values": { - "type": "array", - "items": { - "$ref": "#/$defs/EnumValue" - } - } - }, - "required": [ - "Label", - "Type" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "SpecialConstant": { - "type": "object", - "properties": { - "Type": { - "enum": ["TileIndex", "SphereMapIndex"] - } - }, - "required": [ - "Label", - "Type" - ], - "allOf": [ - { - "$ref": "#/$defs/ConstantBase" - } - ], - "unevaluatedProperties": false - }, - "Constant": { - "oneOf": [ - { - "$ref": "#/$defs/HiddenConstant" - }, { - "$ref": "#/$defs/FloatConstant" - }, { - "$ref": "#/$defs/IntConstant" - }, { - "$ref": "#/$defs/ColorConstant" - }, { - "$ref": "#/$defs/EnumConstant" - }, { - "$ref": "#/$defs/SpecialConstant" - } - ] - }, - "MayVary": { - "oneOf": [ - { - "type": ["array", "null"], - "items": { - "$ref": "#/$defs/Constant" - } - }, { - "allOf": [ - { - "$ref": "#/$defs/Varying" - }, { - "type": "object", - "properties": { - "Items": { - "type": "array", - "items": { - "type": ["array", "null"], - "items": { - "$ref": "#/$defs/Constant" - } - } - } - } - } - ] - } - ] - }, - "LaxInteger": { - "oneOf": [ - { - "type": "integer" - }, { - "type": "string", - "pattern": "^\\d+$" - } - ] - } - } -} diff --git a/schemas/structs/container.json b/schemas/structs/container.json deleted file mode 100644 index 74db4a23..00000000 --- a/schemas/structs/container.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Files": { - "description": "File redirections in this container. Keys are game paths, values are relative file paths.", - "type": [ "object", "null" ], - "patternProperties": { - "^[^/\\\\.:?][^\\\\?:]+$": { - "type": "string", - "pattern": "^[^/\\\\.:?][^?:]+$" - } - }, - "additionalProperties": false - }, - "FileSwaps": { - "description": "File swaps in this container. Keys are original game paths, values are actual game paths.", - "type": [ "object", "null" ], - "patternProperties": { - "^[^/\\\\.?:][^\\\\?:]+$": { - "type": "string", - "pattern": "^[^/\\\\.:?][^?:]+$" - } - }, - "additionalProperties": false - }, - "Manipulations": { - "type": [ "array", "null" ], - "items": { - "$ref": "manipulation.json" - } - } - } -} diff --git a/schemas/structs/group_combining.json b/schemas/structs/group_combining.json deleted file mode 100644 index e42edcb8..00000000 --- a/schemas/structs/group_combining.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "Type": { - "const": "Combining" - }, - "Options": { - "type": "array", - "items": { - "$ref": "option.json" - } - }, - "Containers": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "container.json" - }, - { - "properties": { - "Name": { - "type": [ "string", "null" ] - } - } - } - ] - } - } - } -} diff --git a/schemas/structs/group_imc.json b/schemas/structs/group_imc.json deleted file mode 100644 index 48a04bd9..00000000 --- a/schemas/structs/group_imc.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "properties": { - "Type": { - "const": "Imc" - }, - "AllVariants": { - "type": "boolean" - }, - "OnlyAttributes": { - "type": "boolean" - }, - "Identifier": { - "$ref": "meta_imc.json#ImcIdentifier" - }, - "DefaultEntry": { - "$ref": "meta_imc.json#ImcEntry" - }, - "Options": { - "type": "array", - "items": { - "$ref": "option.json", - "oneOf": [ - { - "properties": { - "AttributeMask": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - } - }, - "required": [ - "AttributeMask" - ] - }, - { - "properties": { - "IsDisableSubMod": { - "const": true - } - }, - "required": [ - "IsDisableSubMod" - ] - } - ] - } - } - } -} diff --git a/schemas/structs/group_multi.json b/schemas/structs/group_multi.json deleted file mode 100644 index ca7d4dfa..00000000 --- a/schemas/structs/group_multi.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Type": { - "const": "Multi" - }, - "Options": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "option.json" - }, - { - "$ref": "container.json" - }, - { - "properties": { - "Priority": { - "type": "integer" - } - } - } - ] - } - } - } -} - - - diff --git a/schemas/structs/group_single.json b/schemas/structs/group_single.json deleted file mode 100644 index 24cda88d..00000000 --- a/schemas/structs/group_single.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Type": { - "const": "Single" - }, - "Options": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "option.json" - }, - { - "$ref": "container.json" - } - ] - } - } - } -} diff --git a/schemas/structs/manipulation.json b/schemas/structs/manipulation.json deleted file mode 100644 index 81f2cef3..00000000 --- a/schemas/structs/manipulation.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Type": { - "enum": [ "Unknown", "Imc", "Eqdp", "Eqp", "Est", "Gmp", "Rsp", "GlobalEqp", "Atch", "Shp", "Atr" ] - }, - "Manipulation": { - "type": "object" - } - }, - "required": [ "Type", "Manipulation" ], - "oneOf": [ - { - "properties": { - "Type": { - "const": "Imc" - }, - "Manipulation": { - "$ref": "meta_imc.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Eqdp" - }, - "Manipulation": { - "$ref": "meta_eqdp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Eqp" - }, - "Manipulation": { - "$ref": "meta_eqp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Est" - }, - "Manipulation": { - "$ref": "meta_est.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Gmp" - }, - "Manipulation": { - "$ref": "meta_gmp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Rsp" - }, - "Manipulation": { - "$ref": "meta_rsp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "GlobalEqp" - }, - "Manipulation": { - "$ref": "meta_geqp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Atch" - }, - "Manipulation": { - "$ref": "meta_atch.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Shp" - }, - "Manipulation": { - "$ref": "meta_shp.json" - } - } - }, - { - "properties": { - "Type": { - "const": "Atr" - }, - "Manipulation": { - "$ref": "meta_atr.json" - } - } - } - ] -} diff --git a/schemas/structs/meta_atch.json b/schemas/structs/meta_atch.json deleted file mode 100644 index 3c9cbef5..00000000 --- a/schemas/structs/meta_atch.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "object", - "properties": { - "Bone": { - "type": "string", - "maxLength": 34 - }, - "Scale": { - "type": "number" - }, - "OffsetX": { - "type": "number" - }, - "OffsetY": { - "type": "number" - }, - "OffsetZ": { - "type": "number" - }, - "RotationX": { - "type": "number" - }, - "RotationY": { - "type": "number" - }, - "RotationZ": { - "type": "number" - } - }, - "required": [ - "Bone", - "Scale", - "OffsetX", - "OffsetY", - "OffsetZ", - "RotationX", - "RotationY", - "RotationZ" - ] - }, - "Gender": { - "$ref": "meta_enums.json#Gender" - }, - "Race": { - "$ref": "meta_enums.json#ModelRace" - }, - "Type": { - "type": "string", - "minLength": 1, - "maxLength": 4 - }, - "Index": { - "$ref": "meta_enums.json#U16" - } - }, - "required": [ - "Entry", - "Gender", - "Race", - "Type", - "Index" - ] -} diff --git a/schemas/structs/meta_atr.json b/schemas/structs/meta_atr.json deleted file mode 100644 index 479d4127..00000000 --- a/schemas/structs/meta_atr.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "boolean" - }, - "Slot": { - "$ref": "meta_enums.json#HumanSlot" - }, - "Id": { - "$ref": "meta_enums.json#U16" - }, - "Attribute": { - "type": "string", - "minLength": 5, - "maxLength": 30, - "pattern": "^atrx_" - }, - "GenderRaceCondition": { - "enum": [ 0, 101, 201, 301, 401, 501, 601, 701, 801, 901, 1001, 1101, 1201, 1301, 1401, 1501, 1601, 1701, 1801 ] - } - }, - "required": [ - "Attribute" - ] -} diff --git a/schemas/structs/meta_enums.json b/schemas/structs/meta_enums.json deleted file mode 100644 index bad184e0..00000000 --- a/schemas/structs/meta_enums.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$defs": { - "EquipSlot": { - "$anchor": "EquipSlot", - "enum": [ "Unknown", "MainHand", "OffHand", "Head", "Body", "Hands", "Belt", "Legs", "Feet", "Ears", "Neck", "Wrists", "RFinger", "BothHand", "LFinger", "HeadBody", "BodyHandsLegsFeet", "SoulCrystal", "LegsFeet", "FullBody", "BodyHands", "BodyLegsFeet", "ChestHands", "Nothing", "All" ] - }, - "HumanSlot": { - "$anchor": "HumanSlot", - "enum": [ "Head", "Body", "Hands", "Legs", "Feet", "Ears", "Neck", "Wrists", "RFinger", "LFinger", "Hair", "Face", "Ear", "Glasses", "Unknown" ] - }, - "Gender": { - "$anchor": "Gender", - "enum": [ "Unknown", "Male", "Female", "MaleNpc", "FemaleNpc" ] - }, - "ModelRace": { - "$anchor": "ModelRace", - "enum": [ "Unknown", "Midlander", "Highlander", "Elezen", "Lalafell", "Miqote", "Roegadyn", "AuRa", "Hrothgar", "Viera" ] - }, - "ObjectType": { - "$anchor": "ObjectType", - "enum": [ "Unknown", "Vfx", "DemiHuman", "Accessory", "World", "Housing", "Monster", "Icon", "LoadingScreen", "Map", "Interface", "Equipment", "Character", "Weapon", "Font" ] - }, - "BodySlot": { - "$anchor": "BodySlot", - "enum": [ "Unknown", "Hair", "Face", "Tail", "Body", "Zear" ] - }, - "SubRace": { - "$anchor": "SubRace", - "enum": [ "Unknown", "Midlander", "Highlander", "Wildwood", "Duskwight", "Plainsfolk", "Dunesfolk", "SeekerOfTheSun", "KeeperOfTheMoon", "Seawolf", "Hellsguard", "Raen", "Xaela", "Helion", "Lost", "Rava", "Veena" ] - }, - "ShapeConnectorCondition": { - "$anchor": "ShapeConnectorCondition", - "enum": [ "None", "Wrists", "Waist", "Ankles" ] - }, - "U8": { - "$anchor": "U8", - "oneOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 255 - }, - { - "type": "string", - "pattern": "^0*(1[0-9][0-9]|[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$" - } - ] - }, - "U16": { - "$anchor": "U16", - "oneOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 65535 - }, - { - "type": "string", - "pattern": "^0*([1-5][0-9]{4}|[0-9]{0,4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$" - } - ] - } - } -} diff --git a/schemas/structs/meta_eqdp.json b/schemas/structs/meta_eqdp.json deleted file mode 100644 index f27606b9..00000000 --- a/schemas/structs/meta_eqdp.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - }, - "Gender": { - "$ref": "meta_enums.json#Gender" - }, - "Race": { - "$ref": "meta_enums.json#ModelRace" - }, - "SetId": { - "$ref": "meta_enums.json#U16" - }, - "Slot": { - "$ref": "meta_enums.json#EquipSlot" - } - }, - "required": [ - "Entry", - "Gender", - "Race", - "SetId", - "Slot" - ] -} diff --git a/schemas/structs/meta_eqp.json b/schemas/structs/meta_eqp.json deleted file mode 100644 index c829d7a7..00000000 --- a/schemas/structs/meta_eqp.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "integer" - }, - "SetId": { - "$ref": "meta_enums.json#U16" - }, - "Slot": { - "$ref": "meta_enums.json#EquipSlot" - } - }, - "required": [ - "Entry", - "SetId", - "Slot" - ] -} diff --git a/schemas/structs/meta_est.json b/schemas/structs/meta_est.json deleted file mode 100644 index 22bce12b..00000000 --- a/schemas/structs/meta_est.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "$ref": "meta_enums.json#U16" - }, - "Gender": { - "$ref": "meta_enums.json#Gender" - }, - "Race": { - "$ref": "meta_enums.json#ModelRace" - }, - "SetId": { - "$ref": "meta_enums.json#U16" - }, - "Slot": { - "enum": [ "Hair", "Face", "Body", "Head" ] - } - }, - "required": [ - "Entry", - "Gender", - "Race", - "SetId", - "Slot" - ] -} diff --git a/schemas/structs/meta_geqp.json b/schemas/structs/meta_geqp.json deleted file mode 100644 index e38fbb86..00000000 --- a/schemas/structs/meta_geqp.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Condition": { - "$ref": "meta_enums.json#U16" - }, - "Type": { - "enum": [ "DoNotHideEarrings", "DoNotHideNecklace", "DoNotHideBracelets", "DoNotHideRingR", "DoNotHideRingL", "DoNotHideHrothgarHats", "DoNotHideVieraHats", "HideHorns", "HideVieraEars", "HideMiqoteEars" ] - } - }, - "required": [ "Type" ], - "oneOf": [ - { - "properties": { - "Type": { - "const": [ "DoNotHideHrothgarHats", "DoNotHideVieraHats", "HideHorns", "HideVieraEars", "HideMiqoteEars" ] - }, - "Condition": { - "const": 0 - } - } - }, - { - "properties": { - "Type": { - "const": [ "DoNotHideHrothgarHats", "DoNotHideVieraHats", "HideHorns", "HideVieraEars", "HideMiqoteEars" ] - } - } - }, - { - "properties": { - "Type": { - "const": [ "DoNotHideEarrings", "DoNotHideNecklace", "DoNotHideBracelets", "DoNotHideRingR", "DoNotHideRingL" ] - }, - "Condition": {} - } - } - ] -} diff --git a/schemas/structs/meta_gmp.json b/schemas/structs/meta_gmp.json deleted file mode 100644 index bf1fb1df..00000000 --- a/schemas/structs/meta_gmp.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "object", - "properties": { - "Enabled": { - "type": "boolean" - }, - "Animated": { - "type": "boolean" - }, - "RotationA": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - }, - "RotationB": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - }, - "RotationC": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - }, - "UnknownA": { - "type": "integer", - "minimum": 0, - "maximum": 15 - }, - "UnknownB": { - "type": "integer", - "minimum": 0, - "maximum": 15 - } - }, - "required": [ - "Enabled", - "Animated", - "RotationA", - "RotationB", - "RotationC", - "UnknownA", - "UnknownB" - ], - "additionalProperties": false - }, - "SetId": { - "$ref": "meta_enums.json#U16" - } - }, - "required": [ - "Entry", - "SetId" - ] -} diff --git a/schemas/structs/meta_imc.json b/schemas/structs/meta_imc.json deleted file mode 100644 index aa9a4fca..00000000 --- a/schemas/structs/meta_imc.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "$ref": "#ImcEntry" - } - }, - "required": [ - "Entry" - ], - "allOf": [ - { - "$ref": "#ImcIdentifier" - } - ], - "$defs": { - "ImcIdentifier": { - "type": "object", - "properties": { - "PrimaryId": { - "$ref": "meta_enums.json#U16" - }, - "SecondaryId": { - "$ref": "meta_enums.json#U16" - }, - "Variant": { - "$ref": "meta_enums.json#U8" - }, - "ObjectType": { - "$ref": "meta_enums.json#ObjectType" - }, - "EquipSlot": { - "$ref": "meta_enums.json#EquipSlot" - }, - "BodySlot": { - "$ref": "meta_enums.json#BodySlot" - } - }, - "$anchor": "ImcIdentifier", - "required": [ - "PrimaryId", - "SecondaryId", - "Variant", - "ObjectType", - "EquipSlot", - "BodySlot" - ] - }, - "ImcEntry": { - "type": "object", - "properties": { - "MaterialId": { - "$ref": "meta_enums.json#U8" - }, - "DecalId": { - "$ref": "meta_enums.json#U8" - }, - "VfxId": { - "$ref": "meta_enums.json#U8" - }, - "MaterialAnimationId": { - "$ref": "meta_enums.json#U8" - }, - "AttributeMask": { - "type": "integer", - "minimum": 0, - "maximum": 1023 - }, - "SoundId": { - "type": "integer", - "minimum": 0, - "maximum": 63 - } - }, - "$anchor": "ImcEntry", - "required": [ - "MaterialId", - "DecalId", - "VfxId", - "MaterialAnimationId", - "AttributeMask", - "SoundId" - ] - } - } -} diff --git a/schemas/structs/meta_rsp.json b/schemas/structs/meta_rsp.json deleted file mode 100644 index 3354281b..00000000 --- a/schemas/structs/meta_rsp.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "number" - }, - "SubRace": { - "$ref": "meta_enums.json#SubRace" - }, - "Attribute": { - "enum": [ "MaleMinSize", "MaleMaxSize", "MaleMinTail", "MaleMaxTail", "FemaleMinSize", "FemaleMaxSize", "FemaleMinTail", "FemaleMaxTail", "BustMinX", "BustMinY", "BustMinZ", "BustMaxX", "BustMaxY", "BustMaxZ" ] - } - }, - "required": [ - "Entry", - "SubRace", - "Attribute" - ] -} diff --git a/schemas/structs/meta_shp.json b/schemas/structs/meta_shp.json deleted file mode 100644 index cb7fd0ec..00000000 --- a/schemas/structs/meta_shp.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Entry": { - "type": "boolean" - }, - "Slot": { - "$ref": "meta_enums.json#HumanSlot" - }, - "Id": { - "$ref": "meta_enums.json#U16" - }, - "Shape": { - "type": "string", - "minLength": 5, - "maxLength": 30, - "pattern": "^shpx_" - }, - "ConnectorCondition": { - "$ref": "meta_enums.json#ShapeConnectorCondition" - }, - "GenderRaceCondition": { - "enum": [ 0, 101, 201, 301, 401, 501, 601, 701, 801, 901, 1001, 1101, 1201, 1301, 1401, 1501, 1601, 1701, 1801 ] - } - }, - "required": [ - "Shape" - ] -} diff --git a/schemas/structs/option.json b/schemas/structs/option.json deleted file mode 100644 index c45ccfdb..00000000 --- a/schemas/structs/option.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "Name": { - "description": "Name of the option.", - "type": "string", - "minLength": 1 - }, - "Description": { - "description": "Description of the option.", - "type": [ "string", "null" ] - }, - "Priority": { - "description": "Priority of the option. If several enabled options within the group define conflicting files or manipulations, the highest priority wins.", - "type": "integer" - }, - "Image": { - "description": "Unused by Penumbra.", - "type": [ "string", "null" ] - } - }, - "required": [ "Name" ] -}