FC crest visibility draft

This commit is contained in:
Exter-N 2023-11-23 20:44:50 +01:00
parent 0a7d800706
commit 63f7818481
28 changed files with 980 additions and 223 deletions

View file

@ -4,7 +4,7 @@ using Penumbra.GameData.Enums;
namespace Glamourer.Structs; namespace Glamourer.Structs;
[Flags] [Flags]
public enum EquipFlag : uint public enum EquipFlag : ulong
{ {
Head = 0x00000001, Head = 0x00000001,
Body = 0x00000002, Body = 0x00000002,
@ -30,12 +30,34 @@ public enum EquipFlag : uint
LFingerStain = 0x00200000, LFingerStain = 0x00200000,
MainhandStain = 0x00400000, MainhandStain = 0x00400000,
OffhandStain = 0x00800000, OffhandStain = 0x00800000,
HeadCrest = 0x01000000,
BodyCrest = 0x02000000,
HandsCrest = 0x04000000,
LegsCrest = 0x08000000,
FeetCrest = 0x10000000,
EarsCrest = 0x20000000,
NeckCrest = 0x40000000,
WristCrest = 0x80000000,
RFingerCrest = 0x100000000,
LFingerCrest = 0x200000000,
MainhandCrest = 0x400000000,
OffhandCrest = 0x800000000,
} }
public static class EquipFlagExtensions public static class EquipFlagExtensions
{ {
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1); public const EquipFlag All = (EquipFlag)(((ulong)EquipFlag.OffhandCrest << 1) - 1);
public const int NumEquipFlags = 24; public const EquipFlag AllRelevant = All
& ~EquipFlag.HandsCrest
& ~EquipFlag.LegsCrest
& ~EquipFlag.FeetCrest
& ~EquipFlag.EarsCrest
& ~EquipFlag.NeckCrest
& ~EquipFlag.WristCrest
& ~EquipFlag.RFingerCrest
& ~EquipFlag.LFingerCrest
& ~EquipFlag.MainhandCrest;
public const int NumEquipFlags = 36;
public static EquipFlag ToFlag(this EquipSlot slot) public static EquipFlag ToFlag(this EquipSlot slot)
=> slot switch => slot switch
@ -73,21 +95,39 @@ public static class EquipFlagExtensions
_ => 0, _ => 0,
}; };
public static EquipFlag ToCrestFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.MainhandCrest,
EquipSlot.OffHand => EquipFlag.OffhandCrest,
EquipSlot.Head => EquipFlag.HeadCrest,
EquipSlot.Body => EquipFlag.BodyCrest,
EquipSlot.Hands => EquipFlag.HandsCrest,
EquipSlot.Legs => EquipFlag.LegsCrest,
EquipSlot.Feet => EquipFlag.FeetCrest,
EquipSlot.Ears => EquipFlag.EarsCrest,
EquipSlot.Neck => EquipFlag.NeckCrest,
EquipSlot.Wrists => EquipFlag.WristCrest,
EquipSlot.RFinger => EquipFlag.RFingerCrest,
EquipSlot.LFinger => EquipFlag.LFingerCrest,
_ => 0,
};
public static EquipFlag ToBothFlags(this EquipSlot slot) public static EquipFlag ToBothFlags(this EquipSlot slot)
=> slot switch => slot switch
{ {
EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain, EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain | EquipFlag.MainhandCrest,
EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain, EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain | EquipFlag.OffhandCrest,
EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain, EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain | EquipFlag.HeadCrest,
EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain, EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain | EquipFlag.BodyCrest,
EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain, EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain | EquipFlag.HandsCrest,
EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain, EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain | EquipFlag.LegsCrest,
EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain, EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain | EquipFlag.FeetCrest,
EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain, EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain | EquipFlag.EarsCrest,
EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain, EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain | EquipFlag.NeckCrest,
EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain, EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain | EquipFlag.WristCrest,
EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain, EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain | EquipFlag.RFingerCrest,
EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain, EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain | EquipFlag.LFingerCrest,
_ => 0, _ => 0,
}; };
} }

View file

@ -20,8 +20,9 @@ public class AutoDesign
Weapons = 0x04, Weapons = 0x04,
Stains = 0x08, Stains = 0x08,
Accessories = 0x10, Accessories = 0x10,
Crests = 0x20,
All = Armor | Accessories | Customizations | Weapons | Stains, All = Armor | Accessories | Customizations | Weapons | Stains | Crests,
} }
public Design? Design; public Design? Design;
@ -69,7 +70,8 @@ public class AutoDesign
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.Stains) ? StainFlags : 0)
| (ApplicationType.HasFlag(Type.Crests) ? CrestFlags : 0);
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0; var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0;
if (Revert) if (Revert)
@ -99,4 +101,21 @@ public class AutoDesign
| EquipFlag.WristStain | EquipFlag.WristStain
| EquipFlag.RFingerStain | EquipFlag.RFingerStain
| EquipFlag.LFingerStain; | EquipFlag.LFingerStain;
public const EquipFlag CrestFlags = EquipFlag.MainhandCrest
| EquipFlag.OffhandCrest
| EquipFlag.HeadCrest
| EquipFlag.BodyCrest
| EquipFlag.HandsCrest
| EquipFlag.LegsCrest
| EquipFlag.FeetCrest
| EquipFlag.EarsCrest
| EquipFlag.NeckCrest
| EquipFlag.WristCrest
| EquipFlag.RFingerCrest
| EquipFlag.LFingerCrest;
public const EquipFlag RelevantCrestFlags = EquipFlag.OffhandCrest
| EquipFlag.HeadCrest
| EquipFlag.BodyCrest;
} }

View file

@ -333,7 +333,7 @@ public class AutoDesignApplier : IDisposable
var item = design.Item(slot); var item = design.Item(slot);
if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _))
{ {
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual) if (!respectManual || state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, item, source); _state.ChangeItem(state, slot, item, source);
totalEquipFlags |= flag; totalEquipFlags |= flag;
} }
@ -342,17 +342,25 @@ public class AutoDesignApplier : IDisposable
var stainFlag = slot.ToStainFlag(); var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag)) if (equipFlags.HasFlag(stainFlag))
{ {
if (!respectManual || state[slot, true] is not StateChanged.Source.Manual) if (!respectManual || state[slot, ActorState.EquipField.Stain] is not StateChanged.Source.Manual)
_state.ChangeStain(state, slot, design.Stain(slot), source); _state.ChangeStain(state, slot, design.Stain(slot), source);
totalEquipFlags |= stainFlag; totalEquipFlags |= stainFlag;
} }
var crestFlag = slot.ToCrestFlag();
if (equipFlags.HasFlag(crestFlag))
{
if (!respectManual || state[slot, ActorState.EquipField.Crest] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, slot, design.Crest(slot), source);
totalEquipFlags |= crestFlag;
}
} }
if (equipFlags.HasFlag(EquipFlag.Mainhand)) if (equipFlags.HasFlag(EquipFlag.Mainhand))
{ {
var item = design.Item(EquipSlot.MainHand); var item = design.Item(EquipSlot.MainHand);
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); 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[EquipSlot.MainHand, ActorState.EquipField.Item] is not StateChanged.Source.Manual;
if (checkUnlock && checkState) if (checkUnlock && checkState)
{ {
if (fromJobChange) if (fromJobChange)
@ -372,7 +380,7 @@ public class AutoDesignApplier : IDisposable
{ {
var item = design.Item(EquipSlot.OffHand); var item = design.Item(EquipSlot.OffHand);
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); 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[EquipSlot.OffHand, ActorState.EquipField.Item] is not StateChanged.Source.Manual;
if (checkUnlock && checkState) if (checkUnlock && checkState)
{ {
if (fromJobChange) if (fromJobChange)
@ -390,17 +398,31 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(EquipFlag.MainhandStain)) if (equipFlags.HasFlag(EquipFlag.MainhandStain))
{ {
if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) if (!respectManual || state[EquipSlot.MainHand, ActorState.EquipField.Stain] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source);
totalEquipFlags |= EquipFlag.MainhandStain; totalEquipFlags |= EquipFlag.MainhandStain;
} }
if (equipFlags.HasFlag(EquipFlag.MainhandCrest))
{
if (!respectManual || state[EquipSlot.MainHand, ActorState.EquipField.Crest] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, EquipSlot.MainHand, design.Crest(EquipSlot.MainHand), source);
totalEquipFlags |= EquipFlag.MainhandCrest;
}
if (equipFlags.HasFlag(EquipFlag.OffhandStain)) if (equipFlags.HasFlag(EquipFlag.OffhandStain))
{ {
if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) if (!respectManual || state[EquipSlot.OffHand, ActorState.EquipField.Stain] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source);
totalEquipFlags |= EquipFlag.OffhandStain; totalEquipFlags |= EquipFlag.OffhandStain;
} }
if (equipFlags.HasFlag(EquipFlag.OffhandCrest))
{
if (!respectManual || state[EquipSlot.OffHand, ActorState.EquipField.Crest] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, EquipSlot.OffHand, design.Crest(EquipSlot.OffHand), source);
totalEquipFlags |= EquipFlag.OffhandCrest;
}
} }
private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags, private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags,

View file

@ -82,7 +82,7 @@ public class DesignBase
internal CustomizeFlag ApplyCustomizeRaw internal CustomizeFlag ApplyCustomizeRaw
=> _applyCustomize; => _applyCustomize;
internal EquipFlag ApplyEquip = EquipFlagExtensions.All; internal EquipFlag ApplyEquip = EquipFlagExtensions.AllRelevant;
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
public bool SetCustomize(CustomizationService customizationService, Customize customize) public bool SetCustomize(CustomizationService customizationService, Customize customize)
@ -166,6 +166,9 @@ public class DesignBase
public bool DoApplyStain(EquipSlot slot) public bool DoApplyStain(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToStainFlag()); => ApplyEquip.HasFlag(slot.ToStainFlag());
public bool DoApplyCrest(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToCrestFlag());
public bool DoApplyCustomize(CustomizeIndex idx) public bool DoApplyCustomize(CustomizeIndex idx)
=> ApplyCustomize.HasFlag(idx.ToFlag()); => ApplyCustomize.HasFlag(idx.ToFlag());
@ -189,6 +192,16 @@ public class DesignBase
return true; return true;
} }
internal bool SetApplyCrest(EquipSlot slot, bool value)
{
var newValue = value ? ApplyEquip | slot.ToCrestFlag() : ApplyEquip & ~slot.ToCrestFlag();
if (newValue == ApplyEquip)
return false;
ApplyEquip = newValue;
return true;
}
internal bool SetApplyCustomize(CustomizeIndex idx, bool value) internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
{ {
var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag(); var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag();
@ -246,13 +259,15 @@ public class DesignBase
protected JObject SerializeEquipment() 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() => new()
{ {
["ItemId"] = id.Id, ["ItemId"] = id.Id,
["Stain"] = stain.Id, ["Stain"] = stain.Id,
["Crest"] = crest,
["Apply"] = apply, ["Apply"] = apply,
["ApplyStain"] = applyStain, ["ApplyStain"] = applyStain,
["ApplyCrest"] = applyCrest,
}; };
var ret = new JObject(); var ret = new JObject();
@ -262,7 +277,8 @@ public class DesignBase
{ {
var item = _designData.Item(slot); var item = _designData.Item(slot);
var stain = _designData.Stain(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"); ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
@ -345,13 +361,15 @@ public class DesignBase
return; 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<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 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;
return (id, stain, apply, applyStain); var applyCrest = item?["ApplyCrest"]?.ToObject<bool>() ?? false;
return (id, stain, crest, apply, applyStain, applyCrest);
} }
void PrintWarning(string msg) void PrintWarning(string msg)
@ -362,21 +380,23 @@ public class DesignBase
foreach (var slot in EquipSlotExtensions.EqdpSlots) 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.ValidateItem(slot, id, out var item, allowUnknown));
PrintWarning(items.ValidateStain(stain, out stain, allowUnknown)); PrintWarning(items.ValidateStain(stain, out stain, allowUnknown));
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.SetApplyEquip(slot, apply); design.SetApplyEquip(slot, apply);
design.SetApplyStain(slot, applyStain); 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)) if (id == ItemManager.NothingId(EquipSlot.MainHand))
id = items.DefaultSword.ItemId; 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)) if (id == ItemManager.NothingId(EquipSlot.OffHand))
id = ItemManager.NothingId(FullEquipType.Shield); id = ItemManager.NothingId(FullEquipType.Shield);
@ -387,10 +407,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(EquipSlot.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(EquipSlot.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

@ -34,6 +34,7 @@ public unsafe struct DesignData
private FullEquipType _typeMainhand; private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand; private FullEquipType _typeOffhand;
private byte _states; private byte _states;
public ushort CrestVisibility;
public bool IsHuman = true; public bool IsHuman = true;
public DesignData() public DesignData()
@ -59,6 +60,15 @@ public unsafe struct DesignData
return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3]; return index > 11 ? (StainId)0 : _equipmentBytes[4 * index + 3];
} }
private static ushort CrestMask(EquipSlot slot)
{
var index = slot.ToIndex();
return index <= 11 ? (ushort)(1u << (int)index) : (ushort)0;
}
public readonly bool Crest(EquipSlot slot)
=> (CrestVisibility & CrestMask(slot)) != 0;
public FullEquipType MainhandType public FullEquipType MainhandType
=> _typeMainhand; => _typeMainhand;
@ -173,6 +183,16 @@ public unsafe struct DesignData
_ => false, _ => false,
}; };
public bool SetCrest(EquipSlot slot, bool visible)
{
var crestVisibility = CrestVisibility;
if (visible)
crestVisibility |= CrestMask(slot);
else
crestVisibility &= (ushort)~CrestMask(slot);
return SetIfDifferent(ref CrestVisibility, crestVisibility);
}
public readonly bool IsWet() public readonly bool IsWet()
=> (_states & 0x01) == 0x01; => (_states & 0x01) == 0x01;
@ -228,12 +248,15 @@ public unsafe struct DesignData
{ {
SetItem(slot, ItemManager.NothingItem(slot)); SetItem(slot, ItemManager.NothingItem(slot));
SetStain(slot, 0); SetStain(slot, 0);
SetCrest(slot, false);
} }
SetItem(EquipSlot.MainHand, items.DefaultSword); SetItem(EquipSlot.MainHand, items.DefaultSword);
SetStain(EquipSlot.MainHand, 0); SetStain(EquipSlot.MainHand, 0);
SetCrest(EquipSlot.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);
} }

