Revamp, temp state.

This commit is contained in:
Ottermandias 2023-12-02 16:33:01 +01:00
parent 358e33346f
commit cc09cced61
22 changed files with 365 additions and 298 deletions

View file

@ -8,18 +8,18 @@ namespace Glamourer.Structs;
[Flags] [Flags]
public enum CrestFlag : ushort public enum CrestFlag : ushort
{ {
Head = 0x0001, OffHand = 0x0001,
Body = 0x0002, Head = 0x0002,
Hands = 0x0004, Body = 0x0004,
Legs = 0x0008, Hands = 0x0008,
Feet = 0x0010, Legs = 0x0010,
Ears = 0x0020, Feet = 0x0020,
Neck = 0x0040, Ears = 0x0040,
Wrists = 0x0080, Neck = 0x0080,
RFinger = 0x0100, Wrists = 0x0100,
LFinger = 0x0200, RFinger = 0x0200,
MainHand = 0x0400, LFinger = 0x0400,
OffHand = 0x0800, MainHand = 0x0800,
} }
public enum CrestType : byte public enum CrestType : byte
@ -32,7 +32,7 @@ public enum CrestType : byte
public static class CrestExtensions public static class CrestExtensions
{ {
public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Offhand << 1) - 1); public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1);
public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand; public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand;
public static readonly IReadOnlyList<CrestFlag> AllRelevantSet = Enum.GetValues<CrestFlag>().Where(f => AllRelevant.HasFlag(f)).ToArray(); public static readonly IReadOnlyList<CrestFlag> AllRelevantSet = Enum.GetValues<CrestFlag>().Where(f => AllRelevant.HasFlag(f)).ToArray();
@ -82,24 +82,6 @@ public static class CrestExtensions
_ => 0, _ => 0,
}; };
public static EquipSlot ToSlot(this CrestFlag flag)
=> flag switch
{
CrestFlag.MainHand => EquipSlot.MainHand,
CrestFlag.OffHand => EquipSlot.OffHand,
CrestFlag.Head => EquipSlot.Head,
CrestFlag.Body => EquipSlot.Body,
CrestFlag.Hands => EquipSlot.Hands,
CrestFlag.Legs => EquipSlot.Legs,
CrestFlag.Feet => EquipSlot.Feet,
CrestFlag.Ears => EquipSlot.Ears,
CrestFlag.Neck => EquipSlot.Neck,
CrestFlag.Wrists => EquipSlot.Wrists,
CrestFlag.RFinger => EquipSlot.RFinger,
CrestFlag.LFinger => EquipSlot.LFinger,
_ => 0,
};
public static string ToLabel(this CrestFlag flag) public static string ToLabel(this CrestFlag flag)
=> flag switch => flag switch
{ {

View file

@ -15,13 +15,13 @@ public class AutoDesign
[Flags] [Flags]
public enum Type : byte public enum Type : byte
{ {
Armor = 0x01, Armor = 0x01,
Customizations = 0x02, Customizations = 0x02,
Weapons = 0x04, Weapons = 0x04,
Stains = 0x08, GearCustomization = 0x08,
Accessories = 0x10, Accessories = 0x10,
All = Armor | Accessories | Customizations | Weapons | Stains, All = Armor | Accessories | Customizations | Weapons | GearCustomization,
} }
public Design? Design; public Design? Design;
@ -80,19 +80,20 @@ public class AutoDesign
return ret; return ret;
} }
public (EquipFlag Equip, CustomizeFlag Customize, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat()
{ {
var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0) var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0)
| (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0)
| (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0)
| (ApplicationType.HasFlag(Type.Stains) ? StainFlags : 0); | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0);
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0;
var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0;
if (Revert) if (Revert)
return (equipFlags, customizeFlags, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor),
ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations));
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest,
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(),
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(),
ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(),

View file

@ -268,6 +268,7 @@ public class AutoDesignApplier : IDisposable
{ {
EquipFlag totalEquipFlags = 0; EquipFlag totalEquipFlags = 0;
CustomizeFlag totalCustomizeFlags = 0; CustomizeFlag totalCustomizeFlags = 0;
CrestFlag totalCrestFlags = 0;
byte totalMetaFlags = 0; byte totalMetaFlags = 0;
if (set.BaseState == AutoDesignSet.Base.Game) if (set.BaseState == AutoDesignSet.Base.Game)
_state.ResetStateFixed(state); _state.ResetStateFixed(state);
@ -291,10 +292,11 @@ public class AutoDesignApplier : IDisposable
if (!data.IsHuman) if (!data.IsHuman)
continue; continue;
var (equipFlags, customizeFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source);
ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source);
ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange);
ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source);
} }
if (totalCustomizeFlags != 0) if (totalCustomizeFlags != 0)
@ -324,6 +326,24 @@ public class AutoDesignApplier : IDisposable
} }
} }
private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual,
StateChanged.Source source)
{
crestFlags &= ~totalCrestFlags;
if (crestFlags == 0)
return;
foreach (var slot in CrestExtensions.AllRelevantSet)
{
if (!crestFlags.HasFlag(slot))
continue;
if (!respectManual || state[slot] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, slot, design.Crest(slot), source);
totalCrestFlags |= slot;
}
}
private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual,
StateChanged.Source source, bool fromJobChange) StateChanged.Source source, bool fromJobChange)
{ {

View file

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization;
using Glamourer.Gui;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -28,8 +26,8 @@ public sealed class Design : DesignBase, ISavable
internal Design(Design other) internal Design(Design other)
: base(other) : base(other)
{ {
Tags = Tags.ToArray(); Tags = other.Tags.ToArray();
Description = Description; Description = other.Description;
AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods); AssociatedMods = new SortedList<Mod, ModSettings>(other.AssociatedMods);
} }
@ -69,8 +67,7 @@ public sealed class Design : DesignBase, ISavable
["Equipment"] = SerializeEquipment(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Mods"] = SerializeMods(), ["Mods"] = SerializeMods(),
} };
;
return ret; return ret;
} }

