diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs new file mode 100644 index 0000000..eb4fefe --- /dev/null +++ b/Glamourer/Designs/DesignEditor.cs @@ -0,0 +1,314 @@ +using Glamourer.Designs.Links; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.Services; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs; + +public class DesignEditor( + SaveService saveService, + DesignChanged designChanged, + CustomizeService customizations, + ItemManager items, + Configuration config) + : IDesignEditor +{ + protected readonly DesignChanged DesignChanged = designChanged; + protected readonly SaveService SaveService = saveService; + protected readonly ItemManager Items = items; + protected readonly CustomizeService Customizations = customizations; + protected readonly Configuration Config = config; + protected readonly Dictionary UndoStore = []; + + private bool _forceFullItemOff = false; + + /// Whether an Undo for the given design is possible. + public bool CanUndo(Design? design) + => design != null && UndoStore.ContainsKey(design.Identifier); + + /// + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var oldValue = design.DesignData.Customize[idx]; + + switch (idx) + { + case CustomizeIndex.Race: + case CustomizeIndex.BodyType: + Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen."); + return; + case CustomizeIndex.Clan: + { + var customize = design.DesignData.Customize; + if (Customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) + return; + if (!design.SetCustomize(Customizations, customize)) + return; + + break; + } + case CustomizeIndex.Gender: + { + var customize = design.DesignData.Customize; + if (Customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) + return; + if (!design.SetCustomize(Customizations, customize)) + return; + + break; + } + default: + if (!Customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender, + design.DesignData.Customize.Face, idx, value) + || !design.GetDesignDataRef().Customize.Set(idx, value)) + return; + + break; + } + + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx)); + } + + /// + public void ChangeEntireCustomize(object data, in CustomizeArray customize, CustomizeFlag apply, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var (newCustomize, applied, changed) = Customizations.Combine(design.DesignData.Customize, customize, apply, true); + if (changed == 0) + return; + + var oldCustomize = design.DesignData.Customize; + design.SetCustomize(Customizations, newCustomize); + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed)); + } + + /// + public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var old = design.DesignData.Parameters[flag]; + if (!design.GetDesignDataRef().Parameters.Set(flag, value)) + return; + + var @new = design.DesignData.Parameters[flag]; + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + } + + /// + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings _ = default) + { + if (data is not Design design) + return; + + switch (slot) + { + case EquipSlot.MainHand: + { + var currentMain = design.DesignData.Item(EquipSlot.MainHand); + var currentOff = design.DesignData.Item(EquipSlot.OffHand); + if (!Items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) + return; + + if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug( + $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); + return; + } + case EquipSlot.OffHand: + { + var currentMain = design.DesignData.Item(EquipSlot.MainHand); + var currentOff = design.DesignData.Item(EquipSlot.OffHand); + if (!Items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) + return; + + if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug( + $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); + DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item)); + return; + } + default: + { + if (!Items.IsItemValid(slot, item.Id, out item)) + return; + + var old = design.DesignData.Item(slot); + if (!design.GetDesignDataRef().SetItem(slot, item)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug( + $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); + return; + } + } + } + + /// + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default) + { + if (data is not Design design) + return; + + if (Items.ValidateStain(stain, out var _, false).Length > 0) + return; + + var oldStain = design.DesignData.Stain(slot); + if (!design.GetDesignDataRef().SetStain(slot, stain)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); + DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + } + + /// + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default) + { + if (item.HasValue) + ChangeItem(data, slot, item.Value, _); + if (stain.HasValue) + ChangeStain(data, slot, stain.Value, _); + } + + /// + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings _ = default) + { + if (data is not Design design) + return; + + var oldCrest = design.DesignData.Crest(slot); + if (!design.GetDesignDataRef().SetCrest(slot, crest)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); + DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + } + + /// + public void ChangeMetaState(object data, MetaIndex metaIndex, bool value, ApplySettings _ = default) + { + if (data is not Design design) + return; + + if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) + return; + + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); + DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); + } + + /// + public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default) + => ApplyDesign(data, other.Design); + + /// + public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default) + { + if (data is not Design design) + return; + + UndoStore[design.Identifier] = design.DesignData; + foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) + design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); + + if (!design.DesignData.IsHuman) + return; + + ChangeEntireCustomize(design, other.DesignData.Customize, other.ApplyCustomize); + + _forceFullItemOff = true; + foreach (var slot in EquipSlotExtensions.FullSlots) + { + if (other.DoApplyEquip(slot)) + ChangeItem(design, slot, other.DesignData.Item(slot)); + + if (other.DoApplyStain(slot)) + ChangeStain(design, slot, other.DesignData.Stain(slot)); + } + _forceFullItemOff = false; + + foreach (var slot in Enum.GetValues().Where(other.DoApplyCrest)) + ChangeCrest(design, slot, other.DesignData.Crest(slot)); + + foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter)) + ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]); + } + + /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. + private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, + out EquipItem? newGauntlets) + { + newOff = null; + newGauntlets = null; + if (newMain.Type != currentMain.Type) + { + var defaultOffhand = Items.GetDefaultOffhand(newMain); + if (!Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + return false; + + newOff = o; + } + else if (!_forceFullItemOff && Config.ChangeEntireItem) + { + var defaultOffhand = Items.GetDefaultOffhand(newMain); + if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) + newOff = o; + + if (newMain.Type is FullEquipType.Fists && Items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g)) + newGauntlets = g; + } + + if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain)) + return false; + + if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + return false; + } + + if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value)) + { + design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); + design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff); + return false; + } + + return true; + } +} diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c3ff2ac..c247396 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -9,32 +9,20 @@ using Newtonsoft.Json.Linq; using OtterGui; using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; -using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignManager +public class DesignManager : DesignEditor { - private readonly CustomizeService _customizations; - private readonly Configuration _config; - private readonly ItemManager _items; - private readonly HumanModelList _humans; - private readonly SaveService _saveService; - private readonly DesignChanged _event; - private readonly Dictionary _undoStore = []; - - public DesignStorage Designs { get; } + public readonly DesignStorage Designs; + private readonly HumanModelList _humans; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config) + : base(saveService, @event, customizations, items, config) { - Designs = storage; - _config = config; - _saveService = saveService; - _items = items; - _customizations = customizations; - _event = @event; - _humans = humans; + Designs = storage; + _humans = humans; LoadDesigns(designLinkLoader); CreateDesignFolder(saveService); @@ -42,27 +30,29 @@ public class DesignManager designLinkLoader.SetAllObjects(); } + #region Design Management + /// /// Clear currently loaded designs and load all designs anew from file. /// Invalid data is fixed, but changes are not saved until manual changes. /// - public void LoadDesigns(DesignLinkLoader linkLoader) + private void LoadDesigns(DesignLinkLoader linkLoader) { _humans.Awaiter.Wait(); - _customizations.Awaiter.Wait(); - _items.ItemData.Awaiter.Wait(); + Customizations.Awaiter.Wait(); + Items.ItemData.Awaiter.Wait(); var stopwatch = Stopwatch.StartNew(); Designs.Clear(); var skipped = 0; ThreadLocal> designs = new(() => [], true); - Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) => + Parallel.ForEach(SaveService.FileNames.Designs(), (f, _) => { try { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(_customizations, _items, linkLoader, data); + var design = Design.LoadDesign(Customizations, Items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) @@ -95,22 +85,18 @@ public class DesignManager Glamourer.Log.Information( $"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); - _event.Invoke(DesignChanged.Type.ReloadedAll, null!, null); + DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null); } - /// Whether an Undo for the given design is possible. - public bool CanUndo(Design? design) - => design != null && _undoStore.ContainsKey(design.Identifier); - /// Create a new temporary design without adding it to the manager. public DesignBase CreateTemporary() - => new(_customizations, _items); + => new(Customizations, Items); /// Create a new design of a given name. public Design CreateEmpty(string name, bool handlePath) { var (actualName, path) = ParseName(name, handlePath); - var design = new Design(_customizations, _items) + var design = new Design(Customizations, Items) { CreationDate = DateTimeOffset.UtcNow, LastEdit = DateTimeOffset.UtcNow, @@ -120,8 +106,8 @@ public class DesignManager }; Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier}."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -140,8 +126,8 @@ public class DesignManager Designs.Add(design); Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -160,8 +146,8 @@ public class DesignManager Designs.Add(design); Glamourer.Log.Debug( $"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}."); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, path); + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, path); return design; } @@ -171,10 +157,14 @@ public class DesignManager foreach (var d in Designs.Skip(design.Index + 1)) --d.Index; Designs.RemoveAt(design.Index); - _saveService.ImmediateDelete(design); - _event.Invoke(DesignChanged.Type.Deleted, design, null); + SaveService.ImmediateDelete(design); + DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null); } + #endregion + + #region Edit Information + /// Rename a design. public void Rename(Design design, string newName) { @@ -184,9 +174,9 @@ public class DesignManager design.Name = newName; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.Renamed, design, oldName); + DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName); } /// Change the description of a design. @@ -198,9 +188,9 @@ public class DesignManager design.Description = description; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed description of design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); + DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription); } public void ChangeColor(Design design, string newColor) @@ -211,9 +201,9 @@ public class DesignManager design.Color = newColor; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); + DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor); } /// Add a new tag to a design. The tags remain sorted. @@ -225,15 +215,11 @@ public class DesignManager design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; var idx = design.Tags.IndexOf(tag); - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx)); + DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx)); } - /// Remove a tag from a design if it exists. - public void RemoveTag(Design design, string tag) - => RemoveTag(design, design.Tags.IndexOf(tag)); - /// Remove a tag from a design by its index. public void RemoveTag(Design design, int tagIdx) { @@ -243,9 +229,9 @@ public class DesignManager var oldTag = design.Tags[tagIdx]; design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray(); design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx)); } /// Rename a tag from a design by its index. The tags stay sorted. @@ -258,9 +244,9 @@ public class DesignManager design.Tags[tagIdx] = newTag; Array.Sort(design.Tags); design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags."); - _event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx)); + DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx)); } /// Add an associated mod to a design. @@ -270,9 +256,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings)); } /// Remove an associated mod from a design. @@ -282,9 +268,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}."); - _event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); + DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings)); } /// Set the write protection status of a design. @@ -293,56 +279,14 @@ public class DesignManager if (!design.SetWriteProtected(value)) return; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected."); - _event.Invoke(DesignChanged.Type.WriteProtection, design, value); + DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value); } - /// Change a customization value. - public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value) - { - var oldValue = design.DesignData.Customize[idx]; + #endregion - switch (idx) - { - case CustomizeIndex.Race: - case CustomizeIndex.BodyType: - Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen."); - return; - case CustomizeIndex.Clan: - { - var customize = design.DesignData.Customize; - if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0) - return; - if (!design.SetCustomize(_customizations, customize)) - return; - - break; - } - case CustomizeIndex.Gender: - { - var customize = design.DesignData.Customize; - if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0) - return; - if (!design.SetCustomize(_customizations, customize)) - return; - - break; - } - default: - if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender, - design.DesignData.Customize.Face, idx, value) - || !design.GetDesignDataRef().Customize.Set(idx, value)) - return; - - break; - } - - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx)); - } + #region Edit Application Rules /// Change whether to apply a specific customize value. public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value) @@ -351,79 +295,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}."); - _event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx); - } - - /// Change a non-weapon equipment piece. - public void ChangeEquip(Design design, EquipSlot slot, EquipItem item) - { - if (!_items.IsItemValid(slot, item.Id, out item)) - return; - - var old = design.DesignData.Item(slot); - if (!design.GetDesignDataRef().SetItem(slot, item)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug( - $"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId})."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot)); - } - - /// Change a weapon. - public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item) - { - var currentMain = design.DesignData.Item(EquipSlot.MainHand); - var currentOff = design.DesignData.Item(EquipSlot.OffHand); - switch (slot) - { - case EquipSlot.MainHand: - - if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item)) - return; - - if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug( - $"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets)); - - return; - case EquipSlot.OffHand: - if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item)) - return; - - if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug( - $"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId})."); - _event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item, (EquipItem?)null)); - return; - default: return; - } - } - - /// Change a customize parameter. - public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value) - { - var old = design.DesignData.Parameters[flag]; - if (!design.GetDesignDataRef().Parameters.Set(flag, value)) - return; - - var @new = design.DesignData.Parameters[flag]; - design.LastEdit = DateTimeOffset.UtcNow; - Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}."); - _saveService.QueueSave(design); - _event.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag)); + DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx); } /// Change whether to apply a specific equipment piece. @@ -433,25 +307,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyEquip, design, slot); - } - - /// Change the stain for any equipment piece. - public void ChangeStain(Design design, EquipSlot slot, StainId stain) - { - if (_items.ValidateStain(stain, out _, false).Length > 0) - return; - - var oldStain = design.DesignData.Stain(slot); - if (!design.GetDesignDataRef().SetStain(slot, stain)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}."); - _event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot)); + DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot); } /// Change whether to apply a specific stain. @@ -461,22 +319,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyStain, design, slot); - } - - /// Change the crest visibility for any equipment piece. - public void ChangeCrest(Design design, CrestFlag slot, bool crest) - { - var oldCrest = design.DesignData.Crest(slot); - if (!design.GetDesignDataRef().SetCrest(slot, crest)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}."); - _event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot)); + DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot); } /// Change whether to apply a specific crest visibility. @@ -486,21 +331,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}."); - _event.Invoke(DesignChanged.Type.ApplyCrest, design, slot); - } - - /// Change the bool value of one of the meta flags. - public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) - { - if (!design.GetDesignDataRef().SetMeta(metaIndex, value)) - return; - - design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); - Glamourer.Log.Debug($"Set value of {metaIndex} to {value}."); - _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value)); + DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot); } /// Change the application value of one of the meta flags. @@ -510,9 +343,9 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}."); - _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); + DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); } /// Change the application value of a customize parameter. @@ -522,68 +355,26 @@ public class DesignManager return; design.LastEdit = DateTimeOffset.UtcNow; - _saveService.QueueSave(design); + SaveService.QueueSave(design); Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}."); - _event.Invoke(DesignChanged.Type.ApplyParameter, design, flag); + DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag); } - /// Apply an entire design based on its appliance rules piece by piece. - public void ApplyDesign(Design design, DesignBase other) - { - _undoStore[design.Identifier] = design.DesignData; - foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta)) - design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index)); - - if (design.DesignData.IsHuman) - { - foreach (var index in Enum.GetValues()) - { - if (other.DoApplyCustomize(index)) - ChangeCustomize(design, index, other.DesignData.Customize[index]); - } - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - if (other.DoApplyEquip(slot)) - ChangeEquip(design, slot, other.DesignData.Item(slot)); - - if (other.DoApplyStain(slot)) - ChangeStain(design, slot, other.DesignData.Stain(slot)); - } - - foreach (var slot in Enum.GetValues()) - { - if (other.DoApplyCrest(slot)) - ChangeCrest(design, slot, other.DesignData.Crest(slot)); - } - } - - if (other.DoApplyEquip(EquipSlot.MainHand)) - ChangeWeapon(design, EquipSlot.MainHand, other.DesignData.Item(EquipSlot.MainHand)); - - if (other.DoApplyEquip(EquipSlot.OffHand)) - ChangeWeapon(design, EquipSlot.OffHand, other.DesignData.Item(EquipSlot.OffHand)); - - if (other.DoApplyStain(EquipSlot.MainHand)) - ChangeStain(design, EquipSlot.MainHand, other.DesignData.Stain(EquipSlot.MainHand)); - - if (other.DoApplyStain(EquipSlot.OffHand)) - ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); - } + #endregion public void UndoDesignChange(Design design) { - if (!_undoStore.Remove(design.Identifier, out var otherData)) + if (!UndoStore.Remove(design.Identifier, out var otherData)) return; var other = CreateTemporary(); - other.SetDesignData(_customizations, otherData); + other.SetDesignData(Customizations, otherData); ApplyDesign(design, other); } private void MigrateOldDesigns() { - if (!File.Exists(_saveService.FileNames.MigrationDesignFile)) + if (!File.Exists(SaveService.FileNames.MigrationDesignFile)) return; var errors = 0; @@ -592,7 +383,7 @@ public class DesignManager var oldDesigns = Designs.ToList(); try { - var text = File.ReadAllText(_saveService.FileNames.MigrationDesignFile); + var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile); var dict = JsonConvert.DeserializeObject>(text) ?? new Dictionary(); var migratedFileSystemPaths = new Dictionary(dict.Count); foreach (var (name, base64) in dict) @@ -600,14 +391,14 @@ public class DesignManager try { var actualName = Path.GetFileName(name); - var design = new Design(_customizations, _items) + var design = new Design(Customizations, Items) { - CreationDate = File.GetCreationTimeUtc(_saveService.FileNames.MigrationDesignFile), - LastEdit = File.GetLastWriteTimeUtc(_saveService.FileNames.MigrationDesignFile), + CreationDate = File.GetCreationTimeUtc(SaveService.FileNames.MigrationDesignFile), + LastEdit = File.GetLastWriteTimeUtc(SaveService.FileNames.MigrationDesignFile), Identifier = CreateNewGuid(), Name = actualName, }; - design.MigrateBase64(_customizations, _items, _humans, base64); + design.MigrateBase64(Customizations, Items, _humans, base64); if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate)) { Add(design, $"Migrated old design to {design.Identifier}."); @@ -628,24 +419,24 @@ public class DesignManager } } - DesignFileSystem.MigrateOldPaths(_saveService, migratedFileSystemPaths); + DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths); Glamourer.Log.Information( $"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs."); } catch (Exception e) { - Glamourer.Log.Error($"Could not migrate old design file {_saveService.FileNames.MigrationDesignFile}:\n{e}"); + Glamourer.Log.Error($"Could not migrate old design file {SaveService.FileNames.MigrationDesignFile}:\n{e}"); } try { - File.Move(_saveService.FileNames.MigrationDesignFile, - Path.ChangeExtension(_saveService.FileNames.MigrationDesignFile, ".json.bak")); - Glamourer.Log.Information($"Moved migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file."); + File.Move(SaveService.FileNames.MigrationDesignFile, + Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak")); + Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file."); } catch (Exception ex) { - Glamourer.Log.Error($"Could not move migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file:\n{ex}"); + Glamourer.Log.Error($"Could not move migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file:\n{ex}"); } } @@ -675,7 +466,7 @@ public class DesignManager { try { - var correctName = _saveService.FileNames.DesignFile(design); + var correctName = SaveService.FileNames.DesignFile(design); File.Move(name, correctName, false); Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}."); } @@ -705,18 +496,17 @@ public class DesignManager /// Returns false if the design is already contained or if the identifier is already in use. /// The design is treated as newly created and invokes an event. /// - private bool Add(Design design, string? message) + private void Add(Design design, string? message) { if (Designs.Any(d => d == design || d.Identifier == design.Identifier)) - return false; + return; design.Index = Designs.Count; Designs.Add(design); if (!message.IsNullOrEmpty()) Glamourer.Log.Debug(message); - _saveService.ImmediateSave(design); - _event.Invoke(DesignChanged.Type.Created, design, null); - return true; + SaveService.ImmediateSave(design); + DesignChanged.Invoke(DesignChanged.Type.Created, design, null); } /// Split a given string into its folder path and its name, if is true. @@ -736,47 +526,4 @@ public class DesignManager return (actualName, path); } - - /// Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. - private bool ChangeMainhandPeriphery(Design design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff, - out EquipItem? newGauntlets) - { - newOff = null; - newGauntlets = null; - if (newMain.Type != currentMain.Type) - { - var defaultOffhand = _items.GetDefaultOffhand(newMain); - if (!_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) - return false; - - newOff = o; - } - else if (_config.ChangeEntireItem) - { - var defaultOffhand = _items.GetDefaultOffhand(newMain); - if (_items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o)) - newOff = o; - - if (newMain.Type is FullEquipType.Fists && _items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g)) - newGauntlets = g; - } - - if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain)) - return false; - - if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value)) - { - design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); - return false; - } - - if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value)) - { - design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain); - design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff); - return false; - } - - return true; - } } diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs new file mode 100644 index 0000000..8cf1a87 --- /dev/null +++ b/Glamourer/Designs/IDesignEditor.cs @@ -0,0 +1,49 @@ +using Glamourer.Designs.Links; +using Glamourer.GameData; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs; + +public readonly record struct ApplySettings( + uint Key = 0, + StateSource Source = StateSource.Manual, + bool RespectManual = false, + bool FromJobChange = false, + bool UseSingleSource = false); + +public interface IDesignEditor +{ + /// Change a customization value. + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings = default); + + /// Change an entire customize array according to the given flags. + public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings = default); + + /// Change a customize parameter. + public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue v, ApplySettings settings = default); + + /// Change an equipment piece. + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) + => ChangeEquip(data, slot, item, null, settings); + + /// Change the stain for any equipment piece. + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default) + => ChangeEquip(data, slot, null, stain, settings); + + /// Change an equipment piece and its stain at the same time. + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default); + + /// Change the crest visibility for any equipment piece. + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default); + + /// Change the bool value of one of the meta flags. + public void ChangeMetaState(object data, MetaIndex slot, bool value, ApplySettings settings = default); + + /// Change all values applies from the given design. + public void ApplyDesign(object data, MergedDesign design, ApplySettings settings = default); + + /// Change all values applies from the given design. + public void ApplyDesign(object data, DesignBase design, ApplySettings settings = default); +} diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 121c58c..f3ebb5f 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -56,6 +56,9 @@ public sealed class DesignChanged() /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. Customize, + /// An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. + EntireCustomize, + /// An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. Equip, diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index 1450fb5..f8dd42c 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -31,9 +31,7 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) => new(slot, design.DesignData) { - ItemSetter = slot.IsEquipment() || slot.IsAccessory() - ? i => manager.ChangeEquip(design, slot, i) - : i => manager.ChangeWeapon(design, slot, i), + ItemSetter = i => manager.ChangeItem(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index 5758229..deb1908 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -30,7 +30,7 @@ public ref struct ToggleDrawData DisplayApplication = true, CurrentValue = design.DesignData.GetMeta(index), CurrentApply = design.DoApplyMeta(index), - SetValue = b => manager.ChangeMeta(design, index, b), + SetValue = b => manager.ChangeMetaState(design, index, b), SetApply = b => manager.ChangeApplyMeta(design, index, b), };