View file

@ -446,6 +446,31 @@ public class DesignManager
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot); _event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
} }
/// <summary> Change the crest visibility for any equipment piece. </summary>
public void ChangeCrest(Design design, EquipSlot slot, bool crest)
{
var oldCrest = design.DesignData.Crest(slot);
if (!design.GetDesignDataRef().SetCrest(slot, crest))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
_event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
}
/// <summary> Change whether to apply a specific crest visibility. </summary>
public void ChangeApplyCrest(Design design, EquipSlot slot, bool value)
{
if (!design.SetApplyCrest(slot, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
_event.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
}
/// <summary> Change the bool value of one of the meta flags. </summary> /// <summary> Change the bool value of one of the meta flags. </summary>
public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
{ {
@ -514,6 +539,9 @@ 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));
if (other.DoApplyCrest(slot))
ChangeCrest(design, slot, other.DesignData.Crest(slot));
} }
} }
@ -528,6 +556,12 @@ 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

@ -0,0 +1,34 @@
using System;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
namespace Glamourer.Events;
/// <summary>
/// Triggered when the crest visibility is updated on a model.
/// <list type="number">
/// <item>Parameter is the model with an update. </item>
/// <item>Parameter is the equipment slot changed. </item>
/// <item>Parameter is the whether the crest will be shown. </item>
/// </list>
/// </summary>
public sealed class CrestVisibilityUpdating : EventWrapper<Action<Model, EquipSlot, Ref<bool>>, CrestVisibilityUpdating.Priority>
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnCrestVisibilityUpdating"/>
StateListener = 0,
}
public CrestVisibilityUpdating()
: base(nameof(CrestVisibilityUpdating))
{ }
public void Invoke(Model model, EquipSlot slot, ref bool visible)
{
var @return = new Ref<bool>(visible);
Invoke(this, model, slot, @return);
visible = @return;
}
}

View file

@ -62,6 +62,9 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary> /// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Stain, Stain,
/// <summary> An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
Crest,
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary> /// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
ApplyCustomize, ApplyCustomize,
@ -71,6 +74,9 @@ public sealed class DesignChanged : EventWrapper<Action<DesignChanged.Type, Desi
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary> /// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
ApplyStain, ApplyStain,
/// <summary> An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. </summary>
ApplyCrest,
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary> /// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
WriteProtection, WriteProtection,

View file

@ -37,6 +37,9 @@ public sealed class StateChanged : EventWrapper<Action<StateChanged.Type, StateC
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary> /// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Stain, Stain,
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
Crest,
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary> /// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
Design, Design,

View file