View file

@ -1,6 +1,4 @@
using System; using Dalamud.Interface.Internal.Notifications;
using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs; using Glamourer.Structs;
@ -9,6 +7,8 @@ using OtterGui.Classes;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using System;
using System.Linq;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -169,8 +169,8 @@ public class DesignBase
public bool DoApplyCustomize(CustomizeIndex idx) public bool DoApplyCustomize(CustomizeIndex idx)
=> ApplyCustomize.HasFlag(idx.ToFlag()); => ApplyCustomize.HasFlag(idx.ToFlag());
public bool DoApplyCrest(EquipSlot slot) public bool DoApplyCrest(CrestFlag slot)
=> ApplyCrest.HasFlag(slot.ToCrestFlag()); => ApplyCrest.HasFlag(slot);
internal bool SetApplyEquip(EquipSlot slot, bool value) internal bool SetApplyEquip(EquipSlot slot, bool value)
{ {
@ -202,9 +202,9 @@ public class DesignBase
return true; return true;
} }
internal bool SetApplyCrest(EquipSlot slot, bool value) internal bool SetApplyCrest(CrestFlag slot, bool value)
{ {
var newValue = value ? ApplyCrest | slot.ToCrestFlag() : ApplyCrest & ~slot.ToCrestFlag(); var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot;
if (newValue == ApplyCrest) if (newValue == ApplyCrest)
return false; return false;
@ -212,28 +212,32 @@ public class DesignBase
return true; return true;
} }
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags) internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
=> new(this, equipFlags, customizeFlags); => new(this, equipFlags, customizeFlags, crestFlags);
internal readonly struct FlagRestrictionResetter : IDisposable internal readonly struct FlagRestrictionResetter : IDisposable
{ {
private readonly DesignBase _design; private readonly DesignBase _design;
private readonly EquipFlag _oldEquipFlags; private readonly EquipFlag _oldEquipFlags;
private readonly CustomizeFlag _oldCustomizeFlags; private readonly CustomizeFlag _oldCustomizeFlags;
private readonly CrestFlag _oldCrestFlags;
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags) public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
_design = d; _design = d;
_oldEquipFlags = d.ApplyEquip; _oldEquipFlags = d.ApplyEquip;
_oldCustomizeFlags = d.ApplyCustomizeRaw; _oldCustomizeFlags = d.ApplyCustomizeRaw;
_oldCrestFlags = d.ApplyCrest;
d.ApplyEquip &= equipFlags; d.ApplyEquip &= equipFlags;
d.ApplyCustomize &= customizeFlags; d.ApplyCustomize &= customizeFlags;
d.ApplyCrest &= crestFlags;
} }
public void Dispose() public void Dispose()
{ {
_design.ApplyEquip = _oldEquipFlags; _design.ApplyEquip = _oldEquipFlags;
_design.ApplyCustomize = _oldCustomizeFlags; _design.ApplyCustomize = _oldCustomizeFlags;
_design.ApplyCrest = _oldCrestFlags;
} }
} }
@ -275,10 +279,11 @@ public class DesignBase
{ {
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
{ {
var item = _designData.Item(slot); var item = _designData.Item(slot);
var stain = _designData.Stain(slot); var stain = _designData.Stain(slot);
var crest = _designData.Crest(slot); var crestSlot = slot.ToCrestFlag();
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(slot)); var crest = _designData.Crest(crestSlot);
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
} }
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply"); ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
@ -365,7 +370,7 @@ public class DesignBase
{ {
var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id; var id = item?["ItemId"]?.ToObject<ulong>() ?? ItemManager.NothingId(slot).Id;
var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0); var stain = (StainId)(item?["Stain"]?.ToObject<byte>() ?? 0);
var crest = (item?["Crest"]?.ToObject<bool>() ?? false); var crest = item?["Crest"]?.ToObject<bool>() ?? false;
var apply = item?["Apply"]?.ToObject<bool>() ?? false; var apply = item?["Apply"]?.ToObject<bool>() ?? false;
var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false; var applyStain = item?["ApplyStain"]?.ToObject<bool>() ?? false;
var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false; var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false;
@ -384,19 +389,21 @@ public class DesignBase
PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown)); PrintWarning(items.ValidateItem(slot, id, out var item, allowUnknown));
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
var crestSlot = slot.ToCrestFlag();
design._designData.SetItem(slot, item); design._designData.SetItem(slot, item);
design._designData.SetStain(slot, stain); design._designData.SetStain(slot, stain);
design._designData.SetCrest(slot, crest); design._designData.SetCrest(crestSlot, crest);
design.SetApplyEquip(slot, apply); design.SetApplyEquip(slot, apply);
design.SetApplyStain(slot, applyStain); design.SetApplyStain(slot, applyStain);
design.SetApplyCrest(slot, applyCrest); design.SetApplyCrest(crestSlot, applyCrest);
} }
{ {
var (id, stain, crest, apply, applyStain, applyCrest) = 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)) if (id == ItemManager.NothingId(EquipSlot.MainHand))
id = items.DefaultSword.ItemId; id = items.DefaultSword.ItemId;
var (idOff, stainOff, crestOff, applyOff, applyStainOff, applyCrestOff) = 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)) if (id == ItemManager.NothingId(EquipSlot.OffHand))
id = ItemManager.NothingId(FullEquipType.Shield); id = ItemManager.NothingId(FullEquipType.Shield);
@ -407,14 +414,14 @@ public class DesignBase
design._designData.SetItem(EquipSlot.OffHand, off); design._designData.SetItem(EquipSlot.OffHand, off);
design._designData.SetStain(EquipSlot.MainHand, stain); design._designData.SetStain(EquipSlot.MainHand, stain);
design._designData.SetStain(EquipSlot.OffHand, stainOff); design._designData.SetStain(EquipSlot.OffHand, stainOff);
design._designData.SetCrest(EquipSlot.MainHand, crest); design._designData.SetCrest(CrestFlag.MainHand, crest);
design._designData.SetCrest(EquipSlot.OffHand, crestOff); design._designData.SetCrest(CrestFlag.OffHand, crestOff);
design.SetApplyEquip(EquipSlot.MainHand, apply); design.SetApplyEquip(EquipSlot.MainHand, apply);
design.SetApplyEquip(EquipSlot.OffHand, applyOff); design.SetApplyEquip(EquipSlot.OffHand, applyOff);
design.SetApplyStain(EquipSlot.MainHand, applyStain); design.SetApplyStain(EquipSlot.MainHand, applyStain);
design.SetApplyStain(EquipSlot.OffHand, applyStainOff); design.SetApplyStain(EquipSlot.OffHand, applyStainOff);
design.SetApplyCrest(EquipSlot.MainHand, applyCrest); design.SetApplyCrest(CrestFlag.MainHand, applyCrest);
design.SetApplyCrest(EquipSlot.OffHand, applyCrestOff); design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff);
} }
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse); var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
design.SetApplyHatVisible(metaValue.Enabled); design.SetApplyHatVisible(metaValue.Enabled);

