diff --git a/Glamourer/Automation/ApplicationType.cs b/Glamourer/Automation/ApplicationType.cs new file mode 100644 index 0000000..e4866de --- /dev/null +++ b/Glamourer/Automation/ApplicationType.cs @@ -0,0 +1,62 @@ +using Glamourer.Designs; +using Glamourer.GameData; +using Penumbra.GameData.Enums; + +namespace Glamourer.Automation; + +[Flags] +public enum ApplicationType : byte +{ + Armor = 0x01, + Customizations = 0x02, + Weapons = 0x04, + GearCustomization = 0x08, + Accessories = 0x10, + + All = Armor | Accessories | Customizations | Weapons | GearCustomization, +} + +public static class ApplicationTypeExtensions +{ + public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, + bool + ApplyWeapon, bool ApplyWet) ApplyWhat(this ApplicationType type, DesignBase? design) + { + var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) + | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) + | (type.HasFlag(ApplicationType.Accessories) ? AccessoryFlags : 0) + | (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0); + var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; + var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; + var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; + + if (design == null) + return (equipFlags, customizeFlags, crestFlag, parameterFlags, type.HasFlag(ApplicationType.Armor), + type.HasFlag(ApplicationType.Armor), + type.HasFlag(ApplicationType.Weapons), type.HasFlag(ApplicationType.Customizations)); + + return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest, + parameterFlags & design.ApplyParameters, + type.HasFlag(ApplicationType.Armor) && design.DoApplyHatVisible(), + type.HasFlag(ApplicationType.Armor) && design.DoApplyVisorToggle(), + type.HasFlag(ApplicationType.Weapons) && design.DoApplyWeaponVisible(), + type.HasFlag(ApplicationType.Customizations) && design.DoApplyWetness()); + } + + public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; + public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; + public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; + + public const EquipFlag StainFlags = EquipFlag.MainhandStain + | EquipFlag.OffhandStain + | EquipFlag.HeadStain + | EquipFlag.BodyStain + | EquipFlag.HandsStain + | EquipFlag.LegsStain + | EquipFlag.FeetStain + | EquipFlag.EarsStain + | EquipFlag.NeckStain + | EquipFlag.WristStain + | EquipFlag.RFingerStain + | EquipFlag.LFingerStain; +} \ No newline at end of file diff --git a/Glamourer/Automation/AutoDesign.cs b/Glamourer/Automation/AutoDesign.cs index 7ab3021..9d709ab 100644 --- a/Glamourer/Automation/AutoDesign.cs +++ b/Glamourer/Automation/AutoDesign.cs @@ -12,22 +12,10 @@ public class AutoDesign { public const string RevertName = "Revert"; - [Flags] - public enum Type : byte - { - Armor = 0x01, - Customizations = 0x02, - Weapons = 0x04, - GearCustomization = 0x08, - Accessories = 0x10, - - All = Armor | Accessories | Customizations | Weapons | GearCustomization, - } - - public Design? Design; - public JobGroup Jobs; - public Type ApplicationType; - public short GearsetIndex = -1; + public Design? Design; + public JobGroup Jobs; + public ApplicationType Type; + public short GearsetIndex = -1; public string Name(bool incognito) => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; @@ -41,10 +29,10 @@ public class AutoDesign public AutoDesign Clone() => new() { - Design = Design, - ApplicationType = ApplicationType, - Jobs = Jobs, - GearsetIndex = GearsetIndex, + Design = Design, + Type = Type, + Jobs = Jobs, + GearsetIndex = GearsetIndex, }; public unsafe bool IsActive(Actor actor) @@ -64,9 +52,9 @@ public class AutoDesign public JObject Serialize() => new() { - ["Design"] = Design?.Identifier.ToString(), - ["ApplicationType"] = (uint)ApplicationType, - ["Conditions"] = CreateConditionObject(), + ["Design"] = Design?.Identifier.ToString(), + ["Type"] = (uint)Type, + ["Conditions"] = CreateConditionObject(), }; private JObject CreateConditionObject() @@ -82,42 +70,5 @@ public class AutoDesign public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() - { - var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) - | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) - | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) - | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); - var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; - var parameterFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeParameterExtensions.All : 0; - var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0; - - if (Revert) - return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor), - ApplicationType.HasFlag(Type.Armor), - ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); - - return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, - parameterFlags & Design.ApplyParameters, - ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), - ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), - ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), - ApplicationType.HasFlag(Type.Customizations) && Design.DoApplyWetness()); - } - - public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; - public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; - public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; - - public const EquipFlag StainFlags = EquipFlag.MainhandStain - | EquipFlag.OffhandStain - | EquipFlag.HeadStain - | EquipFlag.BodyStain - | EquipFlag.HandsStain - | EquipFlag.LegsStain - | EquipFlag.FeetStain - | EquipFlag.EarsStain - | EquipFlag.NeckStain - | EquipFlag.WristStain - | EquipFlag.RFingerStain - | EquipFlag.LFingerStain; + => Type.ApplyWhat(Design); } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index 07bf41f..a92ebe9 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -1,6 +1,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Designs; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; @@ -15,7 +16,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Automation; -public class AutoDesignApplier : IDisposable +public sealed class AutoDesignApplier : IDisposable { private readonly Configuration _config; private readonly AutoDesignManager _manager; @@ -30,6 +31,7 @@ public class AutoDesignApplier : IDisposable private readonly ObjectManager _objects; private readonly WeaponLoading _weapons; private readonly HumanModelList _humans; + private readonly DesignMerger _designMerger; private readonly IClientState _clientState; private ActorState? _jobChangeState; @@ -182,7 +184,7 @@ public class AutoDesignApplier : IDisposable } else if (_state.TryGetValue(id, out var state)) { - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); } } } @@ -196,9 +198,9 @@ public class AutoDesignApplier : IDisposable { if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); else if (_state.TryGetValue(id, out var state)) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); } } } @@ -276,7 +278,7 @@ public class AutoDesignApplier : IDisposable if (set.BaseState == AutoDesignSet.Base.Game) _state.ResetStateFixed(state, respectManual); else if (!respectManual) - state.RemoveFixedDesignSources(); + state.Source.RemoveFixedDesignSources(); if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; @@ -286,7 +288,7 @@ public class AutoDesignApplier : IDisposable if (!design.IsActive(actor)) continue; - if (design.ApplicationType is 0) + if (design.Type is 0) continue; ref readonly var data = ref design.GetDesignData(state); @@ -342,7 +344,7 @@ public class AutoDesignApplier : IDisposable if (!crestFlags.HasFlag(slot)) continue; - if (!respectManual || state[slot] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot] is not StateChanged.Source.Manual) _state.ChangeCrest(state, slot, design.Crest(slot), source); totalCrestFlags |= slot; } @@ -360,7 +362,7 @@ public class AutoDesignApplier : IDisposable if (!parameterFlags.HasFlag(flag)) continue; - if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) + if (!respectManual || state.Source[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); totalParameterFlags |= flag; } @@ -381,7 +383,7 @@ public class AutoDesignApplier : IDisposable var item = design.Item(slot); if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) { - if (!respectManual || state[slot, false] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual) _state.ChangeItem(state, slot, item, source); totalEquipFlags |= flag; } @@ -390,7 +392,7 @@ public class AutoDesignApplier : IDisposable var stainFlag = slot.ToStainFlag(); if (equipFlags.HasFlag(stainFlag)) { - if (!respectManual || state[slot, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, slot, design.Stain(slot), source); totalEquipFlags |= stainFlag; } @@ -400,7 +402,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.MainHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Source[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -420,7 +422,7 @@ public class AutoDesignApplier : IDisposable { var item = design.Item(EquipSlot.OffHand); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); - var checkState = !respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; + var checkState = !respectManual || state.Source[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; if (checkUnlock && checkState) { if (fromJobChange) @@ -438,14 +440,14 @@ public class AutoDesignApplier : IDisposable if (equipFlags.HasFlag(EquipFlag.MainhandStain)) { - if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); totalEquipFlags |= EquipFlag.MainhandStain; } if (equipFlags.HasFlag(EquipFlag.OffhandStain)) { - if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); totalEquipFlags |= EquipFlag.OffhandStain; } @@ -467,7 +469,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Clan)) { - if (!respectManual || state[CustomizeIndex.Clan] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Clan] is not StateChanged.Source.Manual) fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan); customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race; @@ -475,7 +477,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Gender)) { - if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Gender] is not StateChanged.Source.Manual) fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender); customizeFlags &= ~CustomizeFlag.Gender; totalCustomizeFlags |= CustomizeFlag.Gender; @@ -486,7 +488,7 @@ public class AutoDesignApplier : IDisposable if (customizeFlags.HasFlag(CustomizeFlag.Face)) { - if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[CustomizeIndex.Face] is not StateChanged.Source.Manual) _state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source); customizeFlags &= ~CustomizeFlag.Face; totalCustomizeFlags |= CustomizeFlag.Face; @@ -506,7 +508,7 @@ public class AutoDesignApplier : IDisposable if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) continue; - if (!respectManual || state[index] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[index] is not StateChanged.Source.Manual) _state.ChangeCustomize(state, index, value, source); totalCustomizeFlags |= flag; } @@ -518,28 +520,28 @@ public class AutoDesignApplier : IDisposable { if (applyHat && (totalMetaFlags & 0x01) == 0) { - if (!respectManual || state[ActorState.MetaIndex.HatState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.HatState] is not StateChanged.Source.Manual) _state.ChangeHatState(state, design.IsHatVisible(), source); totalMetaFlags |= 0x01; } if (applyVisor && (totalMetaFlags & 0x02) == 0) { - if (!respectManual || state[ActorState.MetaIndex.VisorState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.VisorState] is not StateChanged.Source.Manual) _state.ChangeVisorState(state, design.IsVisorToggled(), source); totalMetaFlags |= 0x02; } if (applyWeapon && (totalMetaFlags & 0x04) == 0) { - if (!respectManual || state[ActorState.MetaIndex.WeaponState] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.WeaponState] is not StateChanged.Source.Manual) _state.ChangeWeaponState(state, design.IsWeaponVisible(), source); totalMetaFlags |= 0x04; } if (applyWet && (totalMetaFlags & 0x08) == 0) { - if (!respectManual || state[ActorState.MetaIndex.Wetness] is not StateChanged.Source.Manual) + if (!respectManual || state.Source[MetaIndex.Wetness] is not StateChanged.Source.Manual) _state.ChangeWetness(state, design.IsWet(), source); totalMetaFlags |= 0x08; } diff --git a/Glamourer/Automation/AutoDesignManager.cs b/Glamourer/Automation/AutoDesignManager.cs index 38e4479..72b8daf 100644 --- a/Glamourer/Automation/AutoDesignManager.cs +++ b/Glamourer/Automation/AutoDesignManager.cs @@ -232,7 +232,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos var newDesign = new AutoDesign() { Design = design, - ApplicationType = AutoDesign.Type.All, + Type = ApplicationType.All, Jobs = _jobs.JobGroups[1], }; set.Designs.Add(newDesign); @@ -328,21 +328,21 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); } - public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type) + public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType) { if (which >= set.Designs.Count || which < 0) return; - type &= AutoDesign.Type.All; + applicationType &= ApplicationType.All; var design = set.Designs[which]; - if (design.ApplicationType == type) + if (design.Type == applicationType) return; - var old = design.ApplicationType; - design.ApplicationType = type; + var old = design.Type; + design.Type = applicationType; Save(); - Glamourer.Log.Debug($"Changed application type from {old} to {type} for associated design {which + 1} in design set."); - _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, type)); + Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set."); + _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType)); } public string ToFilename(FilenameService fileNames) @@ -490,12 +490,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos } } - var applicationType = (AutoDesign.Type)(jObj["ApplicationType"]?.ToObject() ?? 0); + // ApplicationType is a migration from an older property name. + var applicationType = (ApplicationType)(jObj["Type"]?.ToObject() ?? jObj["ApplicationType"]?.ToObject() ?? 0); - var ret = new AutoDesign() + var ret = new AutoDesign { Design = design, - ApplicationType = applicationType & AutoDesign.Type.All, + Type = applicationType & ApplicationType.All, }; return ParseConditions(setName, jObj, ret) ? ret : null; } @@ -550,7 +551,24 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos private ActorIdentifier[] GetGroup(ActorIdentifier identifier) { if (!identifier.IsValid) - return Array.Empty(); + return []; + + return identifier.Type switch + { + IdentifierType.Player => + [ + identifier.CreatePermanent(), + ], + IdentifierType.Retainer => + [ + _actors.CreateRetainer(identifier.PlayerName, + identifier.Retainer == ActorIdentifier.RetainerType.Mannequin + ? ActorIdentifier.RetainerType.Mannequin + : ActorIdentifier.RetainerType.Bell).CreatePermanent(), + ], + IdentifierType.Npc => CreateNpcs(_actors, identifier), + _ => [], + }; static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) { @@ -566,23 +584,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList, IDispos identifier.Kind, kvp.Key)).ToArray(); } - - return identifier.Type switch - { - IdentifierType.Player => new[] - { - identifier.CreatePermanent(), - }, - IdentifierType.Retainer => new[] - { - _actors.CreateRetainer(identifier.PlayerName, - identifier.Retainer == ActorIdentifier.RetainerType.Mannequin - ? ActorIdentifier.RetainerType.Mannequin - : ActorIdentifier.RetainerType.Bell).CreatePermanent(), - }, - IdentifierType.Npc => CreateNpcs(_actors, identifier), - _ => Array.Empty(), - }; } private void OnDesignChange(DesignChanged.Type type, Design design, object? data) diff --git a/Glamourer/Automation/AutoDesignSet.cs b/Glamourer/Automation/AutoDesignSet.cs index 3e1d713..29a0e2e 100644 --- a/Glamourer/Automation/AutoDesignSet.cs +++ b/Glamourer/Automation/AutoDesignSet.cs @@ -3,12 +3,12 @@ using Penumbra.GameData.Actors; namespace Glamourer.Automation; -public class AutoDesignSet +public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List designs) { - public readonly List Designs; + public readonly List Designs = designs; - public string Name; - public ActorIdentifier[] Identifiers; + public string Name = name; + public ActorIdentifier[] Identifiers = identifiers; public bool Enabled; public Base BaseState = Base.Current; @@ -32,13 +32,6 @@ public class AutoDesignSet : this(name, identifiers, new List()) { } - public AutoDesignSet(string name, ActorIdentifier[] identifiers, List designs) - { - Name = name; - Identifiers = identifiers; - Designs = designs; - } - public enum Base : byte { Current, diff --git a/Glamourer/Automation/FixedDesignMigrator.cs b/Glamourer/Automation/FixedDesignMigrator.cs index 9809fd8..856561a 100644 --- a/Glamourer/Automation/FixedDesignMigrator.cs +++ b/Glamourer/Automation/FixedDesignMigrator.cs @@ -56,7 +56,7 @@ public class FixedDesignMigrator(JobService jobs) autoManager.AddDesign(set, leaf.Value); autoManager.ChangeJobCondition(set, set.Designs.Count - 1, design.Item2); - autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? AutoDesign.Type.All : 0); + autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? ApplicationType.All : 0); } } } diff --git a/Glamourer/Designs/Design.cs b/Glamourer/Designs/Design.cs index 4ff6f2d..9f74af0 100644 --- a/Glamourer/Designs/Design.cs +++ b/Glamourer/Designs/Design.cs @@ -1,4 +1,6 @@ using Dalamud.Interface.Internal.Notifications; +using Glamourer.Automation; +using Glamourer.Designs.Links; using Glamourer.Interop.Penumbra; using Glamourer.Services; using Newtonsoft.Json; @@ -35,10 +37,11 @@ public sealed class Design : DesignBase, ISavable public DateTimeOffset LastEdit { get; internal set; } public LowerString Name { get; internal set; } = LowerString.Empty; public string Description { get; internal set; } = string.Empty; - public string[] Tags { get; internal set; } = Array.Empty(); + public string[] Tags { get; internal set; } = []; public int Index { get; internal set; } public string Color { get; internal set; } = string.Empty; - public SortedList AssociatedMods { get; private set; } = new(); + public SortedList AssociatedMods { get; private set; } = []; + public LinkContainer Links { get; private set; } = []; public string Incognito => Identifier.ToString()[..8]; @@ -64,6 +67,7 @@ public sealed class Design : DesignBase, ISavable ["Customize"] = SerializeCustomize(), ["Parameters"] = SerializeParameters(), ["Mods"] = SerializeMods(), + ["Links"] = Links.Serialize(), }; return ret; } @@ -95,24 +99,18 @@ public sealed class Design : DesignBase, ISavable #region Deserialization - public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json) + public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { var version = json["FileVersion"]?.ToObject() ?? 0; return version switch { - FileVersion => LoadDesignV1(customizations, items, json), + FileVersion => LoadDesignV1(customizations, items, linkLoader, json), _ => throw new Exception("The design to be loaded has no valid Version."), }; } - private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json) + private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json) { - static string[] ParseTags(JObject json) - { - var tags = json["Tags"]?.ToObject() ?? Array.Empty(); - return tags.OrderBy(t => t).Distinct().ToArray(); - } - var creationDate = json["CreationDate"]?.ToObject() ?? throw new ArgumentNullException("CreationDate"); var design = new Design(customizations, items) @@ -131,8 +129,15 @@ public sealed class Design : DesignBase, ISavable LoadEquip(items, json["Equipment"], design, design.Name, true); LoadMods(json["Mods"], design); LoadParameters(json["Parameters"], design, design.Name); + LoadLinks(linkLoader, json["Links"], design); design.Color = json["Color"]?.ToObject() ?? string.Empty; return design; + + static string[] ParseTags(JObject json) + { + var tags = json["Tags"]?.ToObject() ?? Array.Empty(); + return tags.OrderBy(t => t).Distinct().ToArray(); + } } private static void LoadMods(JToken? mods, Design design) @@ -161,6 +166,29 @@ public sealed class Design : DesignBase, ISavable } } + private static void LoadLinks(DesignLinkLoader linkLoader, JToken? links, Design design) + { + if (links is not JObject obj) + return; + + Parse(obj["Before"] as JArray, LinkOrder.Before); + Parse(obj["After"] as JArray, LinkOrder.After); + return; + + void Parse(JArray? array, LinkOrder order) + { + if (array == null) + return; + + foreach (var obj in array.OfType()) + { + var identifier = obj["Design"]?.ToObject() ?? throw new ArgumentNullException("Design"); + var type = (ApplicationType)(obj["Type"]?.ToObject() ?? 0); + linkLoader.AddObject(design, new LinkData(identifier, type, order)); + } + } + } + #endregion #region ISavable diff --git a/Glamourer/Designs/DesignConverter.cs b/Glamourer/Designs/DesignConverter.cs index 949fa06..596fbc3 100644 --- a/Glamourer/Designs/DesignConverter.cs +++ b/Glamourer/Designs/DesignConverter.cs @@ -1,4 +1,5 @@ -using Glamourer.GameData; +using Glamourer.Designs.Links; +using Glamourer.GameData; using Glamourer.Services; using Glamourer.State; using Glamourer.Utility; @@ -10,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Designs; -public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans) +public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader) { public const byte Version = 6; @@ -75,7 +76,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi case (byte)'{': var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); ret = jObj1["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj1) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj1) : DesignBase.LoadDesignBase(_customize, _items, jObj1); break; case 1: @@ -90,7 +91,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 3); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -101,7 +102,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 5); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } @@ -111,7 +112,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi var jObj2 = JObject.Parse(decompressed); Debug.Assert(version == 6); ret = jObj2["Identifier"] != null - ? Design.LoadDesign(_customize, _items, jObj2) + ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2) : DesignBase.LoadDesignBase(_customize, _items, jObj2); break; } diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index f16281a..2df7d8d 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -1,4 +1,5 @@ using Dalamud.Utility; +using Glamourer.Designs.Links; using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop.Penumbra; @@ -20,30 +21,33 @@ public class DesignManager private readonly HumanModelList _humans; private readonly SaveService _saveService; private readonly DesignChanged _event; - private readonly List _designs = []; + private readonly DesignStorage _designs; private readonly Dictionary _undoStore = []; public IReadOnlyList Designs => _designs; public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, - DesignChanged @event, HumanModelList humans) + DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader) { - _saveService = saveService; - _items = items; - _customizations = customizations; - _event = @event; - _humans = humans; + _designs = storage; + _saveService = saveService; + _items = items; + _customizations = customizations; + _event = @event; + _humans = humans; + + LoadDesigns(designLinkLoader); CreateDesignFolder(saveService); - LoadDesigns(); MigrateOldDesigns(); + designLinkLoader.SetAllObjects(); } /// /// 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() + public void LoadDesigns(DesignLinkLoader linkLoader) { _humans.Awaiter.Wait(); _customizations.Awaiter.Wait(); @@ -59,7 +63,7 @@ public class DesignManager { var text = File.ReadAllText(f.FullName); var data = JObject.Parse(text); - var design = Design.LoadDesign(_customizations, _items, data); + var design = Design.LoadDesign(_customizations, _items, linkLoader, data); designs.Value!.Add((design, f.FullName)); } catch (Exception ex) @@ -497,14 +501,14 @@ public class DesignManager } /// Change the bool value of one of the meta flags. - public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) + public void ChangeMeta(Design design, MetaIndex metaIndex, bool value) { var change = metaIndex switch { - ActorState.MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), - ActorState.MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), - ActorState.MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), - ActorState.MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), + MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), + MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), + MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), + MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), }; if (!change) @@ -517,14 +521,14 @@ public class DesignManager } /// Change the application value of one of the meta flags. - public void ChangeApplyMeta(Design design, ActorState.MetaIndex metaIndex, bool value) + public void ChangeApplyMeta(Design design, MetaIndex metaIndex, bool value) { var change = metaIndex switch { - ActorState.MetaIndex.Wetness => design.SetApplyWetness(value), - ActorState.MetaIndex.HatState => design.SetApplyHatVisible(value), - ActorState.MetaIndex.VisorState => design.SetApplyVisorToggle(value), - ActorState.MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), + MetaIndex.Wetness => design.SetApplyWetness(value), + MetaIndex.HatState => design.SetApplyHatVisible(value), + MetaIndex.VisorState => design.SetApplyVisorToggle(value), + MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), }; if (!change) diff --git a/Glamourer/Designs/DesignStorage.cs b/Glamourer/Designs/DesignStorage.cs new file mode 100644 index 0000000..297f3be --- /dev/null +++ b/Glamourer/Designs/DesignStorage.cs @@ -0,0 +1,6 @@ +using OtterGui.Services; + +namespace Glamourer.Designs; + +public class DesignStorage : List, IService +{} diff --git a/Glamourer/Designs/Links/DesignLink.cs b/Glamourer/Designs/Links/DesignLink.cs new file mode 100644 index 0000000..2adc055 --- /dev/null +++ b/Glamourer/Designs/Links/DesignLink.cs @@ -0,0 +1,18 @@ +using Glamourer.Automation; + +namespace Glamourer.Designs.Links; + +public record struct DesignLink(Design Link, ApplicationType Type); + +public readonly record struct LinkData(Guid Identity, ApplicationType Type, LinkOrder Order) +{ + public override string ToString() + => Identity.ToString(); +} + +public enum LinkOrder : byte +{ + Self, + After, + Before, +}; diff --git a/Glamourer/Designs/Links/DesignLinkLoader.cs b/Glamourer/Designs/Links/DesignLinkLoader.cs new file mode 100644 index 0000000..4d438bd --- /dev/null +++ b/Glamourer/Designs/Links/DesignLinkLoader.cs @@ -0,0 +1,27 @@ +using Dalamud.Interface.Internal.Notifications; +using OtterGui; +using OtterGui.Classes; +using OtterGui.Services; + +namespace Glamourer.Designs.Links; + +public sealed class DesignLinkLoader(DesignStorage designStorage, MessageService messager) + : DelayedReferenceLoader(messager), IService +{ + protected override bool TryGetObject(LinkData data, [NotNullWhen(true)] out Design? obj) + => designStorage.FindFirst(d => d.Identifier == data.Identity, out obj); + + protected override bool SetObject(Design parent, Design child, LinkData data, out string error) + => LinkContainer.AddLink(parent, child, data.Type, data.Order, out error); + + protected override void HandleChildNotFound(Design parent, LinkData data) + { + Messager.AddMessage(new Notification( + $"Could not find the design {data.Identity}. If this design was deleted, please re-save {parent.Identifier}.", + NotificationType.Warning)); + } + + protected override void HandleChildNotSet(Design parent, Design child, string error) + => Messager.AddMessage(new Notification($"Could not link {child.Identifier} to {parent.Identifier}: {error}", + NotificationType.Warning)); +} diff --git a/Glamourer/Designs/Links/DesignLinkManager.cs b/Glamourer/Designs/Links/DesignLinkManager.cs new file mode 100644 index 0000000..e3fe094 --- /dev/null +++ b/Glamourer/Designs/Links/DesignLinkManager.cs @@ -0,0 +1,74 @@ +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.Services; +using OtterGui.Services; + +namespace Glamourer.Designs.Links; + +public sealed class DesignLinkManager : IService, IDisposable +{ + private readonly DesignStorage _storage; + private readonly DesignChanged _event; + private readonly SaveService _saveService; + + public DesignLinkManager(DesignStorage storage, DesignChanged @event, SaveService saveService) + { + _storage = storage; + _event = @event; + _saveService = saveService; + + _event.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignLinkManager); + } + + public void Dispose() + => _event.Unsubscribe(OnDesignChanged); + + public void MoveDesignLink(Design parent, int idxFrom, LinkOrder orderFrom, int idxTo, LinkOrder orderTo) + { + if (!parent.Links.Reorder(idxFrom, orderFrom, idxTo, orderTo)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + public void AddDesignLink(Design parent, Design child, LinkOrder order) + { + if (!LinkContainer.AddLink(parent, child, ApplicationType.All, order, out _)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + public void RemoveDesignLink(Design parent, int idx, LinkOrder order) + { + if (!parent.Links.Remove(idx, order)) + return; + + parent.LastEdit = DateTimeOffset.UtcNow; + _saveService.QueueSave(parent); + Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}."); + _event.Invoke(DesignChanged.Type.ChangedLink, parent, null); + } + + private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _) + { + if (type is not DesignChanged.Type.Deleted) + return; + + foreach (var design in _storage) + { + if (design.Links.Remove(deletedDesign)) + { + design.LastEdit = DateTimeOffset.UtcNow; + Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion."); + _saveService.QueueSave(design); + } + } + } +} diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs new file mode 100644 index 0000000..94832f9 --- /dev/null +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -0,0 +1,265 @@ +using Glamourer.Automation; +using Glamourer.Events; +using Glamourer.GameData; +using Glamourer.Services; +using Glamourer.State; +using Penumbra.GameData.Enums; +using Penumbra.GameData.Structs; + +namespace Glamourer.Designs.Links; + +using WeaponDict = Dictionary; + +public sealed class MergedDesign +{ + public MergedDesign(DesignManager designManager) + { + Design = designManager.CreateTemporary(); + Design.ApplyEquip = 0; + Design.ApplyCustomize = 0; + Design.ApplyCrest = 0; + Design.ApplyParameters = 0; + Design.SetApplyWetness(false); + Design.SetApplyVisorToggle(false); + Design.SetApplyWeaponVisible(false); + Design.SetApplyHatVisible(false); + } + + public readonly DesignBase Design; + public readonly WeaponDict Weapons = new(4); + public readonly StateSource Source = new(); +} + +public class DesignMerger(DesignManager designManager, CustomizeService _customize) +{ + public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef) + { + var ret = new MergedDesign(designManager); + CustomizeFlag fixFlags = 0; + foreach (var (design, type) in designs) + { + if (type is 0) + continue; + + ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef(); + var source = design == null ? StateChanged.Source.Game : StateChanged.Source.Manual; + + if (!data.IsHuman) + continue; + + var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = type.ApplyWhat(design); + ReduceMeta(data, applyHat, applyVisor, applyWeapon, applyWet, ret, source); + ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source); + ReduceEquip(data, equipFlags, ret, source); + ReduceMainhands(data, equipFlags, ret, source); + ReduceOffhands(data, equipFlags, ret, source); + ReduceCrests(data, crestFlags, ret, source); + ReduceParameters(data, parameterFlags, ret, source); + } + + ApplyFixFlags(ret, fixFlags); + return ret; + } + + + private static void ReduceMeta(in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, MergedDesign ret, + StateChanged.Source source) + { + if (applyHat && !ret.Design.DoApplyHatVisible()) + { + ret.Design.SetApplyHatVisible(true); + ret.Design.GetDesignDataRef().SetHatVisible(design.IsHatVisible()); + ret.Source[MetaIndex.HatState] = source; + } + + if (applyVisor && !ret.Design.DoApplyVisorToggle()) + { + ret.Design.SetApplyVisorToggle(true); + ret.Design.GetDesignDataRef().SetVisor(design.IsVisorToggled()); + ret.Source[MetaIndex.VisorState] = source; + } + + if (applyWeapon && !ret.Design.DoApplyWeaponVisible()) + { + ret.Design.SetApplyWeaponVisible(true); + ret.Design.GetDesignDataRef().SetWeaponVisible(design.IsWeaponVisible()); + ret.Source[MetaIndex.WeaponState] = source; + } + + if (applyWet && !ret.Design.DoApplyWetness()) + { + ret.Design.SetApplyWetness(true); + ret.Design.GetDesignDataRef().SetIsWet(design.IsWet()); + ret.Source[MetaIndex.Wetness] = source; + } + } + + private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateChanged.Source source) + { + crestFlags &= ~ret.Design.ApplyCrest; + if (crestFlags == 0) + return; + + foreach (var slot in CrestExtensions.AllRelevantSet) + { + if (!crestFlags.HasFlag(slot)) + continue; + + ret.Design.GetDesignDataRef().SetCrest(slot, design.Crest(slot)); + ret.Design.SetApplyCrest(slot, true); + ret.Source[slot] = source; + } + } + + private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret, + StateChanged.Source source) + { + parameterFlags &= ~ret.Design.ApplyParameters; + if (parameterFlags == 0) + return; + + foreach (var flag in CustomizeParameterExtensions.AllFlags) + { + if (!parameterFlags.HasFlag(flag)) + continue; + + ret.Design.GetDesignDataRef().Parameters.Set(flag, design.Parameters[flag]); + ret.Design.SetApplyParameter(flag, true); + ret.Source[flag] = source; + } + } + + private static void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + equipFlags &= ~ret.Design.ApplyEquip; + if (equipFlags == 0) + return; + + foreach (var slot in EquipSlotExtensions.EqdpSlots) + { + var flag = slot.ToFlag(); + if (equipFlags.HasFlag(flag)) + { + ret.Design.GetDesignDataRef().SetItem(slot, design.Item(slot)); + ret.Design.SetApplyEquip(slot, true); + ret.Source[slot, false] = source; + } + + var stainFlag = slot.ToStainFlag(); + if (equipFlags.HasFlag(stainFlag)) + { + ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); + ret.Design.SetApplyStain(slot, true); + ret.Source[slot, true] = source; + } + } + + foreach (var slot in EquipSlotExtensions.WeaponSlots) + { + var stainFlag = slot.ToStainFlag(); + if (equipFlags.HasFlag(stainFlag)) + { + ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot)); + ret.Design.SetApplyStain(slot, true); + ret.Source[slot, true] = source; + } + } + } + + private static void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + if (!equipFlags.HasFlag(EquipFlag.Mainhand)) + return; + + ret.Design.SetApplyEquip(EquipSlot.MainHand, true); + var weapon = design.Item(EquipSlot.MainHand); + ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + } + + private static void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source) + { + if (!equipFlags.HasFlag(EquipFlag.Offhand)) + return; + + ret.Design.SetApplyEquip(EquipSlot.OffHand, true); + var weapon = design.Item(EquipSlot.OffHand); + if (weapon.Valid) + ret.Weapons.TryAdd(weapon.Type, (weapon, source)); + } + + private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret, + StateChanged.Source source) + { + customizeFlags &= ~ret.Design.ApplyCustomizeRaw; + if (customizeFlags == 0) + return; + + // Skip anything not human. + if (!ret.Design.DesignData.IsHuman || !design.IsHuman) + return; + + var customize = ret.Design.DesignData.Customize; + if (customizeFlags.HasFlag(CustomizeFlag.Clan)) + { + fixFlags |= _customize.ChangeClan(ref customize, design.Customize.Clan); + ret.Design.SetApplyCustomize(CustomizeIndex.Clan, true); + ret.Design.SetApplyCustomize(CustomizeIndex.Race, true); + customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); + ret.Source[CustomizeIndex.Clan] = source; + ret.Source[CustomizeIndex.Race] = source; + } + + if (customizeFlags.HasFlag(CustomizeFlag.Gender)) + { + fixFlags |= _customize.ChangeGender(ref customize, design.Customize.Gender); + ret.Design.SetApplyCustomize(CustomizeIndex.Gender, true); + customizeFlags &= ~CustomizeFlag.Gender; + ret.Source[CustomizeIndex.Gender] = source; + } + + if (customizeFlags.HasFlag(CustomizeFlag.Face)) + { + customize[CustomizeIndex.Face] = design.Customize.Face; + ret.Design.SetApplyCustomize(CustomizeIndex.Face, true); + customizeFlags &= ~CustomizeFlag.Face; + ret.Source[CustomizeIndex.Face] = source; + } + + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); + var face = customize.Face; + foreach (var index in Enum.GetValues()) + { + var flag = index.ToFlag(); + if (!customizeFlags.HasFlag(flag)) + continue; + + var value = design.Customize[index]; + if (!CustomizeService.IsCustomizationValid(set, face, index, value, out var data)) + continue; + + customize[index] = data?.Value ?? value; + ret.Design.SetApplyCustomize(index, true); + ret.Source[index] = source; + fixFlags &= ~flag; + } + } + + private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags) + { + if (fixFlags == 0) + return; + + var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan) + ? ret.Source[CustomizeIndex.Clan] + : ret.Source[CustomizeIndex.Gender]; + foreach (var index in Enum.GetValues()) + { + var flag = index.ToFlag(); + if (!fixFlags.HasFlag(flag)) + continue; + + ret.Source[index] = source; + ret.Design.SetApplyCustomize(index, true); + } + } +} diff --git a/Glamourer/Designs/Links/LinkContainer.cs b/Glamourer/Designs/Links/LinkContainer.cs new file mode 100644 index 0000000..08a1b6d --- /dev/null +++ b/Glamourer/Designs/Links/LinkContainer.cs @@ -0,0 +1,177 @@ +using Glamourer.Automation; +using Newtonsoft.Json.Linq; +using OtterGui.Filesystem; + +namespace Glamourer.Designs.Links; + +public sealed class LinkContainer : List +{ + public List Before + => this; + + public readonly List After = []; + + public new int Count + => base.Count + After.Count; + + public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder) + { + var fromList = fromOrder switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + + var toList = toOrder switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + + if (fromList == toList) + return fromList.Move(fromIndex, toIndex); + + if (fromIndex < 0 || fromIndex >= fromList.Count) + return false; + + toIndex = Math.Clamp(toIndex, 0, toList.Count); + toList.Insert(toIndex, fromList[fromIndex]); + fromList.RemoveAt(fromIndex); + return true; + } + + public bool Remove(int idx, LinkOrder order) + { + var list = order switch + { + LinkOrder.Before => Before, + LinkOrder.After => After, + _ => throw new ArgumentException("Invalid link order."), + }; + if (idx < 0 || idx >= list.Count) + return false; + + list.RemoveAt(idx); + return true; + } + + public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error) + { + if (parent == child) + { + error = $"Can not link {parent.Identifier} with itself."; + return false; + } + + if (parent.Links.Contains(child)) + { + error = $"Design {parent.Identifier} already contains a direct link to {child.Identifier}."; + return false; + } + + if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order)) + { + error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the parent already links to the child in the opposite direction."; + return false; + } + + if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order)) + { + error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the child already links to the parent in the opposite direction."; + return false; + } + + error = string.Empty; + return true; + } + + public static bool AddLink(Design parent, Design child, ApplicationType type, LinkOrder order, out string error) + { + if (!CanAddLink(parent, child, order, out error)) + return false; + + var list = order switch + { + LinkOrder.Before => parent.Links.Before, + LinkOrder.After => parent.Links.After, + _ => null, + }; + + if (list == null) + { + error = $"Order {order} is invalid."; + return false; + } + + type &= ApplicationType.All; + list.Add(new DesignLink(child, type)); + error = string.Empty; + return true; + } + + public bool Contains(Design child) + => Before.Any(l => l.Link == child) || After.Any(l => l.Link == child); + + public bool Remove(Design child) + => Before.RemoveAll(l => l.Link == child) + After.RemoveAll(l => l.Link == child) > 0; + + public static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(Design design) + { + var set = new HashSet(design.Links.Count * 4); + return GetAllLinks(new DesignLink(design, ApplicationType.All), LinkOrder.Self, set); + } + + private static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(DesignLink design, LinkOrder currentOrder, ISet visited) + { + if (design.Link.Links.Count == 0) + { + if (visited.Add(design.Link)) + yield return (design, currentOrder); + + yield break; + } + + foreach (var link in design.Link.Links.Before + .Where(l => !visited.Contains(l.Link)) + .SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.After ? LinkOrder.After : LinkOrder.Before, visited))) + yield return link; + + if (visited.Add(design.Link)) + yield return (design, currentOrder); + + foreach (var link in design.Link.Links.After.Where(l => !visited.Contains(l.Link)) + .SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.Before ? LinkOrder.Before : LinkOrder.After, visited))) + yield return link; + } + + public JObject Serialize() + { + var before = new JArray(); + foreach (var link in Before) + { + before.Add(new JObject + { + ["Design"] = link.Link.Identifier, + ["Type"] = (uint)link.Type, + }); + } + + var after = new JArray(); + foreach (var link in After) + { + before.Add(new JObject + { + ["Design"] = link.Link.Identifier, + ["Type"] = (uint)link.Type, + }); + } + + return new JObject + { + [nameof(Before)] = before, + [nameof(After)] = after, + }; + } +} diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 2217c34..9b75d5a 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -12,7 +12,7 @@ namespace Glamourer.Events; /// Parameter is any additional data depending on the type of change. /// /// -public sealed class DesignChanged() +public sealed class DesignChanged() : EventWrapper(nameof(DesignChanged)) { public enum Type @@ -50,6 +50,9 @@ public sealed class DesignChanged() /// An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. RemovedMod, + /// An existing design had a link to a different design added, removed or moved. Data is null. + ChangedLink, + /// An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. Customize, @@ -92,6 +95,9 @@ public sealed class DesignChanged() public enum Priority { + /// + DesignLinkManager = 1, + /// AutoDesignManager = 1, diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 431eed0..e85057d 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -141,7 +141,7 @@ public class ActorPanel( if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -187,21 +187,21 @@ public class ActorPanel( { using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); } ImGui.SameLine(); using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); } ImGui.SameLine(); using (_ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); } } diff --git a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs index a50445e..bb9e135 100644 --- a/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs +++ b/Glamourer/Gui/Tabs/AutomationTab/SetPanel.cs @@ -369,13 +369,13 @@ public class SetPanel( private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine) { using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale)); - var newType = design.ApplicationType; + var newType = design.Type; var newTypeInt = (uint)newType; style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) { - if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All)) - newType = (AutoDesign.Type)newTypeInt; + if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All)) + newType = (ApplicationType)newTypeInt; } style.Pop(); @@ -385,7 +385,7 @@ public class SetPanel( void Box(int idx) { var (type, description) = Types[idx]; - var value = design.ApplicationType.HasFlag(type); + var value = design.Type.HasFlag(type); if (ImGui.Checkbox($"##{(byte)type}", ref value)) newType = value ? newType | type : newType & ~type; ImGuiUtil.HoverTooltip(description); @@ -429,14 +429,14 @@ public class SetPanel( } - private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[] + private static readonly IReadOnlyList<(ApplicationType, string)> Types = new[] { - (AutoDesign.Type.Customizations, + (ApplicationType.Customizations, "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), - (AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), - (AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), - (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), + (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), + (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), + (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), }; private sealed class JobGroupCombo : FilterComboCache diff --git a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs index 5408a7c..6eac9ad 100644 --- a/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/ActiveStatePanel.cs @@ -70,44 +70,44 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; } - PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); + PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Source[MetaIndex.ModelId]); ImGui.TableNextRow(); - PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]); + PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Source[MetaIndex.Wetness]); ImGui.TableNextRow(); if (state.BaseData.IsHuman && state.ModelData.IsHuman) { - PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]); + PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Source[MetaIndex.HatState]); ImGui.TableNextRow(); PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), - state[ActorState.MetaIndex.VisorState]); + state.Source[MetaIndex.VisorState]); ImGui.TableNextRow(); PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), - state[ActorState.MetaIndex.WeaponState]); + state.Source[MetaIndex.WeaponState]); ImGui.TableNextRow(); foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) { - PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); + PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Source[slot, false]); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); - ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); + ImGuiUtil.DrawTableColumn(state.Source[slot, true].ToString()); } foreach (var type in Enum.GetValues()) { - PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); + PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Source[type]); ImGui.TableNextRow(); } foreach (var crest in CrestExtensions.AllRelevantSet) { - PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]); + PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Source[crest]); ImGui.TableNextRow(); } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]); + PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Source[flag]); ImGui.TableNextRow(); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs index cbc0e40..6dd7f6e 100644 --- a/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/AutoDesignPanel.cs @@ -42,7 +42,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr foreach (var (design, designIdx) in set.Designs.WithIndex()) { ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); - ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}"); + ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}"); } } } diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs new file mode 100644 index 0000000..774ee3c --- /dev/null +++ b/Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs @@ -0,0 +1,120 @@ +using Dalamud.Interface; +using Glamourer.Designs; +using Glamourer.Designs.Links; +using ImGuiNET; +using OtterGui; +using OtterGui.Raii; +using OtterGui.Services; + +namespace Glamourer.Gui.Tabs.DesignTab; + +public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService +{ + private int _dragDropIndex = -1; + private LinkOrder _dragDropOrder = LinkOrder.Self; + private int _dragDropTargetIndex = -1; + private LinkOrder _dragDropTargetOrder = LinkOrder.Self; + + public void Draw() + { + using var header = ImRaii.CollapsingHeader("Design Links"); + if (!header) + return; + + var width = ImGui.GetContentRegionAvail().X / 2; + DrawList(_selector.Selected!.Links.Before, LinkOrder.Before, width); + ImGui.SameLine(); + DrawList(_selector.Selected!.Links.After, LinkOrder.After, width); + + if (_dragDropTargetIndex < 0 + || _dragDropIndex < 0) + return; + + _linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder); + _dragDropIndex = -1; + _dragDropTargetIndex = -1; + _dragDropOrder = LinkOrder.Self; + _dragDropTargetOrder = LinkOrder.Self; + } + + private void DrawList(IReadOnlyList list, LinkOrder order, float width) + { + using var id = ImRaii.PushId((int)order); + using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter, + new Vector2(width, list.Count * ImGui.GetFrameHeightWithSpacing())); + if (!table) + return; + + var buttonSize = new Vector2(ImGui.GetFrameHeight()); + for (var i = 0; i < list.Count; ++i) + { + id.Push(i); + + ImGui.TableNextColumn(); + var delete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this link.", false, true); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted($"#{i:D2}"); + + var (design, flags) = list[i]; + ImGui.TableNextColumn(); + + ImGui.AlignTextToFramePadding(); + ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text); + DrawDragDrop(design, order, i); + + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(flags.ToString()); + + if (delete) + _linkManager.RemoveDesignLink(_selector.Selected!, i--, order); + } + + ImGui.TableNextColumn(); + string tt; + bool canAdd; + if (_combo.Design == null) + { + tt = "Select a design first."; + canAdd = false; + } + else + { + canAdd = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, order, out var error); + tt = canAdd ? $"Add a link to {_combo.Design.Name}." : $"Can not add a link to {_combo.Design.Name}: {error}"; + } + + if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, !canAdd, true)) + _linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, order); + + ImGui.TableNextColumn(); + ImGui.TableNextColumn(); + _combo.Draw(200); + } + + private void DrawDragDrop(Design design, LinkOrder order, int index) + { + using (var source = ImRaii.DragDropSource()) + { + if (source) + { + ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0); + ImGui.TextUnformatted($"Reordering {design.Name}..."); + _dragDropIndex = index; + _dragDropOrder = order; + } + } + + using var target = ImRaii.DragDropTarget(); + if (!target) + return; + + if (!ImGuiUtil.IsDropping("DraggingLink")) + return; + + _dragDropTargetIndex = index; + _dragDropTargetOrder = order; + } +} diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 447fc5d..f942439 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -31,7 +31,8 @@ public class DesignPanel( DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel, - CustomizeParameterDrawer _parameterDrawer) + CustomizeParameterDrawer _parameterDrawer, + DesignLinkDrawer _designLinkDrawer) { private readonly FileDialogManager _fileDialog = new(); @@ -119,21 +120,21 @@ public class DesignPanel( { using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); } ImGui.SameLine(); using (var _ = ImRaii.Group()) { - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); } } @@ -158,7 +159,7 @@ public class DesignPanel( _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); } - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } @@ -166,6 +167,7 @@ public class DesignPanel( { if (!_config.UseAdvancedParameters) return; + using var h = ImRaii.CollapsingHeader("Advanced Customizations"); if (!h) return; @@ -259,20 +261,20 @@ public class DesignPanel( } } - ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[] + ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[] { EquipSlot.MainHand, EquipSlot.OffHand, }); ImGui.NewLine(); - ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); + ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); ImGui.NewLine(); - ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); + ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); ImGui.NewLine(); - ApplyEquip("Dyes", AutoDesign.StainFlags, true, + ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true, EquipSlotExtensions.FullSlots); ImGui.NewLine(); @@ -294,19 +296,19 @@ public class DesignPanel( var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply); apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, apply); apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, apply); apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) - _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); + _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, apply); } private void DrawParameterApplication() @@ -370,6 +372,7 @@ public class DesignPanel( _designDetails.Draw(); DrawApplicationRules(); _modAssociations.Draw(); + _designLinkDrawer.Draw(); } private void DrawButtonRow() diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 2852128..a406a7b 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -172,7 +172,7 @@ public class NpcPanel( _equipDrawer.DrawWeapons(mainhandData, offhandData, false); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); - EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled)); + EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index ed435c4..bb834a4 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -22,17 +22,17 @@ public ref struct ToggleDrawData public ToggleDrawData() { } - public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design) + public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design) { var (label, value, apply, setValue, setApply) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), + MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), (Action)(b => manager.ChangeMeta(design, index, b)), (Action)(b => manager.ChangeApplyMeta(design, index, b))), - ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), + MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), + MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), - ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), + MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), _ => throw new Exception("Unsupported meta index."), }; @@ -73,19 +73,19 @@ public ref struct ToggleDrawData SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), }; - public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) + public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) { var (label, tooltip, value, setValue) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), + MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), (Action)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), - ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", + MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", state.ModelData.IsVisorToggled(), b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", + MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", state.ModelData.IsWeaponVisible(), b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), - ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), + MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), _ => throw new Exception("Unsupported meta index."), }; @@ -100,14 +100,14 @@ public ref struct ToggleDrawData }; } - public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value) + public static ToggleDrawData FromValue(MetaIndex index, bool value) { var (label, tooltip) = index switch { - ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), - ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), - ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), - ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), + MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), + MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), + MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), + MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), _ => throw new Exception("Unsupported meta index."), }; return new ToggleDrawData diff --git a/Glamourer/Interop/CharaFile/CharaFile.cs b/Glamourer/Interop/CharaFile/CharaFile.cs index 8e25d8e..0613fb3 100644 --- a/Glamourer/Interop/CharaFile/CharaFile.cs +++ b/Glamourer/Interop/CharaFile/CharaFile.cs @@ -14,24 +14,17 @@ public sealed class CharaFile public CustomizeFlag ApplyCustomize; public EquipFlag ApplyEquip; - public static CharaFile? ParseData(ItemManager items, string data, string? name = null) + public static CharaFile ParseData(ItemManager items, string data, string? name = null) { - try - { - var jObj = JObject.Parse(data); - SanityCheck(jObj); - var ret = new CharaFile(); - ret.Data.SetDefaultEquipment(items); - ret.Data.ModelId = ParseModelId(jObj); - ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; - ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); - ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); - return ret; - } - catch - { - return null; - } + var jObj = JObject.Parse(data); + SanityCheck(jObj); + var ret = new CharaFile(); + ret.Data.SetDefaultEquipment(items); + ret.Data.ModelId = ParseModelId(jObj); + ret.Name = jObj["Nickname"]?.ToObject() ?? name ?? "New Design"; + ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); + ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data); + return ret; } private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) @@ -282,9 +275,6 @@ public sealed class CharaFile private static void SanityCheck(JObject jObj) { - if (jObj["TypeName"]?.ToObject() is not "Anamnesis Character File") - throw new Exception("Wrong TypeName property set."); - var type = jObj["ObjectKind"]?.ToObject(); if (type is not "Player") throw new Exception($"ObjectKind {type} != Player is not supported."); diff --git a/Glamourer/Interop/ImportService.cs b/Glamourer/Interop/ImportService.cs index b31a1b0..9587feb 100644 --- a/Glamourer/Interop/ImportService.cs +++ b/Glamourer/Interop/ImportService.cs @@ -63,8 +63,6 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d { var text = File.ReadAllText(path); var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); - if (file == null) - throw new Exception(); name = file.Name; design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); diff --git a/Glamourer/Interop/PalettePlus/PaletteImport.cs b/Glamourer/Interop/PalettePlus/PaletteImport.cs index 474527b..b26f8f9 100644 --- a/Glamourer/Interop/PalettePlus/PaletteImport.cs +++ b/Glamourer/Interop/PalettePlus/PaletteImport.cs @@ -41,9 +41,9 @@ public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager design.ApplyCustomize = 0; design.ApplyEquip = 0; design.ApplyCrest = 0; - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false); - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false); - designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, false); + designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false); + designManager.ChangeApplyMeta(design, MetaIndex.HatState, false); + designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false); foreach (var flag in flags.Iterate()) { designManager.ChangeApplyParameter(design, flag, true); diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index fa08425..f659847 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -255,26 +255,26 @@ public class CommandService : IDisposable } --designIdx; - AutoDesign.Type applicationFlags = 0; + ApplicationType applicationFlags = 0; if (split2.Length == 2) foreach (var character in split2[1]) { switch (char.ToLowerInvariant(character)) { case 'c': - applicationFlags |= AutoDesign.Type.Customizations; + applicationFlags |= ApplicationType.Customizations; break; case 'e': - applicationFlags |= AutoDesign.Type.Armor; + applicationFlags |= ApplicationType.Armor; break; case 'a': - applicationFlags |= AutoDesign.Type.Accessories; + applicationFlags |= ApplicationType.Accessories; break; case 'd': - applicationFlags |= AutoDesign.Type.GearCustomization; + applicationFlags |= ApplicationType.GearCustomization; break; case 'w': - applicationFlags |= AutoDesign.Type.Weapons; + applicationFlags |= ApplicationType.Weapons; break; default: _chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true) diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index ef00f38..83b22ac 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -11,15 +11,6 @@ namespace Glamourer.State; public class ActorState { - public enum MetaIndex - { - Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, - HatState, - VisorState, - WeaponState, - ModelId, - } - public readonly ActorIdentifier Identifier; public bool AllowsRedraw(ICondition condition) @@ -77,47 +68,14 @@ public class ActorState => Unlock(1337); /// This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. - private readonly StateChanged.Source[] _sources = Enumerable - .Repeat(StateChanged.Source.Game, - EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices - + 5 - + CrestExtensions.AllRelevantSet.Count - + CustomizeParameterExtensions.AllFlags.Count).ToArray(); + public readonly StateSource Source = new(); internal ActorState(ActorIdentifier identifier) => Identifier = identifier.CreatePermanent(); - public ref StateChanged.Source this[EquipSlot slot, bool stain] - => ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; - - public ref StateChanged.Source this[CrestFlag slot] - => ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; - - public ref StateChanged.Source this[CustomizeIndex type] - => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; - - public ref StateChanged.Source this[MetaIndex index] - => ref _sources[(int)index]; - - public ref StateChanged.Source this[CustomizeParameterFlag flag] - => ref _sources[ - EquipFlagExtensions.NumEquipFlags - + CustomizationExtensions.NumIndices + 5 - + CrestExtensions.AllRelevantSet.Count - + flag.ToInternalIndex()]; - - public void RemoveFixedDesignSources() - { - for (var i = 0; i < _sources.Length; ++i) - { - if (_sources[i] is StateChanged.Source.Fixed) - _sources[i] = StateChanged.Source.Manual; - } - } - public CustomizeParameterFlag OnlyChangedParameters() - => CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b); + => CustomizeParameterExtensions.AllFlags.Where(f => Source[f] is not StateChanged.Source.Game) + .Aggregate((CustomizeParameterFlag)0, (a, b) => a | b); public bool UpdateTerritory(ushort territory) { @@ -127,4 +85,4 @@ public class ActorState LastTerritory = territory; return true; } -} +} \ No newline at end of file diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 8b69e0a..d497006 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -118,7 +118,7 @@ public class StateApplier( // If the source is not IPC we do not want to apply restrictions. var data = GetData(state); if (apply) - ChangeArmor(data, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, + ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); return data; diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 9dc8418..c9f1d58 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -61,21 +61,21 @@ public class StateEditor state.ModelData.SetHatVisible(true); state.ModelData.SetWeaponVisible(true); state.ModelData.SetVisor(false); - state[ActorState.MetaIndex.ModelId] = source; - state[ActorState.MetaIndex.HatState] = source; - state[ActorState.MetaIndex.WeaponState] = source; - state[ActorState.MetaIndex.VisorState] = source; + state.Source[MetaIndex.ModelId] = source; + state.Source[MetaIndex.HatState] = source; + state.Source[MetaIndex.WeaponState] = source; + state.Source[MetaIndex.VisorState] = source; foreach (var slot in EquipSlotExtensions.FullSlots) { - state[slot, true] = source; - state[slot, false] = source; + state.Source[slot, true] = source; + state.Source[slot, false] = source; } - state[CustomizeIndex.Clan] = source; - state[CustomizeIndex.Gender] = source; + state.Source[CustomizeIndex.Clan] = source; + state.Source[CustomizeIndex.Gender] = source; var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); foreach (var index in Enum.GetValues().Where(set.IsAvailable)) - state[index] = source; + state.Source[index] = source; } else { @@ -83,7 +83,7 @@ public class StateEditor return false; state.ModelData.LoadNonHuman(modelId, customize, equipData); - state[ActorState.MetaIndex.ModelId] = source; + state.Source[MetaIndex.ModelId] = source; } return true; @@ -98,7 +98,7 @@ public class StateEditor return false; state.ModelData.Customize[idx] = value; - state[idx] = source; + state.Source[idx] = source; return true; } @@ -120,7 +120,7 @@ public class StateEditor foreach (var type in Enum.GetValues()) { if (applied.HasFlag(type.ToFlag())) - state[type] = source; + state.Source[type] = source; } return true; @@ -144,12 +144,12 @@ public class StateEditor _gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeItem(state, slot, old, state[slot, false], out _, key); + ChangeItem(state, slot, old, state.Source[slot, false], out _, key); }); } state.ModelData.SetItem(slot, item); - state[slot, false] = source; + state.Source[slot, false] = source; return true; } @@ -174,14 +174,14 @@ public class StateEditor _gPose.AddActionOnLeave(() => { if (old.Type == state.BaseData.Item(slot).Type) - ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key); + ChangeEquip(state, slot, old, oldS, state.Source[slot, false], out _, out _, key); }); } state.ModelData.SetItem(slot, item); state.ModelData.SetStain(slot, stain); - state[slot, false] = source; - state[slot, true] = source; + state.Source[slot, false] = source; + state.Source[slot, true] = source; return true; } @@ -193,7 +193,7 @@ public class StateEditor return false; state.ModelData.SetStain(slot, stain); - state[slot, true] = source; + state.Source[slot, true] = source; return true; } @@ -205,7 +205,7 @@ public class StateEditor return false; state.ModelData.SetCrest(slot, crest); - state[slot] = source; + state.Source[slot] = source; return true; } @@ -218,20 +218,20 @@ public class StateEditor return false; state.ModelData.Parameters.Set(flag, value); - state[flag] = source; + state.Source[flag] = source; return true; } - public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, + public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, uint key = 0) { (var setter, oldValue) = index switch { - ActorState.MetaIndex.Wetness => ((Func)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), - ActorState.MetaIndex.HatState => ((Func)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), - ActorState.MetaIndex.VisorState => ((Func)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), - ActorState.MetaIndex.WeaponState => ((Func)(v => state.ModelData.SetWeaponVisible(v)), + MetaIndex.Wetness => ((Func)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), + MetaIndex.HatState => ((Func)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), + MetaIndex.VisorState => ((Func)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), + MetaIndex.WeaponState => ((Func)(v => state.ModelData.SetWeaponVisible(v)), state.ModelData.IsWeaponVisible()), _ => throw new Exception("Invalid MetaIndex."), }; @@ -240,7 +240,7 @@ public class StateEditor return false; setter(value); - state[index] = source; + state.Source[index] = source; return true; } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index b90d835..d529206 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -170,7 +170,7 @@ public class StateListener : IDisposable var set = _customizations.Manager.GetSet(model.Clan, model.Gender); foreach (var index in CustomizationExtensions.AllBasic) { - if (state[index] is not StateChanged.Source.Fixed) + if (state.Source[index] is not StateChanged.Source.Fixed) { var newValue = customize[index]; var oldValue = model[index]; @@ -213,7 +213,7 @@ public class StateListener : IDisposable && _manager.TryGetValue(identifier, out var state)) { HandleEquipSlot(actor, state, slot, ref armor); - locked = state[slot, false] is StateChanged.Source.Ipc; + locked = state.Source[slot, false] is StateChanged.Source.Ipc; } _funModule.ApplyFunToSlot(actor, ref armor, slot); @@ -240,7 +240,7 @@ public class StateListener : IDisposable continue; var changed = changedItem.Weapon(stain); - if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (current.Value == changed.Value && state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) { _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game); _manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game); @@ -251,7 +251,7 @@ public class StateListener : IDisposable _applier.ChangeWeapon(objects, slot, currentItem, stain); break; default: - _applier.ChangeArmor(objects, slot, current.ToArmor(), state[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); break; } @@ -285,12 +285,12 @@ public class StateListener : IDisposable // Do nothing. But this usually can not happen because the hooked function also writes to game objects later. case UpdateState.Transformed: break; case UpdateState.Change: - if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); else apply = true; - if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; @@ -384,12 +384,12 @@ public class StateListener : IDisposable // Update model state if not on fixed design. case UpdateState.Change: var apply = false; - if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); else apply = true; - if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; @@ -418,7 +418,7 @@ public class StateListener : IDisposable switch (UpdateBaseCrest(actor, state, slot, value)) { case UpdateState.Change: - if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) + if (state.Source[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); else value = state.ModelData.Crest(slot); @@ -564,7 +564,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsVisorToggled(); else _manager.ChangeVisorState(state, value, StateChanged.Source.Game); @@ -597,7 +597,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsHatVisible(); else _manager.ChangeHatState(state, value, StateChanged.Source.Game); @@ -630,7 +630,7 @@ public class StateListener : IDisposable { // if base state changed, either overwrite the actual value if we have fixed values, // or overwrite the stored model state with the new one. - if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) + if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) value = state.ModelData.IsWeaponVisible(); else _manager.ChangeWeaponState(state, value, StateChanged.Source.Game); @@ -732,7 +732,7 @@ public class StateListener : IDisposable foreach (var flag in CustomizeParameterExtensions.AllFlags) { var newValue = data[flag]; - switch (state[flag]) + switch (state.Source[flag]) { case StateChanged.Source.Game: if (state.BaseData.Parameters.Set(flag, newValue)) @@ -755,7 +755,7 @@ public class StateListener : IDisposable break; case StateChanged.Source.Pending: state.BaseData.Parameters.Set(flag, newValue); - state[flag] = StateChanged.Source.Manual; + state.Source[flag] = StateChanged.Source.Manual; if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 0286e73..4bbc595 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -329,49 +329,49 @@ public class StateManager( /// Change hat visibility. public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.HatState, value, source, out var old, key)) return; var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.HatState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState)); } /// Change weapon visibility. public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.WeaponState, value, source, out var old, key)) return; var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.WeaponState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.WeaponState)); } /// Change visor state. public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.VisorState, value, source, out var old, key)) return; var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); Glamourer.Log.Verbose( $"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.VisorState)); + _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.VisorState)); } /// Set GPose Wetness. public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0) { - if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, value, source, out var old, key)) + if (!_editor.ChangeMetaState(state, MetaIndex.Wetness, value, source, out var old, key)) return; var actors = _applier.ChangeWetness(state, true); Glamourer.Log.Verbose( $"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, actors, (old, value, ActorState.MetaIndex.Wetness)); + _event.Invoke(StateChanged.Type.Other, state.Source[MetaIndex.Wetness], state, actors, (old, value, MetaIndex.Wetness)); } #endregion @@ -385,16 +385,16 @@ public class StateManager( var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman; if (design.DoApplyWetness()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); if (state.ModelData.IsHuman) { if (design.DoApplyHatVisible()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); if (design.DoApplyWeaponVisible()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); if (design.DoApplyVisorToggle()) - _editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); + _editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); var flags = state.AllowsRedraw(_condition) ? design.ApplyCustomize @@ -419,13 +419,13 @@ public class StateManager( if (!state.ModelData.Customize.Highlights) _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], - state[CustomizeParameterFlag.HairDiffuse], out _, key); + state.Source[CustomizeParameterFlag.HairDiffuse], out _, key); } var actors = ApplyAll(state, redraw, false); Glamourer.Log.Verbose( $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); - _event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design); + _event.Invoke(StateChanged.Type.Design, state.Source[MetaIndex.Wetness], state, actors, design); return; void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) @@ -455,7 +455,7 @@ public class StateManager( _applier.ChangeCustomize(actors, state.ModelData.Customize); foreach (var slot in EquipSlotExtensions.EqdpSlots) { - _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, + _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc, state.ModelData.IsHatVisible()); } @@ -489,22 +489,22 @@ public class StateManager( state.ModelData = state.BaseData; state.ModelData.SetIsWet(false); foreach (var index in Enum.GetValues()) - state[index] = StateChanged.Source.Game; + state.Source[index] = StateChanged.Source.Game; foreach (var slot in EquipSlotExtensions.FullSlots) { - state[slot, true] = StateChanged.Source.Game; - state[slot, false] = StateChanged.Source.Game; + state.Source[slot, true] = StateChanged.Source.Game; + state.Source[slot, false] = StateChanged.Source.Game; } - foreach (var type in Enum.GetValues()) - state[type] = StateChanged.Source.Game; + foreach (var type in Enum.GetValues()) + state.Source[type] = StateChanged.Source.Game; foreach (var slot in CrestExtensions.AllRelevantSet) - state[slot] = StateChanged.Source.Game; + state.Source[slot] = StateChanged.Source.Game; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) @@ -523,7 +523,7 @@ public class StateManager( state.ModelData.Parameters = state.BaseData.Parameters; foreach (var flag in CustomizeParameterExtensions.AllFlags) - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; var actors = ActorData.Invalid; if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) @@ -538,69 +538,69 @@ public class StateManager( if (!state.Unlock(key)) return; - foreach (var index in Enum.GetValues().Where(i => state[i] is StateChanged.Source.Fixed)) + foreach (var index in Enum.GetValues().Where(i => state.Source[i] is StateChanged.Source.Fixed)) { - state[index] = StateChanged.Source.Game; + state.Source[index] = StateChanged.Source.Game; state.ModelData.Customize[index] = state.BaseData.Customize[index]; } foreach (var slot in EquipSlotExtensions.FullSlots) { - if (state[slot, true] is StateChanged.Source.Fixed) + if (state.Source[slot, true] is StateChanged.Source.Fixed) { - state[slot, true] = StateChanged.Source.Game; + state.Source[slot, true] = StateChanged.Source.Game; state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); } - if (state[slot, false] is StateChanged.Source.Fixed) + if (state.Source[slot, false] is StateChanged.Source.Fixed) { - state[slot, false] = StateChanged.Source.Game; + state.Source[slot, false] = StateChanged.Source.Game; state.ModelData.SetItem(slot, state.BaseData.Item(slot)); } } foreach (var slot in CrestExtensions.AllRelevantSet) { - if (state[slot] is StateChanged.Source.Fixed) + if (state.Source[slot] is StateChanged.Source.Fixed) { - state[slot] = StateChanged.Source.Game; + state.Source[slot] = StateChanged.Source.Game; state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); } } foreach (var flag in CustomizeParameterExtensions.AllFlags) { - switch (state[flag]) + switch (state.Source[flag]) { case StateChanged.Source.Fixed: case StateChanged.Source.Manual when !respectManualPalettes: - state[flag] = StateChanged.Source.Game; + state.Source[flag] = StateChanged.Source.Game; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; break; } } - if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; + state.Source[MetaIndex.HatState] = StateChanged.Source.Game; state.ModelData.SetHatVisible(state.BaseData.IsHatVisible()); } - if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.VisorState] = StateChanged.Source.Game; + state.Source[MetaIndex.VisorState] = StateChanged.Source.Game; state.ModelData.SetVisor(state.BaseData.IsVisorToggled()); } - if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.WeaponState] = StateChanged.Source.Game; + state.Source[MetaIndex.WeaponState] = StateChanged.Source.Game; state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible()); } - if (state[ActorState.MetaIndex.Wetness] is StateChanged.Source.Fixed) + if (state.Source[MetaIndex.Wetness] is StateChanged.Source.Fixed) { - state[ActorState.MetaIndex.Wetness] = StateChanged.Source.Game; + state.Source[MetaIndex.Wetness] = StateChanged.Source.Game; state.ModelData.SetIsWet(state.BaseData.IsWet()); } } diff --git a/Glamourer/State/StateSource.cs b/Glamourer/State/StateSource.cs new file mode 100644 index 0000000..04c934d --- /dev/null +++ b/Glamourer/State/StateSource.cs @@ -0,0 +1,58 @@ +using Glamourer.GameData; +using Penumbra.GameData.Enums; +using static Glamourer.Events.StateChanged; + +namespace Glamourer.State; + +public enum MetaIndex +{ + Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices, + HatState, + VisorState, + WeaponState, + ModelId, +} + +public readonly struct StateSource +{ + public static readonly int Size = EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + + 5 + + CrestExtensions.AllRelevantSet.Count + + CustomizeParameterExtensions.AllFlags.Count; + + + private readonly Source[] _data = Enumerable.Repeat(Source.Game, Size).ToArray(); + + public StateSource() + { } + + public ref Source this[EquipSlot slot, bool stain] + => ref _data[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; + + public ref Source this[CrestFlag slot] + => ref _data[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()]; + + public ref Source this[CustomizeIndex type] + => ref _data[EquipFlagExtensions.NumEquipFlags + (int)type]; + + public ref Source this[MetaIndex index] + => ref _data[(int)index]; + + public ref Source this[CustomizeParameterFlag flag] + => ref _data[ + EquipFlagExtensions.NumEquipFlags + + CustomizationExtensions.NumIndices + + 5 + + CrestExtensions.AllRelevantSet.Count + + flag.ToInternalIndex()]; + + public void RemoveFixedDesignSources() + { + for (var i = 0; i < _data.Length; ++i) + { + if (_data[i] is Source.Fixed) + _data[i] = Source.Manual; + } + } +} diff --git a/OtterGui b/OtterGui index d734d5d..04eb0b5 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit d734d5d2b0686db0f5f4270dc379360d31f72e59 +Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3