@ -13,6 +13,7 @@ using Glamourer.Structs;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -86,13 +87,13 @@ public class EquipmentDrawer
} }
public DataChange DrawEquip(EquipSlot slot, in DesignData designData, out EquipItem rArmor, out StainId rStain, EquipFlag? cApply, public DataChange DrawEquip(EquipSlot slot, in DesignData designData, out EquipItem rArmor, out StainId rStain, out bool rCrest, EquipFlag? cApply,
out bool rApply, out bool rApplyStain, bool locked) out bool rApply, out bool rApplyStain, out bool rApplyCrest, bool locked)
=> DrawEquip(slot, designData.Item(slot), out rArmor, designData.Stain(slot), out rStain, cApply, out rApply, out rApplyStain, locked, => DrawEquip(slot, designData.Item(slot), out rArmor, designData.Stain(slot), out rStain, designData.Crest(slot), out rCrest, cApply, out rApply, out rApplyStain, out rApplyCrest, locked,
designData.Customize.Gender, designData.Customize.Race); designData.Customize.Gender, designData.Customize.Race);
public DataChange DrawEquip(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, EquipFlag? cApply, public DataChange DrawEquip(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, bool cCrest, out bool rCrest, EquipFlag? cApply,
out bool rApply, out bool rApplyStain, bool locked, Gender gender = Gender.Unknown, Race race = Race.Unknown) out bool rApply, out bool rApplyStain, out bool rApplyCrest, bool locked, Gender gender = Gender.Unknown, Race race = Race.Unknown)
{ {
if (_config.HideApplyCheckmarks) if (_config.HideApplyCheckmarks)
cApply = null; cApply = null;
@ -102,24 +103,26 @@ public class EquipmentDrawer
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
if (_config.SmallEquip) if (_config.SmallEquip)
return DrawEquipSmall(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); return DrawEquipSmall(slot, cArmor, out rArmor, cStain, out rStain, cCrest, out rCrest, cApply, out rApply, out rApplyStain, out rApplyCrest, locked, gender, race);
if (!locked && _codes.EnabledArtisan) if (!locked && _codes.EnabledArtisan)
return DrawEquipArtisan(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain); return DrawEquipArtisan(slot, cArmor, out rArmor, cStain, out rStain, cCrest, out rCrest, cApply, out rApply, out rApplyStain, out rApplyCrest);
return DrawEquipNormal(slot, cArmor, out rArmor, cStain, out rStain, cApply, out rApply, out rApplyStain, locked, gender, race); return DrawEquipNormal(slot, cArmor, out rArmor, cStain, out rStain, cCrest, out rCrest, cApply, out rApply, out rApplyStain, out rApplyCrest, locked, gender, race);
} }
public DataChange DrawWeapons(in DesignData designData, out EquipItem rMainhand, out EquipItem rOffhand, out StainId rMainhandStain, public DataChange DrawWeapons(in DesignData designData, out EquipItem rMainhand, out EquipItem rOffhand, out StainId rMainhandStain,
out StainId rOffhandStain, EquipFlag? cApply, bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, out StainId rOffhandStain, out bool rMainhandCrest, out bool rOffhandCrest, EquipFlag? cApply, bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain,
out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked) out bool rApplyMainhandCrest, out bool rApplyOffhand, out bool rApplyOffhandStain, out bool rApplyOffhandCrest, bool locked)
=> DrawWeapons(designData.Item(EquipSlot.MainHand), out rMainhand, designData.Item(EquipSlot.OffHand), out rOffhand, => DrawWeapons(designData.Item(EquipSlot.MainHand), out rMainhand, designData.Item(EquipSlot.OffHand), out rOffhand,
designData.Stain(EquipSlot.MainHand), out rMainhandStain, designData.Stain(EquipSlot.OffHand), out rOffhandStain, cApply, designData.Stain(EquipSlot.MainHand), out rMainhandStain, designData.Stain(EquipSlot.OffHand), out rOffhandStain,
allWeapons, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked); designData.Crest(EquipSlot.MainHand), out rMainhandCrest, designData.Crest(EquipSlot.OffHand), out rOffhandCrest, cApply,
allWeapons, out rApplyMainhand, out rApplyMainhandStain, out rApplyMainhandCrest, out rApplyOffhand, out rApplyOffhandStain, out rApplyOffhandCrest, locked);
private DataChange DrawWeapons(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, private DataChange DrawWeapons(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand,
StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain,
bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool cMainhandCrest, out bool rMainhandCrest, bool cOffhandCrest, out bool rOffhandCrest, EquipFlag? cApply,
bool allWeapons, out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyMainhandCrest, out bool rApplyOffhand, out bool rApplyOffhandStain, out bool rApplyOffhandCrest,
bool locked) bool locked)
{ {
if (cMainhand.ModelId.Id == 0) if (cMainhand.ModelId.Id == 0)
@ -128,10 +131,14 @@ public class EquipmentDrawer
rMainhand = cMainhand; rMainhand = cMainhand;
rMainhandStain = cMainhandStain; rMainhandStain = cMainhandStain;
rOffhandStain = cOffhandStain; rOffhandStain = cOffhandStain;
rMainhandCrest = cMainhandCrest;
rOffhandCrest = cOffhandCrest;
rApplyMainhand = false; rApplyMainhand = false;
rApplyMainhandStain = false; rApplyMainhandStain = false;
rApplyMainhandCrest = false;
rApplyOffhand = false; rApplyOffhand = false;
rApplyOffhandStain = false; rApplyOffhandStain = false;
rApplyOffhandCrest = false;
return DataChange.None; return DataChange.None;
} }
@ -144,15 +151,18 @@ public class EquipmentDrawer
if (_config.SmallEquip) if (_config.SmallEquip)
return DrawWeaponsSmall(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, return DrawWeaponsSmall(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain,
out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, out rOffhandStain, cMainhandCrest, out rMainhandCrest, cOffhandCrest, out rOffhandCrest,
cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyMainhandCrest, out rApplyOffhand, out rApplyOffhandStain, out rApplyOffhandCrest, locked,
allWeapons); allWeapons);
if (!locked && _codes.EnabledArtisan) if (!locked && _codes.EnabledArtisan)
return DrawWeaponsArtisan(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, return DrawWeaponsArtisan(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain,
out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain); out rOffhandStain, cMainhandCrest, out rMainhandCrest, cOffhandCrest, out rOffhandCrest,
cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyMainhandCrest, out rApplyOffhand, out rApplyOffhandStain, out rApplyOffhandCrest);
return DrawWeaponsNormal(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain, return DrawWeaponsNormal(cMainhand, out rMainhand, cOffhand, out rOffhand, cMainhandStain, out rMainhandStain, cOffhandStain,
out rOffhandStain, cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyOffhand, out rApplyOffhandStain, locked, out rOffhandStain, cMainhandCrest, out rMainhandCrest, cOffhandCrest, out rOffhandCrest,
cApply, out rApplyMainhand, out rApplyMainhandStain, out rApplyMainhandCrest, out rApplyOffhand, out rApplyOffhandStain, out rApplyOffhandCrest, locked,
allWeapons); allWeapons);
} }
@ -331,6 +341,49 @@ public class EquipmentDrawer
return change; return change;
} }
internal static bool CanHaveCrest(EquipSlot slot)
=> slot is EquipSlot.OffHand or EquipSlot.Head or EquipSlot.Body;
private DataChange DrawCrest(EquipSlot slot, bool current, EquipFlag? cApply, out bool ret, out bool rApply, bool locked, bool small, bool change2)
{
var changes = DataChange.None;
if (cApply.HasValue)
{
var apply = cApply.Value.HasFlag(slot.ToCrestFlag());
var flags = (sbyte)(apply ? current ? 1 : -1 : 0);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
using (var disabled = ImRaii.Disabled(locked))
{
if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw($"##crest{slot}", flags, out flags))
{
(ret, rApply) = flags switch
{
-1 => (false, true),
0 => (current, false),
_ => (true, true),
};
}
else
{
ret = current;
rApply = apply;
}
}
ImGuiUtil.HoverTooltip($"Display your Free Company crest on this equipment.\n\nThis attribute will be {(apply ? current ? "enabled." : "disabled." : "kept as is.")}");
if (ret != current)
changes |= change2 ? DataChange.Crest2 : DataChange.Crest;
if (rApply != apply)
changes |= change2 ? DataChange.ApplyCrest2 : DataChange.ApplyCrest;
}
else
{
rApply = false;
if (UiHelpers.DrawCheckbox($"##crest{slot}", "Display your Free Company crest on this equipment.", current, out ret, locked))
changes |= change2 ? DataChange.Crest2 : DataChange.Crest;
}
return changes;
}
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary> /// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor) private bool DrawArmorArtisan(EquipSlot slot, EquipItem current, out EquipItem armor)
{ {
@ -383,8 +436,8 @@ public class EquipmentDrawer
return false; return false;
} }
private DataChange DrawEquipArtisan(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, private DataChange DrawEquipArtisan(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, bool cCrest, out bool rCrest,
EquipFlag? cApply, out bool rApply, out bool rApplyStain) EquipFlag? cApply, out bool rApply, out bool rApplyStain, out bool rApplyCrest)
{ {
var changes = DataChange.None; var changes = DataChange.None;
if (DrawStainArtisan(slot, cStain, out rStain)) if (DrawStainArtisan(slot, cStain, out rStain))
@ -392,6 +445,16 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
if (DrawArmorArtisan(slot, cArmor, out rArmor)) if (DrawArmorArtisan(slot, cArmor, out rArmor))
changes |= DataChange.Item; changes |= DataChange.Item;
if (CanHaveCrest(slot))
{
ImGui.SameLine();
changes |= DrawCrest(slot, cCrest, cApply, out rCrest, out rApplyCrest, false, true, false);
}
else
{
rCrest = cCrest;
rApplyCrest = false;
}
if (cApply.HasValue) if (cApply.HasValue)
{ {
ImGui.SameLine(); ImGui.SameLine();
@ -410,8 +473,8 @@ public class EquipmentDrawer
return changes; return changes;
} }
private DataChange DrawEquipSmall(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, private DataChange DrawEquipSmall(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, bool cCrest, out bool rCrest,
EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) EquipFlag? cApply, out bool rApply, out bool rApplyStain, out bool rApplyCrest, bool locked, Gender gender, Race race)
{ {
var changes = DataChange.None; var changes = DataChange.None;
if (DrawStain(slot, cStain, out rStain, locked, true)) if (DrawStain(slot, cStain, out rStain, locked, true))
@ -419,6 +482,16 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
if (DrawItem(slot, cArmor, out rArmor, out var label, locked, true, false, false)) if (DrawItem(slot, cArmor, out rArmor, out var label, locked, true, false, false))
changes |= DataChange.Item; changes |= DataChange.Item;
if (CanHaveCrest(slot))
{
ImGui.SameLine();
changes |= DrawCrest(slot, cCrest, cApply, out rCrest, out rApplyCrest, locked, true, false);
}
else
{
rCrest = cCrest;
rApplyCrest = false;
}
if (cApply.HasValue) if (cApply.HasValue)
{ {
ImGui.SameLine(); ImGui.SameLine();
@ -443,8 +516,8 @@ public class EquipmentDrawer
return changes; return changes;
} }
private DataChange DrawEquipNormal(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, private DataChange DrawEquipNormal(EquipSlot slot, EquipItem cArmor, out EquipItem rArmor, StainId cStain, out StainId rStain, bool cCrest, out bool rCrest,
EquipFlag? cApply, out bool rApply, out bool rApplyStain, bool locked, Gender gender, Race race) EquipFlag? cApply, out bool rApply, out bool rApplyStain, out bool rApplyCrest, bool locked, Gender gender, Race race)
{ {
var changes = DataChange.None; var changes = DataChange.None;
cArmor.DrawIcon(_textures, _iconSize, slot); cArmor.DrawIcon(_textures, _iconSize, slot);
@ -479,6 +552,16 @@ public class EquipmentDrawer
{ {
rApplyStain = true; rApplyStain = true;
} }
if (CanHaveCrest(slot))
{
ImGui.SameLine();
changes |= DrawCrest(slot, cCrest, cApply, out rCrest, out rApplyCrest, locked, false, false);
}
else
{
rCrest = cCrest;
rApplyCrest = true;
}
if (VerifyRestrictedGear(slot, rArmor, gender, race)) if (VerifyRestrictedGear(slot, rArmor, gender, race))
{ {
@ -506,8 +589,9 @@ public class EquipmentDrawer
} }
private DataChange DrawWeaponsSmall(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, private DataChange DrawWeaponsSmall(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand,
StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, bool cMainhandCrest, out bool rMainhandCrest, bool cOffhandCrest, out bool rOffhandCrest, EquipFlag? cApply,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyMainhandCrest, out bool rApplyOffhand, out bool rApplyOffhandStain, out bool rApplyOffhandCrest, bool locked,
bool allWeapons) bool allWeapons)
{ {
var changes = DataChange.None; var changes = DataChange.None;
@ -526,6 +610,16 @@ public class EquipmentDrawer
} }
} }
if (CanHaveCrest(EquipSlot.MainHand))
{
ImGui.SameLine();
changes |= DrawCrest(EquipSlot.MainHand, cMainhandCrest, cApply, out rMainhandCrest, out rApplyMainhandCrest, locked, false, false);
}
else
{
rMainhandCrest = cMainhandCrest;
rApplyMainhandCrest = true;
}
if (cApply.HasValue) if (cApply.HasValue)
{ {
ImGui.SameLine(); ImGui.SameLine();
@ -548,8 +642,10 @@ public class EquipmentDrawer
if (rOffhand.Type is FullEquipType.Unknown) if (rOffhand.Type is FullEquipType.Unknown)
{ {
rOffhandStain = cOffhandStain; rOffhandStain = cOffhandStain;
rOffhandCrest = cOffhandCrest;
rApplyOffhand = false; rApplyOffhand = false;
rApplyOffhandStain = false; rApplyOffhandStain = false;
rApplyOffhandCrest = false;
return changes; return changes;
} }
@ -559,6 +655,17 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, true, false, false)) if (DrawOffhand(rMainhand, rOffhand, out rOffhand, out var offhandLabel, locked, true, false, false))
changes |= DataChange.Item2; changes |= DataChange.Item2;
if (CanHaveCrest(EquipSlot.OffHand))
{
ImGui.SameLine();
changes |= DrawCrest(EquipSlot.OffHand, cOffhandCrest, cApply, out rOffhandCrest, out rApplyOffhandCrest, locked, false, true);
}
else
{
rOffhandCrest = cOffhandCrest;
rApplyOffhandCrest = true;
}
if (cApply.HasValue) if (cApply.HasValue)
{ {
ImGui.SameLine(); ImGui.SameLine();
@ -580,8 +687,9 @@ public class EquipmentDrawer
} }
private DataChange DrawWeaponsNormal(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, private DataChange DrawWeaponsNormal(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand,
StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain, bool locked, bool cMainhandCrest, out bool rMainhandCrest, bool cOffhandCrest, out bool rOffhandCrest, EquipFlag? cApply,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyMainhandCrest, out bool rApplyOffhand, out bool rApplyOffhandStain, out bool rApplyOffhandCrest, bool locked,
bool allWeapons) bool allWeapons)
{ {
var changes = DataChange.None; var changes = DataChange.None;
@ -630,13 +738,26 @@ public class EquipmentDrawer
{ {
rApplyMainhandStain = true; rApplyMainhandStain = true;
} }
if (CanHaveCrest(EquipSlot.MainHand))
{
ImGui.SameLine();
changes |= DrawCrest(EquipSlot.MainHand, cMainhandCrest, cApply, out rMainhandCrest, out rApplyMainhandCrest, locked, false, false);
}
else
{
rMainhandCrest = cMainhandCrest;
rApplyMainhandCrest = true;
}
} }
if (rOffhand.Type is FullEquipType.Unknown) if (rOffhand.Type is FullEquipType.Unknown)
{ {
rOffhandStain = cOffhandStain; rOffhandStain = cOffhandStain;
rOffhandCrest = cOffhandCrest;
rApplyOffhand = false; rApplyOffhand = false;
rApplyOffhandStain = false; rApplyOffhandStain = false;
rApplyOffhandCrest = false;
return changes; return changes;
} }
@ -673,19 +794,31 @@ public class EquipmentDrawer
{ {
rApplyOffhandStain = true; rApplyOffhandStain = true;
} }
if (CanHaveCrest(EquipSlot.OffHand))
{
ImGui.SameLine();
changes |= DrawCrest(EquipSlot.OffHand, cOffhandCrest, cApply, out rOffhandCrest, out rApplyOffhandCrest, locked, false, true);
}
else
{
rOffhandCrest = cOffhandCrest;
rApplyOffhandCrest = true;
}
} }
return changes; return changes;
} }
private DataChange DrawWeaponsArtisan(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand, private DataChange DrawWeaponsArtisan(EquipItem cMainhand, out EquipItem rMainhand, EquipItem cOffhand, out EquipItem rOffhand,
StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain, EquipFlag? cApply, StainId cMainhandStain, out StainId rMainhandStain, StainId cOffhandStain, out StainId rOffhandStain,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyOffhand, out bool rApplyOffhandStain) bool cMainhandCrest, out bool rMainhandCrest, bool cOffhandCrest, out bool rOffhandCrest, EquipFlag? cApply,
out bool rApplyMainhand, out bool rApplyMainhandStain, out bool rApplyMainhandCrest, out bool rApplyOffhand, out bool rApplyOffhandStain, out bool rApplyOffhandCrest)
{ {
rApplyMainhand = (cApply ?? 0).HasFlag(EquipFlag.Mainhand); rApplyMainhand = (cApply ?? 0).HasFlag(EquipFlag.Mainhand);
rApplyMainhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); rApplyMainhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain);
rApplyOffhand = (cApply ?? 0).HasFlag(EquipFlag.Offhand); rApplyOffhand = (cApply ?? 0).HasFlag(EquipFlag.Offhand);
rApplyOffhandStain = (cApply ?? 0).HasFlag(EquipFlag.MainhandStain); rApplyOffhandStain = (cApply ?? 0).HasFlag(EquipFlag.OffhandStain);
bool DrawWeapon(EquipItem current, out EquipItem ret) bool DrawWeapon(EquipItem current, out EquipItem ret)
{ {
@ -741,15 +874,35 @@ public class EquipmentDrawer
ImGui.SameLine(); ImGui.SameLine();
if (DrawWeapon(cMainhand, out rMainhand)) if (DrawWeapon(cMainhand, out rMainhand))
ret |= DataChange.Item; ret |= DataChange.Item;
if (CanHaveCrest(EquipSlot.MainHand))
{
ImGui.SameLine();
ret |= DrawCrest(EquipSlot.MainHand, cMainhandCrest, cApply, out rMainhandCrest, out rApplyMainhandCrest, false, true, false);
}
else
{
rMainhandCrest = cMainhandCrest;
rApplyMainhandCrest = true;
}
} }
using (var id = ImRaii.PushId(1)) using (var id = ImRaii.PushId(1))
{ {
if (DrawStainArtisan(EquipSlot.OffHand, cOffhandStain, out rOffhandStain)) if (DrawStainArtisan(EquipSlot.OffHand, cOffhandStain, out rOffhandStain))
ret |= DataChange.Stain; ret |= DataChange.Stain2;
ImGui.SameLine(); ImGui.SameLine();
if (DrawWeapon(cOffhand, out rOffhand)) if (DrawWeapon(cOffhand, out rOffhand))
ret |= DataChange.Item; ret |= DataChange.Item2;
if (CanHaveCrest(EquipSlot.OffHand))
{
ImGui.SameLine();
ret |= DrawCrest(EquipSlot.OffHand, cOffhandCrest, cApply, out rOffhandCrest, out rApplyOffhandCrest, false, true, true);
}
else
{
rOffhandCrest = cOffhandCrest;
rApplyOffhandCrest = true;
}
} }
return ret; return ret;