View file

@ -13,22 +13,9 @@ using Penumbra.GameData.Enums;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public class DesignConverter public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans)
{ {
public const byte Version = 5; public const byte Version = 6;
private readonly ItemManager _items;
private readonly DesignManager _designs;
private readonly CustomizationService _customize;
private readonly HumanModelList _humans;
public DesignConverter(ItemManager items, DesignManager designs, CustomizationService customize, HumanModelList humans)
{
_items = items;
_designs = designs;
_customize = customize;
_humans = humans;
}
public JObject ShareJObject(DesignBase design) public JObject ShareJObject(DesignBase design)
=> design.JsonSerialize(); => design.JsonSerialize();
@ -36,32 +23,33 @@ public class DesignConverter
public JObject ShareJObject(Design design) public JObject ShareJObject(Design design)
=> design.JsonSerialize(); => design.JsonSerialize();
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
var design = Convert(state, equipFlags, customizeFlags); var design = Convert(state, equipFlags, customizeFlags, crestFlags);
return ShareJObject(design); return ShareJObject(design);
} }
public string ShareBase64(Design design) public string ShareBase64(Design design)
=> ShareBackwardCompatible(ShareJObject(design), design); => ShareBase64(ShareJObject(design));
public string ShareBase64(DesignBase design) public string ShareBase64(DesignBase design)
=> ShareBackwardCompatible(ShareJObject(design), design); => ShareBase64(ShareJObject(design));
public string ShareBase64(ActorState state) public string ShareBase64(ActorState state)
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All); => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All);
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
var design = Convert(state, equipFlags, customizeFlags); var design = Convert(state, equipFlags, customizeFlags, crestFlags);
return ShareBackwardCompatible(ShareJObject(design), design); return ShareBase64(ShareJObject(design));
} }
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags) public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
var design = _designs.CreateTemporary(); var design = _designs.CreateTemporary();
design.ApplyEquip = equipFlags & EquipFlagExtensions.All; design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
design.ApplyCustomize = customizeFlags; design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
design.ApplyCrest = crestFlags & CrestExtensions.All;
design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head));
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
@ -123,6 +111,16 @@ public class DesignConverter
: DesignBase.LoadDesignBase(_customize, _items, jObj2); : DesignBase.LoadDesignBase(_customize, _items, jObj2);
break; break;
} }
case Version:
{
version = bytes.DecompressToString(out var decompressed);
var jObj2 = JObject.Parse(decompressed);
Debug.Assert(version == Version);
ret = jObj2["Identifier"] != null
? Design.LoadDesign(_customize, _items, jObj2)
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
break;
}
default: throw new Exception($"Unknown Version {bytes[0]}."); default: throw new Exception($"Unknown Version {bytes[0]}.");
} }
} }
@ -138,6 +136,7 @@ public class DesignConverter
if (!equip) if (!equip)
{ {
ret.ApplyEquip = 0; ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.SetApplyHatVisible(false); ret.SetApplyHatVisible(false);
ret.SetApplyWeaponVisible(false); ret.SetApplyWeaponVisible(false);
ret.SetApplyVisorToggle(false); ret.SetApplyVisorToggle(false);
@ -146,23 +145,10 @@ public class DesignConverter
return ret; return ret;
} }
private static string ShareBase64(JObject jObj) private static string ShareBase64(JObject jObject)
{ {
var json = jObj.ToString(Formatting.None); var json = jObject.ToString(Formatting.None);
var compressed = json.Compress(Version); var compressed = json.Compress(Version);
return System.Convert.ToBase64String(compressed); return System.Convert.ToBase64String(compressed);
} }
private static string ShareBackwardCompatible(JObject jObject, DesignBase design)
{
var oldBase64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw,
design.DoApplyHatVisible(), design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected(), 1f);
var oldBytes = System.Convert.FromBase64String(oldBase64);
var json = jObject.ToString(Formatting.None);
var compressed = json.Compress(Version);
var bytes = new byte[oldBytes.Length + compressed.Length];
oldBytes.CopyTo(bytes, 0);
compressed.CopyTo(bytes, oldBytes.Length);
return System.Convert.ToBase64String(bytes);
}
} }

View file

