From 5cd224b164d92a47a4f03b5def720c99fa881277 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 9 Aug 2024 16:01:44 +0200 Subject: [PATCH] Add design migration for inverted gloss and specular, and a backup before doing that. --- Glamourer/Configuration.cs | 6 +- Glamourer/Designs/Design.cs | 75 ++++++++++++++++++-- Glamourer/Designs/DesignConverter.cs | 11 +-- Glamourer/Designs/DesignManager.cs | 2 +- Glamourer/Services/ConfigMigrationService.cs | 11 +++ 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 11c45a8..165e20d 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -143,10 +143,10 @@ public class Configuration : IPluginConfiguration, ISavable public static class Constants { - public const int CurrentVersion = 6; + public const int CurrentVersion = 7; public static readonly ISortMode[] ValidSortModes = - { + [ ISortMode.FoldersFirst, ISortMode.Lexicographical, new DesignFileSystem.CreationDate(), @@ -159,7 +159,7 @@ public class Configuration : IPluginConfiguration, ISavable ISortMode.InverseFoldersLast, ISortMode.InternalOrder, ISortMode.InverseInternalOrder, - }; + ]; } /// Convert SortMode Types to their name. diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 8e4d366..fb18873 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OtterGui.Classes; using Penumbra.GameData.Structs; +using Notification = OtterGui.Classes.Notification; namespace Glamourer.Designs; @@ -34,7 +35,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn } // Metadata - public new const int FileVersion = 1; + public new const int FileVersion = 2; public Guid Identifier { get; internal init; } public DateTimeOffset CreationDate { get; internal init; } @@ -143,17 +144,81 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn #region Deserialization - public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + public static Design LoadDesign(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch { - FileVersion => LoadDesignV1(customizations, items, linkLoader, json), + 1 => LoadDesignV1(saveService, customizations, items, linkLoader, json), + FileVersion => LoadDesignV2(customizations, items, linkLoader, json), _ => throw new Exception("The design to be loaded has no valid Version."), }; } - private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + /// The values for gloss and specular strength were switched. Swap them for all appropriate designs. + private static Design LoadDesignV1(SaveService saveService, CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) + { + var design = LoadDesignV2(customizations, items, linkLoader, json); + var materialDesignData = design.GetMaterialDataRef(); + if (materialDesignData.Values.Count == 0) + return design; + + var materialData = materialDesignData.Clone(); + // Guesstimate whether to migrate material rows: + // Update 1.3.0.10 released at that time, so any design last updated before that can be migrated. + if (design.LastEdit <= new DateTime(2024, 8, 7, 16, 0, 0, DateTimeKind.Utc)) + { + Migrate("because it was saved the wrong way around before 1.3.0.10, and this design was not changed since that release."); + } + else + { + var hasNegativeGloss = false; + var hasNonPositiveGloss = false; + var specularLarger = 0; + foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + { + hasNegativeGloss |= value.Value.GlossStrength < 0; + hasNonPositiveGloss |= value.Value.GlossStrength <= 0; + if (value.Value.SpecularStrength > value.Value.GlossStrength) + ++specularLarger; + } + + // If there is any negative gloss, this is wrong and can be migrated. + if (hasNegativeGloss) + Migrate("because it had a negative Gloss value, which is not supported and thus probably outdated."); + // If there is any non-positive Gloss and some specular values that are larger, it is probably wrong and can be migrated. + else if (hasNonPositiveGloss && specularLarger > 0) + Migrate("because it had a zero Gloss value, and at least one Specular Strength larger than the Gloss, which is unusual."); + // If most of the specular strengths are larger, it is probably wrong and can be migrated. + else if (specularLarger > materialData.Values.Count / 2) + Migrate("because most of its Specular Strength values were larger than the Gloss values, which is unusual."); + } + + return design; + + void Migrate(string reason) + { + materialDesignData.Clear(); + foreach (var (key, value) in materialData.GetValues(MaterialValueIndex.Min(), MaterialValueIndex.Max())) + { + var gloss = Math.Clamp(value.Value.SpecularStrength, 0, (float)Half.MaxValue); + var specularStrength = Math.Clamp(value.Value.GlossStrength, 0, (float)Half.MaxValue); + var colorRow = value.Value with + { + GlossStrength = gloss, + SpecularStrength = specularStrength, + }; + materialDesignData.AddOrUpdateValue(MaterialValueIndex.FromKey(key), value with { Value = colorRow }); + } + + Glamourer.Messager.AddMessage(new Notification( + $"Swapped Gloss and Specular Strength in {materialDesignData.Values.Count} Rows in design {design.Incognito} {reason}", + NotificationType.Info)); + saveService.Save(SaveType.ImmediateSync,design); + } + } + + private static Design LoadDesignV2(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var creationDate = json["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"); @@ -183,7 +248,7 @@ public sealed class Design : DesignBase, ISavable, IDesignStandIn static string[] ParseTags(JObject json) { - var tags = json["Tags"]?.ToObject() ?? Array.Empty(); + var tags = json["Tags"]?.ToObject() ?? []; return tags.OrderBy(t => t).Distinct().ToArray(); } } diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 2ebcea0..058b023 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; public class DesignConverter( + SaveService saveService, ItemManager _items, DesignManager _designs, CustomizeService _customize, @@ -69,7 +70,7 @@ public class DesignConverter( try { var ret = jObject["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObject) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObject) : DesignBase.LoadDesignBase(_customize, _items, jObject); if (!customize) @@ -100,7 +101,7 @@ public class DesignConverter( case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj1) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj1) : DesignBase.LoadDesignBase(_customize, _items, jObj1); break; case 1: @@ -115,7 +116,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -126,7 +127,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -136,7 +137,7 @@ public class DesignConverter( var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) + ? Design.LoadDesign(saveService, _customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index a31ba83..63c98c0 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -53,7 +53,7 @@ public sealed class DesignManager : DesignEditor { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(Customizations, Items, linkLoader, data); + var design = Design.LoadDesign(SaveService, Customizations, Items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) diff --git a/Glamourer/Services/ConfigMigrationService.cs b/Glamourer/Services/ConfigMigrationService.cs index 88eaf69..3f997c9 100644 --- a/Glamourer/Services/ConfigMigrationService.cs +++ b/Glamourer/Services/ConfigMigrationService.cs @@ -23,9 +23,20 @@ public class ConfigMigrationService(SaveService saveService, FixedDesignMigrator MigrateV2To4(); MigrateV4To5(); MigrateV5To6(); + MigrateV6To7(); AddColors(config, true); } + private void MigrateV6To7() + { + if (_config.Version > 6) + return; + + // Do not actually change anything in the config, just create a backup before designs are migrated. + backupService.CreateMigrationBackup("pre_gloss_specular_migration"); + _config.Version = 7; + } + private void MigrateV5To6() { if (_config.Version > 5)