View file

@ -176,7 +176,7 @@ public class ActorPanel
var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked); var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _state!.IsLocked);
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var changes = _equipmentDrawer.DrawEquip(slot, _state!.ModelData, out var newArmor, out var newStain, null, out _, out _, var changes = _equipmentDrawer.DrawEquip(slot, _state!.ModelData, out var newArmor, out var newStain, out var newCrest, null, out _, out _, out _,
_state.IsLocked); _state.IsLocked);
if (usedAllStain) if (usedAllStain)
{ {
@ -192,14 +192,20 @@ public class ActorPanel
case DataChange.Stain: case DataChange.Stain:
_stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual); _stateManager.ChangeStain(_state, slot, newStain, StateChanged.Source.Manual);
break; break;
case DataChange.Crest:
_stateManager.ChangeCrest(_state, slot, newCrest, StateChanged.Source.Manual);
break;
case DataChange.Item | DataChange.Stain: case DataChange.Item | DataChange.Stain:
_stateManager.ChangeEquip(_state, slot, newArmor, newStain, StateChanged.Source.Manual); case DataChange.Item | DataChange.Crest:
case DataChange.Stain | DataChange.Crest:
case DataChange.Item | DataChange.Stain | DataChange.Crest:
_stateManager.ChangeEquip(_state, slot, changes.HasFlag(DataChange.Item) ? newArmor : null, changes.HasFlag(DataChange.Stain) ? newStain : null, changes.HasFlag(DataChange.Crest) ? newCrest : null, StateChanged.Source.Manual);
break; break;
} }
} }
var weaponChanges = _equipmentDrawer.DrawWeapons(_state!.ModelData, out var newMainhand, out var newOffhand, out var newMainhandStain, var weaponChanges = _equipmentDrawer.DrawWeapons(_state!.ModelData, out var newMainhand, out var newOffhand, out var newMainhandStain,
out var newOffhandStain, null, GameMain.IsInGPose(), out _, out _, out _, out _, _state.IsLocked); out var newOffhandStain, out var newMainhandCrest, out var newOffhandCrest, null, GameMain.IsInGPose(), out _, out _, out _, out _, out _, out _, _state.IsLocked);
if (usedAllStain) if (usedAllStain)
{ {
weaponChanges |= DataChange.Stain | DataChange.Stain2; weaponChanges |= DataChange.Stain | DataChange.Stain2;
@ -208,20 +214,34 @@ public class ActorPanel
} }
if (weaponChanges.HasFlag(DataChange.Item)) if (weaponChanges.HasFlag(DataChange.Item))
if (weaponChanges.HasFlag(DataChange.Stain)) {
_stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMainhand, newMainhandStain, StateChanged.Source.Manual); if (weaponChanges.HasFlag(DataChange.Stain) || weaponChanges.HasFlag(DataChange.Crest))
_stateManager.ChangeEquip(_state, EquipSlot.MainHand, newMainhand, weaponChanges.HasFlag(DataChange.Stain) ? newMainhandStain : null, weaponChanges.HasFlag(DataChange.Crest) ? newMainhandCrest : null, StateChanged.Source.Manual);
else else
_stateManager.ChangeItem(_state, EquipSlot.MainHand, newMainhand, StateChanged.Source.Manual); _stateManager.ChangeItem(_state, EquipSlot.MainHand, newMainhand, StateChanged.Source.Manual);
else if (weaponChanges.HasFlag(DataChange.Stain)) }
_stateManager.ChangeStain(_state, EquipSlot.MainHand, newMainhandStain, StateChanged.Source.Manual); else
{
if (weaponChanges.HasFlag(DataChange.Stain))
_stateManager.ChangeStain(_state, EquipSlot.MainHand, newMainhandStain, StateChanged.Source.Manual);
if (weaponChanges.HasFlag(DataChange.Crest))
_stateManager.ChangeCrest(_state, EquipSlot.MainHand, newMainhandCrest, StateChanged.Source.Manual);
}
if (weaponChanges.HasFlag(DataChange.Item2)) if (weaponChanges.HasFlag(DataChange.Item2))
if (weaponChanges.HasFlag(DataChange.Stain2)) {
_stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOffhand, newOffhandStain, StateChanged.Source.Manual); if (weaponChanges.HasFlag(DataChange.Stain2) || weaponChanges.HasFlag(DataChange.Crest2))
_stateManager.ChangeEquip(_state, EquipSlot.OffHand, newOffhand, weaponChanges.HasFlag(DataChange.Stain2) ? newOffhandStain : null, weaponChanges.HasFlag(DataChange.Crest2) ? newOffhandCrest : null, StateChanged.Source.Manual);
else else
_stateManager.ChangeItem(_state, EquipSlot.OffHand, newOffhand, StateChanged.Source.Manual); _stateManager.ChangeItem(_state, EquipSlot.OffHand, newOffhand, StateChanged.Source.Manual);
else if (weaponChanges.HasFlag(DataChange.Stain2)) }
_stateManager.ChangeStain(_state, EquipSlot.OffHand, newOffhandStain, StateChanged.Source.Manual); else
{
if (weaponChanges.HasFlag(DataChange.Stain2))
_stateManager.ChangeStain(_state, EquipSlot.OffHand, newOffhandStain, StateChanged.Source.Manual);
if (weaponChanges.HasFlag(DataChange.Crest2))
_stateManager.ChangeCrest(_state, EquipSlot.OffHand, newOffhandCrest, StateChanged.Source.Manual);
}
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
if (EquipmentDrawer.DrawHatState(_state!.ModelData.IsHatVisible(), out var newHatState, _state!.IsLocked)) if (EquipmentDrawer.DrawHatState(_state!.ModelData.IsHatVisible(), out var newHatState, _state!.IsLocked))

View file

@ -141,8 +141,8 @@ public class SetPanel
{ {
var (numCheckboxes, numSpacing) = (_config.ShowAllAutomatedApplicationRules, _config.ShowUnlockedItemWarnings) switch var (numCheckboxes, numSpacing) = (_config.ShowAllAutomatedApplicationRules, _config.ShowUnlockedItemWarnings) switch
{ {
(true, true) => (9, 14), (true, true) => (10, 15),
(true, false) => (7, 10), (true, false) => (8, 11),
(false, true) => (4, 4), (false, true) => (4, 4),
(false, false) => (2, 0), (false, false) => (2, 0),
}; };
@ -173,7 +173,7 @@ public class SetPanel
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthFixed, 220 * ImGuiHelpers.GlobalScale);
if (_config.ShowAllAutomatedApplicationRules) if (_config.ShowAllAutomatedApplicationRules)
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed,
6 * ImGui.GetFrameHeight() + 10 * ImGuiHelpers.GlobalScale); 7 * ImGui.GetFrameHeight() + 11 * ImGuiHelpers.GlobalScale);
else else
ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
} }
@ -182,7 +182,7 @@ public class SetPanel
ImGui.TableSetupColumn("Design / Job Restrictions", ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("Design / Job Restrictions", ImGuiTableColumnFlags.WidthFixed, 250 * ImGuiHelpers.GlobalScale);
if (_config.ShowAllAutomatedApplicationRules) if (_config.ShowAllAutomatedApplicationRules)
ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed, ImGui.TableSetupColumn("Application", ImGuiTableColumnFlags.WidthFixed,
3 * ImGui.GetFrameHeight() + 4 * ImGuiHelpers.GlobalScale); 4 * ImGui.GetFrameHeight() + 5 * ImGuiHelpers.GlobalScale);
else else
ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X); ImGui.TableSetupColumn("Use", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Use").X);
} }
@ -383,14 +383,16 @@ public class SetPanel
Box(0); Box(0);
ImGui.SameLine(); ImGui.SameLine();
Box(1); Box(1);
ImGui.SameLine();
Box(2);
if (singleLine) if (singleLine)
ImGui.SameLine(); ImGui.SameLine();
Box(2);
ImGui.SameLine();
Box(3); Box(3);
ImGui.SameLine(); ImGui.SameLine();
Box(4); Box(4);
ImGui.SameLine();
Box(5);
} }
_manager.ChangeApplicationType(set, autoDesignIndex, newType); _manager.ChangeApplicationType(set, autoDesignIndex, newType);
@ -424,6 +426,7 @@ public class SetPanel
(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.Stains, "Apply all dye changes that are enabled in this design."),
(AutoDesign.Type.Crests, "Apply all crest visibility 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

@ -335,14 +335,15 @@ public unsafe class DebugTab : ITab
return; return;
if (ImGui.SmallButton("Hide")) if (ImGui.SmallButton("Hide"))
_updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty); _updateSlotService.UpdateSlot(model, EquipSlot.Head, CharacterArmor.Empty, null);
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Show")) if (ImGui.SmallButton("Show"))
_updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head)); _updateSlotService.UpdateSlot(model, EquipSlot.Head, actor.GetArmor(EquipSlot.Head), actor.GetCrest(EquipSlot.Head));
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Toggle")) if (ImGui.SmallButton("Toggle"))
_updateSlotService.UpdateSlot(model, EquipSlot.Head, _updateSlotService.UpdateSlot(model, EquipSlot.Head,
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty,
model.AsHuman->Head.Value == 0 ? actor.GetCrest(EquipSlot.Head) : null);
} }
private void DrawWeaponState(Actor actor, Model model) private void DrawWeaponState(Actor actor, Model model)
@ -423,8 +424,11 @@ public unsafe class DebugTab : ITab
if (ImGui.SmallButton("Change Stain")) if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5); _updateSlotService.UpdateStain(model, slot, 5);
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Toggle Crest"))
_updateSlotService.UpdateCrest(model, slot, !model.IsFreeCompanyCrestVisibleOnSlot((byte)slot.ToIndex()));
ImGui.SameLine();
if (ImGui.SmallButton("Reset")) if (ImGui.SmallButton("Reset"))
_updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot)); _updateSlotService.UpdateSlot(model, slot, actor.GetArmor(slot), actor.GetCrest(slot));
} }
} }
@ -1146,7 +1150,7 @@ public unsafe class DebugTab : ITab
public void DrawState(ActorData data, ActorState state) public void DrawState(ActorData data, ActorState state)
{ {
using var table = ImRaii.Table("##state", 7, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit); using var table = ImRaii.Table("##state", 9, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit);
if (!table) if (!table)
return; return;
@ -1189,10 +1193,12 @@ public unsafe class DebugTab : ITab
ImGui.TableNextRow(); ImGui.TableNextRow();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) 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[slot, ActorState.EquipField.Item]);
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); ImGuiUtil.DrawTableColumn(state[slot, ActorState.EquipField.Stain].ToString());
ImGuiUtil.DrawTableColumn(state.BaseData.Crest(slot).ToString());
ImGuiUtil.DrawTableColumn(state[slot, ActorState.EquipField.Crest].ToString());
} }
foreach (var type in Enum.GetValues<CustomizeIndex>()) foreach (var type in Enum.GetValues<CustomizeIndex>())
@ -1307,12 +1313,16 @@ public unsafe class DebugTab : ITab
var apply = design.DoApplyEquip(slot); var apply = design.DoApplyEquip(slot);
var stain = design.DesignData.Stain(slot); var stain = design.DesignData.Stain(slot);
var applyStain = design.DoApplyStain(slot); var applyStain = design.DoApplyStain(slot);
var crest = design.DesignData.Crest(slot);
var applyCrest = design.DoApplyCrest(slot);
ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(slot.ToName());
ImGuiUtil.DrawTableColumn(item.Name); ImGuiUtil.DrawTableColumn(item.Name);
ImGuiUtil.DrawTableColumn(item.ItemId.ToString()); ImGuiUtil.DrawTableColumn(item.ItemId.ToString());
ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep"); ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep");
ImGuiUtil.DrawTableColumn(stain.ToString()); ImGuiUtil.DrawTableColumn(stain.ToString());
ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep"); ImGuiUtil.DrawTableColumn(applyStain ? "Apply" : "Keep");
ImGuiUtil.DrawTableColumn(crest.ToString());
ImGuiUtil.DrawTableColumn(applyCrest ? "Apply" : "Keep");
} }
ImGuiUtil.DrawTableColumn("Hat Visible"); ImGuiUtil.DrawTableColumn("Hat Visible");