@ -61,8 +61,8 @@ public unsafe struct DesignData
return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3];
} }
public readonly bool Crest(EquipSlot slot) public readonly bool Crest(CrestFlag slot)
=> CrestVisibility.HasFlag(slot.ToCrestFlag()); => CrestVisibility.HasFlag(slot);
public FullEquipType MainhandType public FullEquipType MainhandType
@ -179,9 +179,9 @@ public unsafe struct DesignData
_ => false, _ => false,
}; };
public bool SetCrest(EquipSlot slot, bool visible) public bool SetCrest(CrestFlag slot, bool visible)
{ {
var newValue = visible ? CrestVisibility | slot.ToCrestFlag() : CrestVisibility & ~slot.ToCrestFlag(); var newValue = visible ? CrestVisibility | slot : CrestVisibility & ~slot;
if (newValue == CrestVisibility) if (newValue == CrestVisibility)
return false; return false;
@ -244,15 +244,15 @@ public unsafe struct DesignData
{ {
SetItem(slot, ItemManager.NothingItem(slot)); SetItem(slot, ItemManager.NothingItem(slot));
SetStain(slot, 0); SetStain(slot, 0);
SetCrest(slot, false); SetCrest(slot.ToCrestFlag(), false);
} }
SetItem(EquipSlot.MainHand, items.DefaultSword); SetItem(EquipSlot.MainHand, items.DefaultSword);
SetStain(EquipSlot.MainHand, 0); SetStain(EquipSlot.MainHand, 0);
SetCrest(EquipSlot.MainHand, false); SetCrest(CrestFlag.MainHand, false);
SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield)); SetItem(EquipSlot.OffHand, ItemManager.NothingItem(FullEquipType.Shield));
SetStain(EquipSlot.OffHand, 0); SetStain(EquipSlot.OffHand, 0);
SetCrest(EquipSlot.OffHand, false); SetCrest(CrestFlag.OffHand, false);
} }

View file

@ -8,6 +8,7 @@ using Glamourer.Events;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
@ -447,7 +448,7 @@ public class DesignManager
} }
/// <summary> Change the crest visibility for any equipment piece. </summary> /// <summary> Change the crest visibility for any equipment piece. </summary>
public void ChangeCrest(Design design, EquipSlot slot, bool crest) public void ChangeCrest(Design design, CrestFlag slot, bool crest)
{ {
var oldCrest = design.DesignData.Crest(slot); var oldCrest = design.DesignData.Crest(slot);
if (!design.GetDesignDataRef().SetCrest(slot, crest)) if (!design.GetDesignDataRef().SetCrest(slot, crest))
@ -460,7 +461,7 @@ public class DesignManager
} }
/// <summary> Change whether to apply a specific crest visibility. </summary> /// <summary> Change whether to apply a specific crest visibility. </summary>
public void ChangeApplyCrest(Design design, EquipSlot slot, bool value) public void ChangeApplyCrest(Design design, CrestFlag slot, bool value)
{ {
if (!design.SetApplyCrest(slot, value)) if (!design.SetApplyCrest(slot, value))
return; return;
@ -539,7 +540,10 @@ public class DesignManager
if (other.DoApplyStain(slot)) if (other.DoApplyStain(slot))
ChangeStain(design, slot, other.DesignData.Stain(slot)); ChangeStain(design, slot, other.DesignData.Stain(slot));
}
foreach (var slot in Enum.GetValues<CrestFlag>())
{
if (other.DoApplyCrest(slot)) if (other.DoApplyCrest(slot))
ChangeCrest(design, slot, other.DesignData.Crest(slot)); ChangeCrest(design, slot, other.DesignData.Crest(slot));
} }
@ -556,12 +560,6 @@ public class DesignManager
if (other.DoApplyStain(EquipSlot.OffHand)) if (other.DoApplyStain(EquipSlot.OffHand))
ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand)); ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand));
if (other.DoApplyCrest(EquipSlot.MainHand))
ChangeCrest(design, EquipSlot.MainHand, other.DesignData.Crest(EquipSlot.MainHand));
if (other.DoApplyCrest(EquipSlot.OffHand))
ChangeCrest(design, EquipSlot.OffHand, other.DesignData.Crest(EquipSlot.OffHand));
} }
public void UndoDesignChange(Design design) public void UndoDesignChange(Design design)

View file

@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable
return; return;
} }
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize); using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
} }

View file

@ -5,7 +5,6 @@ using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
@ -16,6 +15,7 @@ using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -167,21 +167,21 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Head, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.Body, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(EquipSlot.OffHand, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
} }
} }
@ -296,8 +296,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
{ {
ImGui.OpenPopup("Save as Design"); ImGui.OpenPopup("Save as Design");
_newName = _state!.Identifier.ToName(); _newName = _state!.Identifier.ToName();
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
_newDesign = _converter.Convert(_state, applyGear, applyCustomize); _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest);
} }
private void SaveDesignDrawPopup() private void SaveDesignDrawPopup()
@ -332,8 +332,8 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
{ {
try try
{ {
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
var text = _converter.ShareBase64(_state!, applyGear, applyCustomize); var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest);
ImGui.SetClipboardText(text); ImGui.SetClipboardText(text);
} }
catch (Exception ex) catch (Exception ex)
@ -372,9 +372,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
return; return;
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state,
StateChanged.Source.Manual); StateChanged.Source.Manual);
} }
@ -390,9 +390,9 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
return; return;
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize), state, _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state,
StateChanged.Source.Manual); StateChanged.Source.Manual);
} }
} }

View file

@ -286,7 +286,7 @@ public class SetPanel
var size = new Vector2(ImGui.GetFrameHeight()); var size = new Vector2(ImGui.GetFrameHeight());
size.X += ImGuiHelpers.GlobalScale; size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _, _) = design.ApplyWhat(); var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat();
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{ {
@ -457,7 +457,7 @@ public class SetPanel
"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."), "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.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.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
(AutoDesign.Type.Stains, "Apply all dye changes that are enabled in this 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."), (AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
}; };

View file

@ -113,21 +113,21 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Head, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.Body, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(EquipSlot.OffHand, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!));
} }
} }
@ -191,10 +191,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant);
foreach (var flag in CrestExtensions.AllRelevantSet) foreach (var flag in CrestExtensions.AllRelevantSet)
{ {
var slot = flag.ToSlot(); var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(flag);
var apply = bigChange ? ((CrestFlag)flags & flag) == flag : _selector.Selected!.DoApplyCrest(slot);
if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange) if (ImGui.Checkbox($"Apply {flag.ToLabel()} Crest", ref apply) || bigChange)
_manager.ChangeApplyCrest(_selector.Selected!, slot, apply); _manager.ChangeApplyCrest(_selector.Selected!, flag, apply);
} }
} }
@ -389,8 +388,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
} }
} }
@ -408,8 +407,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
} }
} }

