diff --git a/Glamourer.GameData/Structs/CrestFlag.cs b/Glamourer.GameData/Structs/CrestFlag.cs new file mode 100644 index 0000000..17275f5 --- /dev/null +++ b/Glamourer.GameData/Structs/CrestFlag.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent; +using Penumbra.GameData.Enums; + +namespace Glamourer.Structs; + +[Flags] +public enum CrestFlag : ushort +{ + Head = 0x0001, + Body = 0x0002, + Hands = 0x0004, + Legs = 0x0008, + Feet = 0x0010, + Ears = 0x0020, + Neck = 0x0040, + Wrist = 0x0080, + RFinger = 0x0100, + LFinger = 0x0200, + Mainhand = 0x0400, + Offhand = 0x0800, +} + +public static class CrestExtensions +{ + public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); + public const CrestFlag AllRelevant = CrestFlag.Body; + + public static CrestFlag ToCrestFlag(this EquipSlot slot) + => slot switch + { + EquipSlot.MainHand => CrestFlag.Mainhand, + EquipSlot.OffHand => CrestFlag.Offhand, + EquipSlot.Head => CrestFlag.Head, + EquipSlot.Body => CrestFlag.Body, + EquipSlot.Hands => CrestFlag.Hands, + EquipSlot.Legs => CrestFlag.Legs, + EquipSlot.Feet => CrestFlag.Feet, + EquipSlot.Ears => CrestFlag.Ears, + EquipSlot.Neck => CrestFlag.Neck, + EquipSlot.Wrists => CrestFlag.Wrist, + EquipSlot.RFinger => CrestFlag.RFinger, + EquipSlot.LFinger => CrestFlag.LFinger, + _ => 0, + }; + + public static bool Valid(this CrestFlag crest) + => AllRelevant.HasFlag(crest); +} diff --git a/Glamourer/Designs/DesignBase.cs b/Glamourer/Designs/DesignBase.cs index 183ca99..1e45052 100644 --- a/Glamourer/Designs/DesignBase.cs +++ b/Glamourer/Designs/DesignBase.cs @@ -39,7 +39,6 @@ public class DesignBase ApplyEquip = equipFlags & EquipFlagExtensions.All; _designFlags = 0; CustomizationSet = SetCustomizationSet(customize); - } internal DesignBase(DesignBase clone) @@ -83,6 +82,7 @@ public class DesignBase => _applyCustomize; internal EquipFlag ApplyEquip = EquipFlagExtensions.All; + internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; public bool SetCustomize(CustomizationService customizationService, Customize customize) @@ -169,6 +169,9 @@ public class DesignBase public bool DoApplyCustomize(CustomizeIndex idx) => ApplyCustomize.HasFlag(idx.ToFlag()); + public bool DoApplyCrest(EquipSlot slot) + => ApplyCrest.HasFlag(slot.ToFlag()); + internal bool SetApplyEquip(EquipSlot slot, bool value) { var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); @@ -199,6 +202,16 @@ public class DesignBase return true; } + internal bool SetApplyCrest(EquipSlot slot, bool value) + { + var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); + if (newValue == ApplyCrest) + return false; + + ApplyCrest = newValue; + return true; + } + internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) => new(this, equipFlags, customizeFlags); @@ -246,13 +259,15 @@ public class DesignBase protected JObject SerializeEquipment() { - static JObject Serialize(CustomItemId id, StainId stain, bool apply, bool applyStain) + static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest) => new() { ["ItemId"] = id.Id, ["Stain"] = stain.Id, + ["Crest"] = crest, ["Apply"] = apply, ["ApplyStain"] = applyStain, + ["ApplyCrest"] = applyCrest, }; var ret = new JObject(); @@ -262,7 +277,8 @@ public class DesignBase { var item = _designData.Item(slot); var stain = _designData.Stain(slot); - ret[slot.ToString()] = Serialize(item.Id, stain, DoApplyEquip(slot), DoApplyStain(slot)); + var crest = _designData.Crest(slot); + ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); } ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); @@ -345,13 +361,15 @@ public class DesignBase return; } - static (CustomItemId, StainId, bool, bool) ParseItem(EquipSlot slot, JToken? item) + static (CustomItemId, StainId, bool, bool, bool, bool) ParseItem(EquipSlot slot, JToken? item) { var id = item?["ItemId"]?.ToObject() ?? ItemManager.NothingId(slot).Id; var stain = (StainId)(item?["Stain"]?.ToObject() ?? 0); + var crest = (item?["Crest"]?.ToObject() ?? false); var apply = item?["Apply"]?.ToObject() ?? false; var applyStain = item?["ApplyStain"]?.ToObject() ?? false; - return (id, stain, apply, applyStain); + var applyCrest = item?["ApplyCrest"]?.ToObject() ?? false; + return (id, stain, crest, apply, applyStain, applyCrest); } void PrintWarning(string msg) @@ -362,21 +380,23 @@ public class DesignBase foreach (var slot in EquipSlotExtensions.EqdpSlots) { - var (id, stain, apply, applyStain) = ParseItem(slot, equip[slot.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(slot, equip[slot.ToString()]); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); design._designData.SetItem(slot, item); design._designData.SetStain(slot, stain); + design._designData.SetCrest(slot, crest); design.SetApplyEquip(slot, apply); design.SetApplyStain(slot, applyStain); + design.SetApplyCrest(slot, applyCrest); } { - var (id, stain, apply, applyStain) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); + var (id, stain, crest, apply, applyStain, applyCrest) = ParseItem(EquipSlot.MainHand, equip[EquipSlot.MainHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.MainHand)) id = items.DefaultSword.ItemId; - var (idOff, stainOff, applyOff, applyStainOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); + var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = ParseItem(EquipSlot.OffHand, equip[EquipSlot.OffHand.ToString()]); if (id == ItemManager.NothingId(EquipSlot.OffHand)) id = ItemManager.NothingId(FullEquipType.Shield); @@ -387,10 +407,14 @@ public class DesignBase design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.OffHand, stainOff); + design._designData.SetCrest(EquipSlot.MainHand, crest); + design._designData.SetCrest(EquipSlot.OffHand, crestOff); design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.OffHand, applyStainOff); + design.SetApplyCrest(EquipSlot.MainHand, applyCrest); + design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); } var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); design.SetApplyHatVisible(metaValue.Enabled); diff --git a/Glamourer/Designs/DesignData.cs b/Glamourer/Designs/DesignData.cs index 4a24f59..fe2e655 100644 --- a/Glamourer/Designs/DesignData.cs +++ b/Glamourer/Designs/DesignData.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using Glamourer.Customization; using Glamourer.Services; +using Glamourer.Structs; using OtterGui.Classes; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -29,6 +30,7 @@ public unsafe struct DesignData private fixed byte _equipmentBytes[48]; public Customize Customize = Customize.Default; public uint ModelId; + public CrestFlag CrestVisibility; private WeaponType _secondaryMainhand; private WeaponType _secondaryOffhand; private FullEquipType _typeMainhand; @@ -59,6 +61,10 @@ public unsafe struct DesignData return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; } + public readonly bool Crest(EquipSlot slot) + => CrestVisibility.HasFlag(slot.ToCrestFlag()); + + public FullEquipType MainhandType => _typeMainhand; @@ -173,6 +179,16 @@ public unsafe struct DesignData _ => false, }; + public bool SetCrest(EquipSlot slot, bool visible) + { + var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); + if (newValue == CrestVisibility) + return false; + + CrestVisibility = newValue; + return true; + } + public readonly bool IsWet() => (_states & 0x01) == 0x01; @@ -228,12 +244,15 @@ public unsafe struct DesignData { SetItem(slot, ItemManager.NothingItem(slot)); SetStain(slot, 0); + SetCrest(slot, false); } SetItem(EquipSlot.MainHand, items.DefaultSword); SetStain(EquipSlot.MainHand, 0); + SetCrest(EquipSlot.MainHand, false); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetStain(EquipSlot.OffHand, 0); + SetCrest(EquipSlot.OffHand, false); }