View file

@ -118,6 +118,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
case DesignChanged.Type.ApplyCustomize: case DesignChanged.Type.ApplyCustomize:
case DesignChanged.Type.ApplyEquip: case DesignChanged.Type.ApplyEquip:
case DesignChanged.Type.ApplyStain: case DesignChanged.Type.ApplyStain:
case DesignChanged.Type.ApplyCrest:
case DesignChanged.Type.Customize: case DesignChanged.Type.Customize:
case DesignChanged.Type.Equip: case DesignChanged.Type.Equip:
case DesignChanged.Type.ChangedColor: case DesignChanged.Type.ChangedColor:

View file

@ -28,6 +28,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations,
DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel) DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel)
{ {
private static readonly EquipSlot[] CrestSlots = EquipSlotExtensions.FullSlots.Where(EquipmentDrawer.CanHaveCrest).ToArray();
private readonly FileDialogManager _fileDialog = new(); private readonly FileDialogManager _fileDialog = new();
private HeaderDrawer.Button LockButton() private HeaderDrawer.Button LockButton()
@ -95,23 +97,28 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected()); var usedAllStain = _equipmentDrawer.DrawAllStain(out var newAllStain, _selector.Selected!.WriteProtected());
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
var changes = _equipmentDrawer.DrawEquip(slot, _selector.Selected!.DesignData, out var newArmor, out var newStain, var changes = _equipmentDrawer.DrawEquip(slot, _selector.Selected!.DesignData, out var newArmor, out var newStain, out var newCrest,
_selector.Selected.ApplyEquip, out var newApply, out var newApplyStain, _selector.Selected!.WriteProtected()); _selector.Selected.ApplyEquip, out var newApply, out var newApplyStain, out var newApplyCrest, _selector.Selected!.WriteProtected());
if (changes.HasFlag(DataChange.Item)) if (changes.HasFlag(DataChange.Item))
_manager.ChangeEquip(_selector.Selected, slot, newArmor); _manager.ChangeEquip(_selector.Selected, slot, newArmor);
if (changes.HasFlag(DataChange.Stain)) if (changes.HasFlag(DataChange.Stain))
_manager.ChangeStain(_selector.Selected, slot, newStain); _manager.ChangeStain(_selector.Selected, slot, newStain);
else if (usedAllStain) else if (usedAllStain)
_manager.ChangeStain(_selector.Selected, slot, newAllStain); _manager.ChangeStain(_selector.Selected, slot, newAllStain);
if (changes.HasFlag(DataChange.Crest))
_manager.ChangeCrest(_selector.Selected, slot, newCrest);
if (changes.HasFlag(DataChange.ApplyItem)) if (changes.HasFlag(DataChange.ApplyItem))
_manager.ChangeApplyEquip(_selector.Selected, slot, newApply); _manager.ChangeApplyEquip(_selector.Selected, slot, newApply);
if (changes.HasFlag(DataChange.ApplyStain)) if (changes.HasFlag(DataChange.ApplyStain))
_manager.ChangeApplyStain(_selector.Selected, slot, newApplyStain); _manager.ChangeApplyStain(_selector.Selected, slot, newApplyStain);
if (changes.HasFlag(DataChange.ApplyCrest))
_manager.ChangeApplyCrest(_selector.Selected, slot, newApplyCrest);
} }
var weaponChanges = _equipmentDrawer.DrawWeapons(_selector.Selected!.DesignData, out var newMainhand, out var newOffhand, var weaponChanges = _equipmentDrawer.DrawWeapons(_selector.Selected!.DesignData, out var newMainhand, out var newOffhand,
out var newMainhandStain, out var newOffhandStain, _selector.Selected.ApplyEquip, true, out var applyMain, out var applyMainStain, out var newMainhandStain, out var newOffhandStain, out var newMainhandCrest, out var newOffhandCrest,
out var applyOff, out var applyOffStain, _selector.Selected!.WriteProtected()); _selector.Selected.ApplyEquip, true, out var applyMain, out var applyMainStain, out var applyMainCrest,
out var applyOff, out var applyOffStain, out var applyOffCrest, _selector.Selected!.WriteProtected());
if (weaponChanges.HasFlag(DataChange.Item)) if (weaponChanges.HasFlag(DataChange.Item))
_manager.ChangeWeapon(_selector.Selected, EquipSlot.MainHand, newMainhand); _manager.ChangeWeapon(_selector.Selected, EquipSlot.MainHand, newMainhand);
@ -119,20 +126,28 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
_manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newMainhandStain); _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newMainhandStain);
else if (usedAllStain) else if (usedAllStain)
_manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newAllStain); _manager.ChangeStain(_selector.Selected, EquipSlot.MainHand, newAllStain);
if (weaponChanges.HasFlag(DataChange.Crest))
_manager.ChangeCrest(_selector.Selected, EquipSlot.MainHand, newMainhandCrest);
if (weaponChanges.HasFlag(DataChange.ApplyItem)) if (weaponChanges.HasFlag(DataChange.ApplyItem))
_manager.ChangeApplyEquip(_selector.Selected, EquipSlot.MainHand, applyMain); _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.MainHand, applyMain);
if (weaponChanges.HasFlag(DataChange.ApplyStain)) if (weaponChanges.HasFlag(DataChange.ApplyStain))
_manager.ChangeApplyStain(_selector.Selected, EquipSlot.MainHand, applyMainStain); _manager.ChangeApplyStain(_selector.Selected, EquipSlot.MainHand, applyMainStain);
if (weaponChanges.HasFlag(DataChange.ApplyCrest))
_manager.ChangeApplyCrest(_selector.Selected, EquipSlot.MainHand, applyMainCrest);
if (weaponChanges.HasFlag(DataChange.Item2)) if (weaponChanges.HasFlag(DataChange.Item2))
_manager.ChangeWeapon(_selector.Selected, EquipSlot.OffHand, newOffhand); _manager.ChangeWeapon(_selector.Selected, EquipSlot.OffHand, newOffhand);
if (weaponChanges.HasFlag(DataChange.Stain2)) if (weaponChanges.HasFlag(DataChange.Stain2))
_manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newOffhandStain); _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newOffhandStain);
else if (usedAllStain) else if (usedAllStain)
_manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newAllStain); _manager.ChangeStain(_selector.Selected, EquipSlot.OffHand, newAllStain);
if (weaponChanges.HasFlag(DataChange.Crest2))
_manager.ChangeCrest(_selector.Selected, EquipSlot.OffHand, newOffhandCrest);
if (weaponChanges.HasFlag(DataChange.ApplyItem2)) if (weaponChanges.HasFlag(DataChange.ApplyItem2))
_manager.ChangeApplyEquip(_selector.Selected, EquipSlot.OffHand, applyOff); _manager.ChangeApplyEquip(_selector.Selected, EquipSlot.OffHand, applyOff);
if (weaponChanges.HasFlag(DataChange.ApplyStain2)) if (weaponChanges.HasFlag(DataChange.ApplyStain2))
_manager.ChangeApplyStain(_selector.Selected, EquipSlot.OffHand, applyOffStain); _manager.ChangeApplyStain(_selector.Selected, EquipSlot.OffHand, applyOffStain);
if (weaponChanges.HasFlag(DataChange.ApplyCrest2))
_manager.ChangeApplyCrest(_selector.Selected, EquipSlot.OffHand, applyOffCrest);
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawEquipmentMetaToggles(); DrawEquipmentMetaToggles();
@ -223,43 +238,59 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots) void ApplyEquip(string label, EquipFlag allFlags, ActorState.EquipField equipField, IEnumerable<EquipSlot> slots)
{ {
var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); // The flags we may edit with a single "big change" checkbox will always fit in a uint, but the whole bitfield doesn't
var shift = (int)equipField;
var flags = (uint)((ulong)(allFlags & _selector.Selected!.ApplyEquip) >> shift);
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)((ulong)allFlags >> shift));
if (stain)
foreach (var slot in slots) var adjustedFlags = (EquipFlag)((ulong)flags << shift);
{ switch (equipField)
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot); {
if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange) case ActorState.EquipField.Stain:
_manager.ChangeApplyStain(_selector.Selected!, slot, apply); foreach (var slot in slots)
} {
else var apply = bigChange ? adjustedFlags.HasFlag(slot.ToStainFlag()) : _selector.Selected!.DoApplyStain(slot);
foreach (var slot in slots) if (ImGui.Checkbox($"Apply {slot.ToName()} Dye", ref apply) || bigChange)
{ _manager.ChangeApplyStain(_selector.Selected!, slot, apply);
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot); }
if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange) break;
_manager.ChangeApplyEquip(_selector.Selected!, slot, apply); case ActorState.EquipField.Crest:
} foreach (var slot in slots)
{
var apply = bigChange ? adjustedFlags.HasFlag(slot.ToCrestFlag()) : _selector.Selected!.DoApplyCrest(slot);
if (ImGui.Checkbox($"Apply {slot.ToName()} Crest Visibility", ref apply) || bigChange)
_manager.ChangeApplyCrest(_selector.Selected!, slot, apply);
}
break;
default:
foreach (var slot in slots)
{
var apply = bigChange ? adjustedFlags.HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot);
if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
_manager.ChangeApplyEquip(_selector.Selected!, slot, apply);
}
break;
}
} }
ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[] ApplyEquip("Weapons", AutoDesign.WeaponFlags, ActorState.EquipField.Item, EquipSlotExtensions.WeaponSlots);
{
EquipSlot.MainHand,
EquipSlot.OffHand,
});
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); ApplyEquip("Armor", AutoDesign.ArmorFlags, ActorState.EquipField.Item, EquipSlotExtensions.EquipmentSlots);
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); ApplyEquip("Accessories", AutoDesign.AccessoryFlags, ActorState.EquipField.Item, EquipSlotExtensions.AccessorySlots);
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Dyes", AutoDesign.StainFlags, true, ApplyEquip("Dyes", AutoDesign.StainFlags, ActorState.EquipField.Stain,
EquipSlotExtensions.FullSlots); EquipSlotExtensions.FullSlots);
ImGui.NewLine();
ApplyEquip("Crest Visibilities", AutoDesign.RelevantCrestFlags, ActorState.EquipField.Crest, CrestSlots);
ImGui.NewLine(); ImGui.NewLine();
const uint all = 0x0Fu; const uint all = 0x0Fu;
var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00)

View file

@ -17,7 +17,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui; namespace Glamourer.Gui;
[Flags] [Flags]
public enum DataChange : byte public enum DataChange : ushort
{ {
None = 0x00, None = 0x00,
Item = 0x01, Item = 0x01,
@ -28,6 +28,10 @@ public enum DataChange : byte
Stain2 = 0x20, Stain2 = 0x20,
ApplyItem2 = 0x40, ApplyItem2 = 0x40,
ApplyStain2 = 0x80, ApplyStain2 = 0x80,
Crest = 0x100,
ApplyCrest = 0x200,
Crest2 = 0x400,
ApplyCrest2 = 0x800,
} }
public static class UiHelpers public static class UiHelpers

View file

@ -119,14 +119,14 @@ public class ContextMenuService : IDisposable
return; return;
var slot = item.Type.ToSlot(); var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, slot, item, 0, false, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
{ {
if (item.ModelId.Id is > 1600 and < 1651 if (item.ModelId.Id is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, false, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, false, StateChanged.Source.Manual);
} }
}; };
} }
@ -143,14 +143,14 @@ public class ContextMenuService : IDisposable
return; return;
var slot = item.Type.ToSlot(); var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, slot, item, 0, false, StateChanged.Source.Manual);
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
{ {
if (item.ModelId.Id is > 1600 and < 1651 if (item.ModelId.Id is > 1600 and < 1651
&& _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets)) && _items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, false, StateChanged.Source.Manual);
if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand)) if (_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual); _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, false, StateChanged.Source.Manual);
} }
}; };
} }

View file

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

View file