View file

@ -52,10 +52,10 @@ public ref struct ToggleDrawData
}; };
} }
public static ToggleDrawData CrestFromDesign(EquipSlot slot, DesignManager manager, Design design) public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design)
=> new() => new()
{ {
Label = $"{slot.ToCrestFlag().ToLabel()} Crest", Label = $"{slot.ToLabel()} Crest",
Tooltip = string.Empty, Tooltip = string.Empty,
Locked = design.WriteProtected(), Locked = design.WriteProtected(),
DisplayApplication = true, DisplayApplication = true,
@ -65,14 +65,14 @@ public ref struct ToggleDrawData
SetApply = v => manager.ChangeApplyCrest(design, slot, v), SetApply = v => manager.ChangeApplyCrest(design, slot, v),
}; };
public static ToggleDrawData CrestFromState(EquipSlot slot, StateManager manager, ActorState state) public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state)
=> new() => new()
{ {
Label = $"{slot.ToCrestFlag().ToLabel()} Crest", Label = $"{slot.ToLabel()} Crest",
Tooltip = "Hide or show your free company crest on this piece of gear.", Tooltip = "Hide or show your free company crest on this piece of gear.",
Locked = state.IsLocked, Locked = state.IsLocked,
CurrentValue = state.ModelData.Crest(slot), // TODO CurrentValue = state.ModelData.Crest(slot),
SetValue = v => { }, //manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), 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(ActorState.MetaIndex index, StateManager manager, ActorState state)

View file

@ -88,13 +88,13 @@ public static class UiHelpers
return (currentValue != newValue, currentApply != newApply); return (currentValue != newValue, currentApply != newApply);
} }
public static (EquipFlag, CustomizeFlag) ConvertKeysToFlags() public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{ {
(false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All),
(true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant), (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All),
(true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0), (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All),
(false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant), (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0),
}; };
public static (bool, bool) ConvertKeysToBool() public static (bool, bool) ConvertKeysToBool()

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
@ -19,11 +20,11 @@ namespace Glamourer.Interop;
/// <item>Parameter is the whether the crest will be shown. </item> /// <item>Parameter is the whether the crest will be shown. </item>
/// </list> /// </list>
/// </summary> /// </summary>
public sealed unsafe class CrestService : EventWrapper<Action<Model, EquipSlot, Ref<bool>>, CrestService.Priority>, IDisposable public sealed unsafe class CrestService : EventWrapper<Action<Actor, CrestFlag, Ref<bool>>, CrestService.Priority>
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="State.StateListener.OnCrestVisibilityUpdating"/> /// <seealso cref="State.StateListener.OnCrestChange"/>
StateListener = 0, StateListener = 0,
} }
@ -37,19 +38,51 @@ public sealed unsafe class CrestService : EventWrapper<Action<Model, EquipSlot,
interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour); interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour);
_humanSetFreeCompanyCrestVisibleOnSlot.Enable(); _humanSetFreeCompanyCrestVisibleOnSlot.Enable();
_weaponSetFreeCompanyCrestVisibleOnSlot.Enable(); _weaponSetFreeCompanyCrestVisibleOnSlot.Enable();
_crestChangeHook.Enable();
} }
public void UpdateCrests(Actor gameObject, CrestFlag flags)
{
if (!gameObject.IsCharacter)
return;
flags &= CrestExtensions.AllRelevant;
var currentCrests = gameObject.CrestBitfield;
using var update = _inUpdate.EnterMethod();
_crestChangeHook.Original(gameObject.AsCharacter, (byte) flags);
gameObject.CrestBitfield = currentCrests;
}
public delegate void DrawObjectCrestUpdateDelegate(Model drawObject, CrestFlag slot, ref bool value);
public event DrawObjectCrestUpdateDelegate? ModelCrestSetup;
protected override void Dispose(bool _) protected override void Dispose(bool _)
{ {
_humanSetFreeCompanyCrestVisibleOnSlot.Dispose(); _humanSetFreeCompanyCrestVisibleOnSlot.Dispose();
_weaponSetFreeCompanyCrestVisibleOnSlot.Dispose(); _weaponSetFreeCompanyCrestVisibleOnSlot.Dispose();
_crestChangeHook.Dispose();
} }
public void Invoke(Model model, EquipSlot slot, ref bool visible) private delegate void CrestChangeDelegate(Character* character, byte crestFlags);
[Signature("E8 ?? ?? ?? ?? 48 8B 55 ?? 49 8B CE E8", DetourName = nameof(CrestChangeDetour))]
private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!;
private void CrestChangeDetour(Character* character, byte crestFlags)
{ {
var ret = new Ref<bool>(visible); var actor = (Actor)character;
Invoke(this, model, slot, ret); foreach (var slot in CrestExtensions.AllRelevantSet)
visible = ret; {
var newValue = new Ref<bool>(((CrestFlag)crestFlags).HasFlag(slot));
Invoke(this, actor, slot, newValue);
crestFlags = (byte)(newValue.Value ? crestFlags | (byte)slot : crestFlags & (byte)~slot);
}
Glamourer.Log.Information(
$"Called CrestChange on {(ulong)character:X} with {crestFlags:X} and prior flags {((Actor)character).CrestBitfield}.");
using var _ = _inUpdate.EnterMethod();
_crestChangeHook.Original(character, crestFlags);
} }
public static bool GetModelCrest(Actor gameObject, CrestFlag slot) public static bool GetModelCrest(Actor gameObject, CrestFlag slot)
@ -83,42 +116,9 @@ public sealed unsafe class CrestService : EventWrapper<Action<Model, EquipSlot,
return false; return false;
} }
public void UpdateCrest(Actor gameObject, CrestFlag slot, bool crest)
{
if (!gameObject.IsCharacter)
return;
var (type, index) = slot.ToIndex();
switch (type)
{
case CrestType.Human:
{
var model = gameObject.Model;
if (!model.IsHuman)
return;
using var _ = _inUpdate.EnterMethod();
var setter = (delegate* unmanaged<Human*, byte, byte, void>)((nint*)model.AsCharacterBase->VTable)[96];
setter(model.AsHuman, index, crest ? (byte)1 : (byte)0);
break;
}
case CrestType.Offhand:
{
var model = (Model)gameObject.AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).DrawObject;
if (!model.IsWeapon)
return;
using var _ = _inUpdate.EnterMethod();
var setter = (delegate* unmanaged<Weapon*, byte, byte, void>)((nint*)model.AsCharacterBase->VTable)[96];
setter(model.AsWeapon, index, crest ? (byte)1 : (byte)0);
break;
}
}
}
private readonly InMethodChecker _inUpdate = new(); private readonly InMethodChecker _inUpdate = new();
private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible); private delegate void SetCrestDelegateIntern(DrawObject* drawObject, byte slot, byte visible);
[Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)] [Signature(global::Penumbra.GameData.Sigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!; private readonly nint* _humanVTable = null!;
@ -129,26 +129,27 @@ public sealed unsafe class CrestService : EventWrapper<Action<Model, EquipSlot,
private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot; private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot;
private readonly Hook<SetCrestDelegateIntern> _weaponSetFreeCompanyCrestVisibleOnSlot; private readonly Hook<SetCrestDelegateIntern> _weaponSetFreeCompanyCrestVisibleOnSlot;
private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible)
{ {
var slot = ((uint)slotIdx).ToEquipSlot();
var rVisible = visible != 0; var rVisible = visible != 0;
var inUpdate = _inUpdate.InMethod; var inUpdate = _inUpdate.InMethod;
var slot = (CrestFlag)((ushort)CrestFlag.Head << slotIdx);
if (!inUpdate) if (!inUpdate)
Invoke(drawObject, slot, ref rVisible); ModelCrestSetup?.Invoke(drawObject, slot, ref rVisible);
Glamourer.Log.Excessive( Glamourer.Log.Excessive(
$"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); $"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
_humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); _humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
} }
private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible) private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(DrawObject* drawObject, byte slotIdx, byte visible)
{ {
var rVisible = visible != 0; var rVisible = visible != 0;
var inUpdate = _inUpdate.InMethod; var inUpdate = _inUpdate.InMethod;
if (!inUpdate) if (!inUpdate && slotIdx == 0)
Invoke(drawObject, EquipSlot.BothHand, ref rVisible); ModelCrestSetup?.Invoke(drawObject, CrestFlag.OffHand, ref rVisible);
Glamourer.Log.Excessive( Glamourer.Log.Excessive(
$"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate})."); $"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{(ulong)drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
_weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0); _weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
} }
} }

View file

@ -108,7 +108,7 @@ public readonly unsafe struct Actor : IEquatable<Actor>
=> ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()]; => ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()];
public bool GetCrest(CrestFlag slot) public bool GetCrest(CrestFlag slot)
=> (GetFreeCompanyCrestBitfield() & CrestMask(slot)) != 0; => CrestBitfield.HasFlag(slot);
public CharacterWeapon GetMainhand() public CharacterWeapon GetMainhand()
=> new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value); => new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.MainHand).ModelId.Value);
@ -120,20 +120,8 @@ public readonly unsafe struct Actor : IEquatable<Actor>
=> *(Customize*)&AsCharacter->DrawData.CustomizeData; => *(Customize*)&AsCharacter->DrawData.CustomizeData;
// TODO remove this when available in ClientStructs // TODO remove this when available in ClientStructs
private byte GetFreeCompanyCrestBitfield() internal ref CrestFlag CrestBitfield
=> ((byte*)Address)[0x1BBB]; => ref *(CrestFlag*)((byte*)Address + 0x1BBB);
private static byte CrestMask(CrestFlag slot)
=> slot switch
{
CrestFlag.OffHand => 0x01,
CrestFlag.Head => 0x02,
CrestFlag.Body => 0x04,
CrestFlag.Hands => 0x08,
CrestFlag.Legs => 0x10,
CrestFlag.Feet => 0x20,
_ => 0x00,
};
public override string ToString() public override string ToString()
=> $"0x{Address:X}"; => $"0x{Address:X}";

View file

@ -5,6 +5,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Structs;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -13,6 +14,7 @@ namespace Glamourer.Interop;
public unsafe class WeaponService : IDisposable public unsafe class WeaponService : IDisposable
{ {
private readonly WeaponLoading _event; private readonly WeaponLoading _event;
private readonly CrestService _crestService;
private readonly ThreadLocal<bool> _inUpdate = new(() => false); private readonly ThreadLocal<bool> _inUpdate = new(() => false);
@ -20,9 +22,10 @@ public unsafe class WeaponService : IDisposable
_original; _original;
public WeaponService(WeaponLoading @event, IGameInteropProvider interop) public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService)
{ {
_event = @event; _event = @event;
_crestService = crestService;
_loadWeaponHook = _loadWeaponHook =
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
_original = _original =
@ -69,6 +72,7 @@ public unsafe class WeaponService : IDisposable
_event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon); _event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon);
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4); _loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
if (tmpWeapon.Value != weapon.Value) if (tmpWeapon.Value != weapon.Value)
{ {
if (tmpWeapon.Set.Id == 0) if (tmpWeapon.Set.Id == 0)

View file

@ -165,7 +165,7 @@ public class CommandService : IDisposable
.AddInitialPurple("Customizations, ") .AddInitialPurple("Customizations, ")
.AddInitialPurple("Equipment, ") .AddInitialPurple("Equipment, ")
.AddInitialPurple("Accessories, ") .AddInitialPurple("Accessories, ")
.AddInitialPurple("Dyes and ") .AddInitialPurple("Dyes & Crests and ")
.AddInitialPurple("Weapons, where ").AddPurple("CEADW") .AddInitialPurple("Weapons, where ").AddPurple("CEADW")
.AddText(" means everything should be toggled on, and no value means nothing should be toggled on.") .AddText(" means everything should be toggled on, and no value means nothing should be toggled on.")
.BuiltString); .BuiltString);
@ -268,7 +268,7 @@ public class CommandService : IDisposable
applicationFlags |= AutoDesign.Type.Accessories; applicationFlags |= AutoDesign.Type.Accessories;
break; break;
case 'd': case 'd':
applicationFlags |= AutoDesign.Type.Stains; applicationFlags |= AutoDesign.Type.GearCustomization;
break; break;
case 'w': case 'w':
applicationFlags |= AutoDesign.Type.Weapons; applicationFlags |= AutoDesign.Type.Weapons;
@ -472,7 +472,7 @@ public class CommandService : IDisposable
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) && _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
continue; continue;
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant); var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All);
_designManager.CreateClone(design, split[0], true); _designManager.CreateClone(design, split[0], true);
return true; return true;
} }