@ -91,6 +91,9 @@ public readonly unsafe struct Model : IEquatable<Model>
public CharacterArmor GetArmor(EquipSlot slot) public CharacterArmor GetArmor(EquipSlot slot)
=> ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()]; => ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()];
public bool GetCrest(EquipSlot slot)
=> IsFreeCompanyCrestVisibleOnSlot((byte)slot.ToIndex());
public Customize GetCustomize() public Customize GetCustomize()
=> *(Customize*)&AsHuman->Customize; => *(Customize*)&AsHuman->Customize;
@ -195,6 +198,27 @@ public readonly unsafe struct Model : IEquatable<Model>
return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second); return discriminator1 == 0 && discriminator2 != 0 ? (second, first) : (first, second);
} }
// TODO remove these when available in ClientStructs
public bool IsFreeCompanyCrestVisibleOnSlot(byte slot)
{
if (!IsCharacterBase)
return false;
var characterBase = AsCharacterBase;
var getter = (delegate* unmanaged<CharacterBase*, byte, byte>)((nint*)characterBase->VTable)[95];
return getter(characterBase, slot) != 0;
}
public void SetFreeCompanyCrestVisibleOnSlot(byte slot, bool visible)
{
if (!IsCharacterBase)
return;
var characterBase = AsCharacterBase;
var setter = (delegate* unmanaged<CharacterBase*, byte, byte, void>)((nint*)characterBase->VTable)[96];
setter(characterBase, slot, visible ? (byte)1 : (byte)0);
}
public override string ToString() public override string ToString()
=> $"0x{Address:X}"; => $"0x{Address:X}";
} }

View file

@ -1,4 +1,6 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Threading;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
@ -6,47 +8,78 @@ using Glamourer.Events;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using PenumbraSigs = Penumbra.GameData.Sigs;
namespace Glamourer.Interop; namespace Glamourer.Interop;
public unsafe class UpdateSlotService : IDisposable public unsafe class UpdateSlotService : IDisposable
{ {
public readonly SlotUpdating SlotUpdatingEvent; public readonly SlotUpdating SlotUpdatingEvent;
public readonly CrestVisibilityUpdating CrestVisibilityUpdatingEvent;
private readonly ThreadLocal<uint> _crestVisibilityUpdate = new(() => 0, false);
public UpdateSlotService(SlotUpdating slotUpdating, IGameInteropProvider interop) public UpdateSlotService(SlotUpdating slotUpdating, CrestVisibilityUpdating crestVisibilityUpdating, IGameInteropProvider interop)
{ {
SlotUpdatingEvent = slotUpdating; SlotUpdatingEvent = slotUpdating;
CrestVisibilityUpdatingEvent = crestVisibilityUpdating;
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_humanSetFreeCompanyCrestVisibleOnSlot = interop.HookFromAddress<SetCrestDelegateIntern>(_humanVTable[96], HumanSetFreeCompanyCrestVisibleOnSlotDetour);
_weaponSetFreeCompanyCrestVisibleOnSlot = interop.HookFromAddress<SetCrestDelegateIntern>(_weaponVTable[96], WeaponSetFreeCompanyCrestVisibleOnSlotDetour);
_flagSlotForUpdateHook.Enable(); _flagSlotForUpdateHook.Enable();
_humanSetFreeCompanyCrestVisibleOnSlot.Enable();
_weaponSetFreeCompanyCrestVisibleOnSlot.Enable();
} }
public void Dispose() public void Dispose()
{ {
_flagSlotForUpdateHook.Dispose(); _flagSlotForUpdateHook.Dispose();
_humanSetFreeCompanyCrestVisibleOnSlot.Dispose();
_weaponSetFreeCompanyCrestVisibleOnSlot.Dispose();
} }
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data) public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data, bool? crest)
{ {
if (!drawObject.IsCharacterBase) if (!drawObject.IsCharacterBase)
return; return;
FlagSlotForUpdateInterop(drawObject, slot, data); FlagSlotForUpdateInterop(drawObject, slot, data);
if (crest.HasValue)
{
using var _ = EnterCrestVisibilityUpdate();
drawObject.SetFreeCompanyCrestVisibleOnSlot((byte)slot.ToIndex(), crest.Value);
}
} }
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainId stain, bool? crest)
=> UpdateSlot(drawObject, slot, armor.With(stain)); => UpdateSlot(drawObject, slot, armor.With(stain), crest);
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain); => UpdateArmor(drawObject, slot, armor, drawObject.GetArmor(slot).Stain, null);
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain) public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain); => UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain, null);
public void UpdateCrest(Model drawObject, EquipSlot slot, bool crest)
{
using var _ = EnterCrestVisibilityUpdate();
drawObject.SetFreeCompanyCrestVisibleOnSlot((byte)slot.ToIndex(), crest);
}
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data); private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
private delegate void SetCrestDelegateIntern(nint drawObject, byte slot, byte visible);
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))] [Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!; private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
[Signature(PenumbraSigs.HumanVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _humanVTable = null!;
[Signature(PenumbraSigs.WeaponVTable, ScanType = ScanType.StaticAddress)]
private readonly nint* _weaponVTable = null!;
private readonly Hook<SetCrestDelegateIntern> _humanSetFreeCompanyCrestVisibleOnSlot;
private readonly Hook<SetCrestDelegateIntern> _weaponSetFreeCompanyCrestVisibleOnSlot;
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
{ {
var slot = slotIdx.ToEquipSlot(); var slot = slotIdx.ToEquipSlot();
@ -56,6 +89,52 @@ public unsafe class UpdateSlotService : IDisposable
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue; return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
} }
private void HumanSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible)
{
var slot = ((uint)slotIdx).ToEquipSlot();
var rVisible = visible != 0;
var inUpdate = _crestVisibilityUpdate.IsValueCreated && _crestVisibilityUpdate.Value > 0;
if (!inUpdate)
CrestVisibilityUpdatingEvent.Invoke(drawObject, slot, ref rVisible);
Glamourer.Log.Excessive($"[Human.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} for slot {slot} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
_humanSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
}
private void WeaponSetFreeCompanyCrestVisibleOnSlotDetour(nint drawObject, byte slotIdx, byte visible)
{
var rVisible = visible != 0;
var inUpdate = _crestVisibilityUpdate.IsValueCreated && _crestVisibilityUpdate.Value > 0;
if (!inUpdate)
CrestVisibilityUpdatingEvent.Invoke(drawObject, EquipSlot.BothHand, ref rVisible);
Glamourer.Log.Excessive($"[Weapon.SetFreeCompanyCrestVisibleOnSlot] Called with 0x{drawObject:X} with {rVisible} (original: {visible != 0}, in update: {inUpdate}).");
_weaponSetFreeCompanyCrestVisibleOnSlot.Original(drawObject, slotIdx, rVisible ? (byte)1 : (byte)0);
}
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor) private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); => _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
/// <summary>
/// Temporarily disables the crest update hooks on the current thread.
/// </summary>
/// <returns> A struct that will undo this operation when disposed. Best used with: <code>using (var _ = updateSlotService.EnterCrestVisibilityUpdate()) { ... }</code> </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public CrestVisibilityUpdateRaii EnterCrestVisibilityUpdate()
=> new(this);
public readonly ref struct CrestVisibilityUpdateRaii
{
private readonly ThreadLocal<uint> _crestVisibilityUpdate;
public CrestVisibilityUpdateRaii(UpdateSlotService parent)
{
_crestVisibilityUpdate = parent._crestVisibilityUpdate;
++_crestVisibilityUpdate.Value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public readonly void Dispose()
{
--_crestVisibilityUpdate.Value;
}
}
} }

View file

@ -13,6 +13,7 @@ namespace Glamourer.Interop;
public unsafe class WeaponService : IDisposable public unsafe class WeaponService : IDisposable
{ {
private readonly WeaponLoading _event; private readonly WeaponLoading _event;
private readonly UpdateSlotService _updateSlotService;
private readonly ThreadLocal<bool> _inUpdate = new(() => false); private readonly ThreadLocal<bool> _inUpdate = new(() => false);
@ -20,9 +21,10 @@ public unsafe class WeaponService : IDisposable
_original; _original;
public WeaponService(WeaponLoading @event, IGameInteropProvider interop) public WeaponService(WeaponLoading @event, UpdateSlotService updateSlotService, IGameInteropProvider interop)
{ {
_event = @event; _event = @event;
_updateSlotService = updateSlotService;
_loadWeaponHook = _loadWeaponHook =
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour); interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
_original = _original =
@ -83,24 +85,39 @@ public unsafe class WeaponService : IDisposable
} }
// Load a specific weapon for a character by its data and slot. // Load a specific weapon for a character by its data and slot.
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon) public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon, bool? crest)
{ {
switch (slot) switch (slot)
{ {
case EquipSlot.MainHand: case EquipSlot.MainHand:
_inUpdate.Value = true; _inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
if (crest.HasValue)
{
using var _ = _updateSlotService.EnterCrestVisibilityUpdate();
character.Model.GetMainhand().Address.SetFreeCompanyCrestVisibleOnSlot(0, crest.Value);
}
_inUpdate.Value = false; _inUpdate.Value = false;
return; return;
case EquipSlot.OffHand: case EquipSlot.OffHand:
_inUpdate.Value = true; _inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0);
if (crest.HasValue)
{
using var _ = _updateSlotService.EnterCrestVisibilityUpdate();
character.Model.GetOffhand().Address.SetFreeCompanyCrestVisibleOnSlot(0, crest.Value);
}
_inUpdate.Value = false; _inUpdate.Value = false;
return; return;
case EquipSlot.BothHand: case EquipSlot.BothHand:
_inUpdate.Value = true; _inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0); _loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0);
if (crest.HasValue)
{
using var _ = _updateSlotService.EnterCrestVisibilityUpdate();
character.Model.GetMainhand().Address.SetFreeCompanyCrestVisibleOnSlot(0, crest.Value);
}
_inUpdate.Value = false; _inUpdate.Value = false;
return; return;
} }
@ -112,6 +129,21 @@ public unsafe class WeaponService : IDisposable
var (_, _, mh, oh) = mdl.GetWeapons(character); var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh; var value = slot == EquipSlot.OffHand ? oh : mh;
var weapon = value.With(value.Set.Id == 0 ? 0 : stain); var weapon = value.With(value.Set.Id == 0 ? 0 : stain);
LoadWeapon(character, slot, weapon); LoadWeapon(character, slot, weapon, null);
}
public void UpdateCrest(Actor character, EquipSlot slot, bool visible)
{
using var _ = _updateSlotService.EnterCrestVisibilityUpdate();
switch (slot)
{
case EquipSlot.MainHand:
case EquipSlot.BothHand:
character.Model.GetMainhand().Address.SetFreeCompanyCrestVisibleOnSlot(0, visible);
break;
case EquipSlot.OffHand:
character.Model.GetOffhand().Address.SetFreeCompanyCrestVisibleOnSlot(0, visible);
break;
}
} }
} }

View file

@ -165,8 +165,9 @@ public class CommandService : IDisposable
.AddInitialPurple("Customizations, ") .AddInitialPurple("Customizations, ")
.AddInitialPurple("Equipment, ") .AddInitialPurple("Equipment, ")
.AddInitialPurple("Accessories, ") .AddInitialPurple("Accessories, ")
.AddInitialPurple("Dyes and ") .AddInitialPurple("Dyes, ")
.AddInitialPurple("Weapons, where ").AddPurple("CEADW") .AddInitialPurple("Free company crest visibilities and ")
.AddInitialPurple("Weapons, where ").AddPurple("CEADFW")
.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);
return false; return false;
@ -270,6 +271,9 @@ public class CommandService : IDisposable
case 'd': case 'd':
applicationFlags |= AutoDesign.Type.Stains; applicationFlags |= AutoDesign.Type.Stains;
break; break;
case 'f':
applicationFlags |= AutoDesign.Type.Crests;
break;
case 'w': case 'w':
applicationFlags |= AutoDesign.Type.Weapons; applicationFlags |= AutoDesign.Type.Weapons;
break; break;

View file