View file

@ -1,13 +1,12 @@
using System.Linq; using System.Linq;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -17,30 +16,9 @@ namespace Glamourer.State;
/// This class applies changes made to state to actual objects in the game. /// This class applies changes made to state to actual objects in the game.
/// It handles applying those changes as well as redrawing the actor if necessary. /// It handles applying those changes as well as redrawing the actor if necessary.
/// </summary> /// </summary>
public class StateApplier public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize,
ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests)
{ {
private readonly PenumbraService _penumbra;
private readonly UpdateSlotService _updateSlot;
private readonly VisorService _visor;
private readonly WeaponService _weapon;
private readonly MetaService _metaService;
private readonly ChangeCustomizeService _changeCustomize;
private readonly ItemManager _items;
private readonly ObjectManager _objects;
public StateApplier(UpdateSlotService updateSlot, VisorService visor, WeaponService weapon, ChangeCustomizeService changeCustomize,
ItemManager items, PenumbraService penumbra, MetaService metaService, ObjectManager objects)
{
_updateSlot = updateSlot;
_visor = visor;
_weapon = weapon;
_changeCustomize = changeCustomize;
_items = items;
_penumbra = penumbra;
_metaService = metaService;
_objects = objects;
}
/// <summary> Simply force a redraw regardless of conditions. </summary> /// <summary> Simply force a redraw regardless of conditions. </summary>
public void ForceRedraw(ActorData data) public void ForceRedraw(ActorData data)
{ {
@ -279,6 +257,22 @@ public class StateApplier
return data; return data;
} }
/// <summary> Change the crest state on actors. </summary>
public void ChangeCrests(ActorData data, CrestFlag flags)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_crests.UpdateCrests(actor, flags);
}
/// <inheritdoc cref="ChangeCrests(ActorData, CrestFlag)"/>
public ActorData ChangeCrests(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeCrests(data, state.ModelData.CrestVisibility);
return data;
}
private ActorData GetData(ActorState state) private ActorData GetData(ActorState state)
{ {
_objects.Update(); _objects.Update();

View file

@ -4,6 +4,7 @@ using Dalamud.Plugin.Services;
using Glamourer.Customization; using Glamourer.Customization;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -197,6 +198,18 @@ public class StateEditor
return true; return true;
} }
/// <summary> Change the crest of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0)
{
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetCrest(slot, crest);
state[slot] = source;
return true;
}
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
uint key = 0) uint key = 0)
{ {

View file

@ -13,6 +13,7 @@ using Penumbra.GameData.Structs;
using System; using System;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.Structs;
namespace Glamourer.State; namespace Glamourer.State;
@ -42,6 +43,7 @@ public class StateListener : IDisposable
private readonly MovedEquipment _movedEquipment; private readonly MovedEquipment _movedEquipment;
private readonly GPoseService _gPose; private readonly GPoseService _gPose;
private readonly ChangeCustomizeService _changeCustomizeService; private readonly ChangeCustomizeService _changeCustomizeService;
private readonly CrestService _crestService;
private readonly ICondition _condition; private readonly ICondition _condition;
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
@ -52,7 +54,7 @@ public class StateListener : IDisposable
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition) ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition, CrestService crestService)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
@ -74,6 +76,7 @@ public class StateListener : IDisposable
_changeCustomizeService = changeCustomizeService; _changeCustomizeService = changeCustomizeService;
_customizations = customizations; _customizations = customizations;
_condition = condition; _condition = condition;
_crestService = crestService;
Subscribe(); Subscribe();
} }
@ -405,6 +408,58 @@ public class StateListener : IDisposable
} }
} }
private void OnCrestChange(Actor actor, CrestFlag slot, Ref<bool> value)
{
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|| !_manager.TryGetValue(identifier, out var state))
return;
switch (UpdateBaseCrest(actor, state, slot, value.Value))
{
case UpdateState.Change:
if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
else
value.Value = state.ModelData.Crest(slot);
break;
case UpdateState.NoChange:
case UpdateState.HatHack:
value.Value = state.ModelData.Crest(slot);
break;
case UpdateState.Transformed: break;
}
}
private void OnModelCrestSetup(Model model, CrestFlag slot, ref bool value)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|| !_manager.TryGetValue(identifier, out var state))
return;
value = state.ModelData.Crest(slot);
}
private static UpdateState UpdateBaseCrest(Actor actor, ActorState state, CrestFlag slot, bool visible)
{
if (actor.IsTransformed)
return UpdateState.Transformed;
if (state.BaseData.Crest(slot) != visible)
{
state.BaseData.SetCrest(slot, visible);
return UpdateState.Change;
}
return UpdateState.NoChange;
}
/// <summary> Update base data for a single changed weapon slot. </summary> /// <summary> Update base data for a single changed weapon slot. </summary>
private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon)
{ {
@ -616,6 +671,8 @@ public class StateListener : IDisposable
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener); _headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener); _weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
_crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener);
_crestService.ModelCrestSetup += OnModelCrestSetup;
} }
private void Unsubscribe() private void Unsubscribe()
@ -629,6 +686,8 @@ public class StateListener : IDisposable
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange); _headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange); _weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
_changeCustomizeService.Unsubscribe(OnCustomizeChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange);
_crestService.Unsubscribe(OnCrestChange);
_crestService.ModelCrestSetup -= OnModelCrestSetup;
} }
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
@ -639,8 +698,9 @@ public class StateListener : IDisposable
if (_creatingState == null) if (_creatingState == null)
return; return;
_applier.ChangeHatState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsHatVisible()); var data = new ActorData(gameObject, _creatingIdentifier.ToName());
_applier.ChangeWeaponState(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWeaponVisible()); _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible());
_applier.ChangeWetness(new ActorData(gameObject, _creatingIdentifier.ToName()), _creatingState.ModelData.IsWet()); _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible());
_applier.ChangeWetness(data, _creatingState.ModelData.IsWet());
} }
} }

View file

@ -10,6 +10,7 @@ using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -17,32 +18,12 @@ using Penumbra.GameData.Structs;
namespace Glamourer.State; namespace Glamourer.State;
public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState> public class StateManager(ActorService _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor,
HumanModelList _humans, ICondition _condition, IClientState _clientState)
: IReadOnlyDictionary<ActorIdentifier, ActorState>
{ {
private readonly ActorService _actors;
private readonly ItemManager _items;
private readonly HumanModelList _humans;
private readonly StateChanged _event;
private readonly StateApplier _applier;
private readonly StateEditor _editor;
private readonly ICondition _condition;
private readonly IClientState _clientState;
private readonly Dictionary<ActorIdentifier, ActorState> _states = new(); private readonly Dictionary<ActorIdentifier, ActorState> _states = new();
public StateManager(ActorService actors, ItemManager items, StateChanged @event, StateApplier applier, StateEditor editor,
HumanModelList humans, ICondition condition, IClientState clientState)
{
_actors = actors;
_items = items;
_event = @event;
_applier = applier;
_editor = editor;
_humans = humans;
_condition = condition;
_clientState = clientState;
}
public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator() public IEnumerator<KeyValuePair<ActorIdentifier, ActorState>> GetEnumerator()
=> _states.GetEnumerator(); => _states.GetEnumerator();
@ -83,7 +64,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
// and the draw objects data for the model data (where possible). // and the draw objects data for the model data (where possible).
state = new ActorState(identifier) state = new ActorState(identifier)
{ {
ModelData = FromActor(actor, true, false), ModelData = FromActor(actor, true, false),
BaseData = FromActor(actor, false, false), BaseData = FromActor(actor, false, false),
LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0), LastJob = (byte)(actor.IsCharacter ? actor.AsCharacter->CharacterData.ClassJob : 0),
LastTerritory = _clientState.TerritoryType, LastTerritory = _clientState.TerritoryType,
@ -162,6 +143,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
// Visor state is a flag on the game object, but we can see the actual state on the draw object. // Visor state is a flag on the game object, but we can see the actual state on the draw object.
ret.SetVisor(VisorService.GetVisorState(model)); ret.SetVisor(VisorService.GetVisorState(model));
foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot));
} }
else else
{ {
@ -180,6 +164,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
off = actor.GetOffhand(); off = actor.GetOffhand();
FistWeaponHack(ref ret, ref main, ref off); FistWeaponHack(ref ret, ref main, ref off);
ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled); ret.SetVisor(actor.AsCharacter->DrawData.IsVisorToggled);
foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, actor.GetCrest(slot));
} }
// Set the weapons regardless of source. // Set the weapons regardless of source.
@ -206,7 +193,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
if (mainhand.Set.Id is < 1601 or >= 1651) if (mainhand.Set.Id is < 1601 or >= 1651)
return; return;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant) offhand.Type.Id); var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Set, (Variant)offhand.Type.Id);
offhand.Set = (SetId)(mainhand.Set.Id + 50); offhand.Set = (SetId)(mainhand.Set.Id + 50);
offhand.Variant = mainhand.Variant; offhand.Variant = mainhand.Variant;
offhand.Type = mainhand.Type; offhand.Type = mainhand.Type;
@ -304,6 +291,18 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
} }
/// <summary> Change the crest of an equipment piece. </summary>
public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
return;
var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot));
}
/// <summary> Change hat visibility. </summary> /// <summary> Change hat visibility. </summary>
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
@ -356,19 +355,8 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0) public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0)
{ {
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(),
{ source,
var unused = (applyPiece, applyStain) switch
{
(false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
out _, key),
};
}
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(), source,
out var oldModelId, key)) out var oldModelId, key))
return; return;
@ -393,12 +381,28 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot)); HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
} }
var actors = ApplyAll(state, redraw, false); var actors = ApplyAll(state, redraw, false);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); $"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[ActorState.MetaIndex.Wetness], state, actors, design);
return;
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
{
var unused = (applyPiece, applyStain) switch
{
(false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
out _, key),
};
}
} }
private ActorData ApplyAll(ActorState state, bool redraw, bool withLock) private ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
@ -430,6 +434,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_applier.ChangeHatState(actors, state.ModelData.IsHatVisible()); _applier.ChangeHatState(actors, state.ModelData.IsHatVisible());
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
} }
return actors; return actors;
@ -453,10 +458,13 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
state[slot, true] = StateChanged.Source.Game; state[slot, true] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game; state[slot, false] = StateChanged.Source.Game;
} }
foreach (var type in Enum.GetValues<ActorState.MetaIndex>()) foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
state[type] = StateChanged.Source.Game; state[type] = StateChanged.Source.Game;
foreach (var slot in CrestExtensions.AllRelevantSet)
state[slot] = StateChanged.Source.Game;
var actors = ActorData.Invalid; var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
actors = ApplyAll(state, redraw, true); actors = ApplyAll(state, redraw, true);
@ -491,6 +499,15 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
} }
} }
foreach (var slot in CrestExtensions.AllRelevantSet)
{
if (state[slot] is StateChanged.Source.Fixed)
{
state[slot] = StateChanged.Source.Game;
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
}
}
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game;