@ -64,6 +64,7 @@ public static class ServiceManager
private static IServiceCollection AddEvents(this IServiceCollection services) private static IServiceCollection AddEvents(this IServiceCollection services)
=> services.AddSingleton<VisorStateChanged>() => services.AddSingleton<VisorStateChanged>()
.AddSingleton<SlotUpdating>() .AddSingleton<SlotUpdating>()
.AddSingleton<CrestVisibilityUpdating>()
.AddSingleton<DesignChanged>() .AddSingleton<DesignChanged>()
.AddSingleton<AutomationChanged>() .AddSingleton<AutomationChanged>()
.AddSingleton<StateChanged>() .AddSingleton<StateChanged>()

View file

@ -22,6 +22,13 @@ public class ActorState
ModelId, ModelId,
} }
public enum EquipField
{
Item = 0,
Stain = 12,
Crest = 24,
}
public readonly ActorIdentifier Identifier; public readonly ActorIdentifier Identifier;
public bool AllowsRedraw(ICondition condition) public bool AllowsRedraw(ICondition condition)
@ -85,8 +92,8 @@ public class ActorState
internal ActorState(ActorIdentifier identifier) internal ActorState(ActorIdentifier identifier)
=> Identifier = identifier.CreatePermanent(); => Identifier = identifier.CreatePermanent();
public ref StateChanged.Source this[EquipSlot slot, bool stain] public ref StateChanged.Source this[EquipSlot slot, EquipField field]
=> ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)]; => ref _sources[slot.ToIndex() + (int)field];
public ref StateChanged.Source this[CustomizeIndex type] public ref StateChanged.Source this[CustomizeIndex type]
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type]; => ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];

View file

@ -99,11 +99,11 @@ public class StateApplier
} }
/// <summary> /// <summary>
/// Change a single piece of armor and/or stain depending on slot. /// Change a single piece of armor and/or stain and/or crest visibility depending on slot.
/// This uses the current customization of the model to potentially prevent restricted gear types from appearing. /// This uses the current customization of the model to potentially prevent restricted gear types from appearing.
/// This never requires redrawing. /// This never requires redrawing.
/// </summary> /// </summary>
public void ChangeArmor(ActorData data, EquipSlot slot, CharacterArmor armor, bool checkRestrictions, bool isHatVisible = true) public void ChangeArmor(ActorData data, EquipSlot slot, CharacterArmor armor, bool crest, bool checkRestrictions, bool isHatVisible = true)
{ {
if (slot is EquipSlot.Head && !isHatVisible) if (slot is EquipSlot.Head && !isHatVisible)
return; return;
@ -118,11 +118,11 @@ public class StateApplier
{ {
var customize = mdl.GetCustomize(); var customize = mdl.GetCustomize();
var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender); var (_, resolvedItem) = _items.ResolveRestrictedGear(armor, slot, customize.Race, customize.Gender);
_updateSlot.UpdateSlot(actor.Model, slot, resolvedItem); _updateSlot.UpdateSlot(actor.Model, slot, resolvedItem, crest);
} }
else else
{ {
_updateSlot.UpdateSlot(actor.Model, slot, armor); _updateSlot.UpdateSlot(actor.Model, slot, armor, crest);
} }
} }
} }
@ -133,7 +133,7 @@ public class StateApplier
// If the source is not IPC we do not want to apply restrictions. // If the source is not IPC we do not want to apply restrictions.
var data = GetData(state); var data = GetData(state);
if (apply) 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.ModelData.Crest(slot), state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
return data; return data;
@ -175,13 +175,47 @@ public class StateApplier
} }
/// <summary>
/// Change the crest visibility of a single piece of armor or weapon.
/// </summary>
public void ChangeCrest(ActorData data, EquipSlot slot, bool visible)
{
var idx = slot.ToIndex();
switch (idx)
{
case < 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_updateSlot.UpdateCrest(actor.Model, slot, visible);
break;
case 10:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.UpdateCrest(actor, EquipSlot.MainHand, visible);
break;
case 11:
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.UpdateCrest(actor, EquipSlot.OffHand, visible);
break;
}
}
/// <inheritdoc cref="ChangeCrest(ActorData,EquipSlot,bool)"/>
public ActorData ChangeCrest(ActorState state, EquipSlot slot, bool apply)
{
var data = GetData(state);
if (apply)
ChangeCrest(data, slot, state.ModelData.Crest(slot));
return data;
}
/// <summary> Apply a weapon to the appropriate slot. </summary> /// <summary> Apply a weapon to the appropriate slot. </summary>
public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain) public void ChangeWeapon(ActorData data, EquipSlot slot, EquipItem item, StainId stain, bool crest)
{ {
if (slot is EquipSlot.MainHand) if (slot is EquipSlot.MainHand)
ChangeMainhand(data, item, stain); ChangeMainhand(data, item, stain, crest);
else else
ChangeOffhand(data, item, stain); ChangeOffhand(data, item, stain, crest);
} }
/// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/> /// <inheritdoc cref="ChangeWeapon(ActorData,EquipSlot,EquipItem,StainId)"/>
@ -192,7 +226,7 @@ public class StateApplier
data = data.OnlyGPose(); data = data.OnlyGPose();
if (apply) if (apply)
ChangeWeapon(data, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot)); ChangeWeapon(data, slot, state.ModelData.Item(slot), state.ModelData.Stain(slot), state.ModelData.Crest(slot));
return data; return data;
} }
@ -200,19 +234,19 @@ public class StateApplier
/// <summary> /// <summary>
/// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both. /// Apply a weapon to the mainhand. If the weapon type has no associated offhand type, apply both.
/// </summary> /// </summary>
public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain) public void ChangeMainhand(ActorData data, EquipItem weapon, StainId stain, bool crest)
{ {
var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand; var slot = weapon.Type.ValidOffhand() == FullEquipType.Unknown ? EquipSlot.BothHand : EquipSlot.MainHand;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain)); _weapon.LoadWeapon(actor, slot, weapon.Weapon().With(stain), crest);
} }
/// <summary> Apply a weapon to the offhand. </summary> /// <summary> Apply a weapon to the offhand. </summary>
public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain) public void ChangeOffhand(ActorData data, EquipItem weapon, StainId stain, bool crest)
{ {
stain = weapon.ModelId.Id == 0 ? 0 : stain; stain = weapon.ModelId.Id == 0 ? 0 : stain;
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman)) foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain)); _weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain), crest);
} }
/// <summary> Change the visor state of actors only on the draw object. </summary> /// <summary> Change the visor state of actors only on the draw object. </summary>

View file

@ -67,8 +67,9 @@ public class StateEditor
state[ActorState.MetaIndex.VisorState] = source; state[ActorState.MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
state[slot, true] = source; state[slot, ActorState.EquipField.Stain] = source;
state[slot, false] = source; state[slot, ActorState.EquipField.Item] = source;
state[slot, ActorState.EquipField.Crest] = source;
} }
state[CustomizeIndex.Clan] = source; state[CustomizeIndex.Clan] = source;
@ -126,7 +127,7 @@ public class StateEditor
return true; return true;
} }
/// <summary> Change a single piece of equipment without stain. </summary> /// <summary> Change a single piece of equipment without stain or crest visibility. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0) public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0)
{ {
oldItem = state.ModelData.Item(slot); oldItem = state.ModelData.Item(slot);
@ -144,44 +145,58 @@ public class StateEditor
_gPose.AddActionOnLeave(() => _gPose.AddActionOnLeave(() =>
{ {
if (old.Type == state.BaseData.Item(slot).Type) if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state[slot, false], out _, key); ChangeItem(state, slot, old, state[slot, ActorState.EquipField.Item], out _, key);
}); });
} }
state.ModelData.SetItem(slot, item); state.ModelData.SetItem(slot, item);
state[slot, false] = source; state[slot, ActorState.EquipField.Item] = source;
return true; return true;
} }
/// <summary> Change a single piece of equipment including stain. </summary> /// <summary> Change a single piece of equipment including stain and crest visibility. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, out EquipItem oldItem, public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem? item, StainId? stain, bool? crest, StateChanged.Source source, out EquipItem oldItem,
out StainId oldStain, uint key = 0) out StainId oldStain, out bool oldCrest, uint key = 0)
{ {
oldItem = state.ModelData.Item(slot); oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot); oldStain = state.ModelData.Stain(slot);
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key)) if (!state.CanUnlock(key))
return false; return false;
// Can not change weapon type from expected type in state. // Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType if (item.HasValue && (slot is EquipSlot.MainHand && item.Value.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType) || slot is EquipSlot.OffHand && item.Value.Type != state.BaseData.OffhandType))
{ {
if (!_gPose.InGPose) if (!_gPose.InGPose)
return false; return false;
var old = oldItem; var old = oldItem;
var oldS = oldStain; var oldS = oldStain;
var oldC = oldCrest;
_gPose.AddActionOnLeave(() => _gPose.AddActionOnLeave(() =>
{ {
if (old.Type == state.BaseData.Item(slot).Type) if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key); ChangeEquip(state, slot, old, oldS, oldC, state[slot, ActorState.EquipField.Item], out _, out _, out _, key);
}); });
} }
state.ModelData.SetItem(slot, item); if (item.HasValue)
state.ModelData.SetStain(slot, stain); {
state[slot, false] = source; state.ModelData.SetItem(slot, item.Value);
state[slot, true] = source; state[slot, ActorState.EquipField.Item] = source;
}
if (stain.HasValue)
{
state.ModelData.SetStain(slot, stain.Value);
state[slot, ActorState.EquipField.Stain] = source;
}
if (crest.HasValue)
{
state.ModelData.SetCrest(slot, crest.Value);
state[slot, ActorState.EquipField.Crest] = source;
}
return true; return true;
} }
@ -193,7 +208,19 @@ public class StateEditor
return false; return false;
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stain);
state[slot, true] = source; state[slot, ActorState.EquipField.Stain] = source;
return true;
}
/// <summary> Change only the crest visibility of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, EquipSlot 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, ActorState.EquipField.Crest] = source;
return true; return true;
} }

View file

@ -32,6 +32,7 @@ public class StateListener : IDisposable
private readonly CustomizationService _customizations; private readonly CustomizationService _customizations;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly SlotUpdating _slotUpdating; private readonly SlotUpdating _slotUpdating;
private readonly CrestVisibilityUpdating _crestVisibilityUpdating;
private readonly WeaponLoading _weaponLoading; private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState; private readonly VisorStateChanged _visorState;
@ -51,29 +52,30 @@ public class StateListener : IDisposable
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
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, CrestVisibilityUpdating crestVisibilityUpdating, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition) ChangeCustomizeService changeCustomizeService, CustomizationService customizations, ICondition condition)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
_penumbra = penumbra; _penumbra = penumbra;
_actors = actors; _actors = actors;
_config = config; _config = config;
_slotUpdating = slotUpdating; _slotUpdating = slotUpdating;
_weaponLoading = weaponLoading; _crestVisibilityUpdating = crestVisibilityUpdating;
_visorState = visorState; _weaponLoading = weaponLoading;
_weaponVisibility = weaponVisibility; _visorState = visorState;
_headGearVisibility = headGearVisibility; _weaponVisibility = weaponVisibility;
_autoDesignApplier = autoDesignApplier; _headGearVisibility = headGearVisibility;
_funModule = funModule; _autoDesignApplier = autoDesignApplier;
_humans = humans; _funModule = funModule;
_applier = applier; _humans = humans;
_movedEquipment = movedEquipment; _applier = applier;
_objects = objects; _movedEquipment = movedEquipment;
_gPose = gPose; _objects = objects;
_changeCustomizeService = changeCustomizeService; _gPose = gPose;
_customizations = customizations; _changeCustomizeService = changeCustomizeService;
_condition = condition; _customizations = customizations;
_condition = condition;
Subscribe(); Subscribe();
} }
@ -212,7 +214,7 @@ public class StateListener : IDisposable
&& _manager.TryGetValue(identifier, out var state)) && _manager.TryGetValue(identifier, out var state))
{ {
HandleEquipSlot(actor, state, slot, ref armor.Value); HandleEquipSlot(actor, state, slot, ref armor.Value);
locked = state[slot, false] is StateChanged.Source.Ipc; locked = state[slot, ActorState.EquipField.Item] is StateChanged.Source.Ipc;
} }
_funModule.ApplyFun(actor, ref armor.Value, slot); _funModule.ApplyFun(actor, ref armor.Value, slot);
@ -223,6 +225,34 @@ public class StateListener : IDisposable
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); (_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
} }
private void OnCrestVisibilityUpdating(Model model, EquipSlot slot, Ref<bool> visible)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (slot == EquipSlot.BothHand)
{
var rootModel = actor.Model;
var (mainHand, offHand, _, _) = rootModel.GetWeapons(actor);
if (model == mainHand)
slot = EquipSlot.MainHand;
else if (model == offHand)
slot = EquipSlot.OffHand;
else
{
Glamourer.Log.Excessive($"Cannot identify weapon slot: got model {model.Address:X}, main hand is {mainHand.Address:X}, off hand is {offHand.Address:X}");
return;
}
}
if (actor.Identifier(_actors.AwaitedService, out var identifier)
&& _manager.TryGetValue(identifier, out var state))
{
HandleCrestVisibility(actor, state, slot, ref visible.Value);
}
}
private void OnMovedEquipment((EquipSlot, uint, StainId)[] items) private void OnMovedEquipment((EquipSlot, uint, StainId)[] items)
{ {
_objects.Update(); _objects.Update();
@ -235,11 +265,12 @@ public class StateListener : IDisposable
var currentItem = state.BaseData.Item(slot); var currentItem = state.BaseData.Item(slot);
var model = state.ModelData.Weapon(slot); var model = state.ModelData.Weapon(slot);
var current = currentItem.Weapon(state.BaseData.Stain(slot)); var current = currentItem.Weapon(state.BaseData.Stain(slot));
var crest = state.BaseData.Crest(slot);
if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem)) if (model.Value == current.Value || !_items.ItemService.AwaitedService.TryGetValue(item, EquipSlot.MainHand, out var changedItem))
continue; continue;
var changed = changedItem.Weapon(stain); 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[slot, ActorState.EquipField.Item] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
{ {
_manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game); _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
_manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game); _manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game);
@ -247,10 +278,10 @@ public class StateListener : IDisposable
{ {
case EquipSlot.MainHand: case EquipSlot.MainHand:
case EquipSlot.OffHand: case EquipSlot.OffHand:
_applier.ChangeWeapon(objects, slot, currentItem, stain); _applier.ChangeWeapon(objects, slot, currentItem, stain, crest);
break; break;
default: default:
_applier.ChangeArmor(objects, slot, current.ToArmor(), state[slot, false] is not StateChanged.Source.Ipc, _applier.ChangeArmor(objects, slot, current.ToArmor(), crest, state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
break; break;
} }
@ -284,15 +315,20 @@ public class StateListener : IDisposable
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later. // Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
case UpdateState.Transformed: break; case UpdateState.Transformed: break;
case UpdateState.Change: case UpdateState.Change:
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state[slot, ActorState.EquipField.Stain] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
if (state[slot, ActorState.EquipField.Crest] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
else
apply = true;
break; break;
case UpdateState.NoChange: case UpdateState.NoChange:
apply = true; apply = true;
@ -346,6 +382,7 @@ public class StateListener : IDisposable
var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant);
state.BaseData.SetItem(EquipSlot.Head, item); state.BaseData.SetItem(EquipSlot.Head, item);
state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain);
state.BaseData.SetCrest(EquipSlot.Head, actor.GetCrest(slot));
return UpdateState.Change; return UpdateState.Change;
} }
@ -374,6 +411,20 @@ public class StateListener : IDisposable
return change; return change;
} }
private UpdateState UpdateBaseCrest(Actor actor, ActorState state, EquipSlot 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> Handle a full equip slot update for base data and model data. </summary> /// <summary> Handle a full equip slot update for base data and model data. </summary>
private void HandleEquipSlot(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor) private void HandleEquipSlot(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor)
{ {
@ -383,12 +434,12 @@ public class StateListener : IDisposable
// Update model state if not on fixed design. // Update model state if not on fixed design.
case UpdateState.Change: case UpdateState.Change:
var apply = false; var apply = false;
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state[slot, ActorState.EquipField.Stain] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
@ -405,6 +456,24 @@ public class StateListener : IDisposable
} }
} }
private void HandleCrestVisibility(Actor actor, ActorState state, EquipSlot slot, ref bool visible)
{
switch (UpdateBaseCrest(actor, state, slot, visible))
{
case UpdateState.Change:
if (state[slot, ActorState.EquipField.Crest] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
else
visible = state.ModelData.Crest(slot);
break;
case UpdateState.NoChange:
case UpdateState.HatHack:
visible = state.ModelData.Crest(slot);
break;
case UpdateState.Transformed: break;
}
}
/// <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)
{ {
@ -415,6 +484,7 @@ public class StateListener : IDisposable
return UpdateState.Transformed; return UpdateState.Transformed;
} }
var crest = actor.GetCrest(slot);
var baseData = state.BaseData.Weapon(slot); var baseData = state.BaseData.Weapon(slot);
var change = UpdateState.NoChange; var change = UpdateState.NoChange;
@ -610,6 +680,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener); _slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
_crestVisibilityUpdating.Subscribe(OnCrestVisibilityUpdating, CrestVisibilityUpdating.Priority.StateListener);
_movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener); _movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener); _weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener); _visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
@ -623,6 +694,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
_slotUpdating.Unsubscribe(OnSlotUpdating); _slotUpdating.Unsubscribe(OnSlotUpdating);
_crestVisibilityUpdating.Unsubscribe(OnCrestVisibilityUpdating);
_movedEquipment.Unsubscribe(OnMovedEquipment); _movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading); _weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange); _visorState.Unsubscribe(OnVisorChange);

View file

@ -143,10 +143,12 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
ret.Customize = model.GetCustomize(); ret.Customize = model.GetCustomize();
// We can not use the head slot data from the draw object if the hat is hidden. // We can not use the head slot data from the draw object if the hat is hidden.
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head); var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant); var headCrest = ret.IsHatVisible() || ignoreHatState ? model.GetCrest(EquipSlot.Head) : actor.GetCrest(EquipSlot.Head);
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
ret.SetItem(EquipSlot.Head, headItem); ret.SetItem(EquipSlot.Head, headItem);
ret.SetStain(EquipSlot.Head, head.Stain); ret.SetStain(EquipSlot.Head, head.Stain);
ret.SetCrest(EquipSlot.Head, headCrest);
// The other slots can be used from the draw object. // The other slots can be used from the draw object.
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
@ -155,6 +157,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
var item = _items.Identify(slot, armor.Set, armor.Variant); var item = _items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain); ret.SetStain(slot, armor.Stain);
ret.SetCrest(slot, model.GetCrest(slot));
} }
// Weapons use the draw objects of the weapons, but require the game object either way. // Weapons use the draw objects of the weapons, but require the game object either way.
@ -174,6 +177,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
var item = _items.Identify(slot, armor.Set, armor.Variant); var item = _items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item); ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain); ret.SetStain(slot, armor.Stain);
ret.SetCrest(slot, actor.GetCrest(slot));
} }
main = actor.GetMainhand(); main = actor.GetMainhand();
@ -187,8 +191,10 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type); var offItem = _items.Identify(EquipSlot.OffHand, off.Set, off.Type, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem); ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain); ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetCrest(EquipSlot.MainHand, actor.GetCrest(EquipSlot.MainHand));
ret.SetItem(EquipSlot.OffHand, offItem); ret.SetItem(EquipSlot.OffHand, offItem);
ret.SetStain(EquipSlot.OffHand, off.Stain); ret.SetStain(EquipSlot.OffHand, off.Stain);
ret.SetCrest(EquipSlot.OffHand, actor.GetCrest(EquipSlot.OffHand));
// Wetness can technically only be set in GPose or via external tools. // Wetness can technically only be set in GPose or via external tools.
// It is only available in the game object. // It is only available in the game object.
@ -257,7 +263,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied)); _event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied));
} }
/// <summary> Change a single piece of equipment without stain. </summary> /// <summary> Change a single piece of equipment without stain or crest visibility. </summary>
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks> /// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0) public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0)
{ {
@ -274,21 +280,25 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_event.Invoke(type, source, state, actors, (old, item, slot)); _event.Invoke(type, source, state, actors, (old, item, slot));
} }
/// <summary> Change a single piece of equipment including stain. </summary> /// <summary> Change a single piece of equipment including stain and crest visibility. </summary>
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, uint key = 0) public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem? item, StainId? stain, bool? crest, StateChanged.Source source, uint key = 0)
{ {
if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key)) if (!_editor.ChangeEquip(state, slot, item, stain, crest, source, out var old, out var oldStain, out var oldCrest, key))
return; return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon; var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc) ? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc, : _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType)); (item ?? old).Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {(item ?? old).Name} ({(item ?? old).ItemId}), its stain from {oldStain.Id} to {(stain ?? oldStain).Id} and its crest visibility from {oldCrest} to {crest ?? oldCrest}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot)); if (item.HasValue)
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot)); _event.Invoke(type, source, state, actors, (old, item.Value, slot));
if (stain.HasValue)
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain.Value, slot));
if (crest.HasValue)
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (oldCrest, crest.Value, slot));
} }
/// <summary> Change only the stain of an equipment piece. </summary> /// <summary> Change only the stain of an equipment piece. </summary>
@ -304,6 +314,19 @@ 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 only the crest visibility of an equipment piece. </summary>
/// <remarks> Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeCrest(ActorState state, EquipSlot slot, bool crest, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
return;
var actors = _applier.ChangeCrest(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} crest visibility 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,15 +379,16 @@ 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) void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain, bool applyCrest)
{ {
var unused = (applyPiece, applyStain) switch var unused = (applyPiece, applyStain, applyCrest) switch
{ {
(false, false) => false, (false, false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key), (true, false, 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), (false, true, false) => _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 _, (false, false, true) => _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key),
out _, key), _ => _editor.ChangeEquip(state, slot, applyPiece ? design.DesignData.Item(slot) : null, applyStain ? design.DesignData.Stain(slot) : null, applyCrest ? design.DesignData.Crest(slot) : null, source, out _,
out _, out _, key),
}; };
} }
@ -392,7 +416,7 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
redraw |= applied.RequiresRedraw(); redraw |= applied.RequiresRedraw();
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), design.DoApplyCrest(slot));
} }
var actors = ApplyAll(state, redraw, false); var actors = ApplyAll(state, redraw, false);
@ -415,14 +439,14 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
_applier.ChangeCustomize(actors, state.ModelData.Customize); _applier.ChangeCustomize(actors, state.ModelData.Customize);
foreach (var slot in EquipSlotExtensions.EqdpSlots) 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.ModelData.Crest(slot), state[slot, ActorState.EquipField.Item] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
} }
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors; var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
_applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand)); _applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand), state.ModelData.Crest(EquipSlot.MainHand));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
_applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand)); _applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand), state.ModelData.Crest(EquipSlot.OffHand));
} }
if (state.ModelData.IsHuman) if (state.ModelData.IsHuman)
@ -450,8 +474,9 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
state[slot, true] = StateChanged.Source.Game; state[slot, ActorState.EquipField.Stain] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game; state[slot, ActorState.EquipField.Item] = StateChanged.Source.Game;
state[slot, ActorState.EquipField.Crest] = StateChanged.Source.Game;
} }
foreach (var type in Enum.GetValues<ActorState.MetaIndex>()) foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
@ -478,15 +503,21 @@ public class StateManager : IReadOnlyDictionary<ActorIdentifier, ActorState>
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
if (state[slot, true] is StateChanged.Source.Fixed) if (state[slot, ActorState.EquipField.Crest] is StateChanged.Source.Fixed)
{ {
state[slot, true] = StateChanged.Source.Game; state[slot, ActorState.EquipField.Crest] = StateChanged.Source.Game;
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
}
if (state[slot, ActorState.EquipField.Stain] is StateChanged.Source.Fixed)
{
state[slot, ActorState.EquipField.Stain] = StateChanged.Source.Game;
state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
} }
if (state[slot, false] is StateChanged.Source.Fixed) if (state[slot, ActorState.EquipField.Item] is StateChanged.Source.Fixed)
{ {
state[slot, false] = StateChanged.Source.Game; state[slot, ActorState.EquipField.Item] = StateChanged.Source.Game;
state.ModelData.SetItem(slot, state.BaseData.Item(slot)); state.ModelData.SetItem(slot, state.BaseData.Item(slot));
} }
} }