Current state.

This commit is contained in:
Ottermandias 2024-07-15 15:19:51 +02:00
parent 7a602d6ec5
commit 81059411e5
42 changed files with 913 additions and 320 deletions

@ -1 +1 @@
Subproject commit 4aaece34289d64363bc32aaa8fe52c8e7d3dce32 Subproject commit ca003395306791b9e595683c47824b4718385311

View file

@ -35,6 +35,7 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
state = null; state = null;
return GlamourerApiEc.ActorNotFound; return GlamourerApiEc.ActorNotFound;
} }
stateManager.TryGetValue(identifier, out state); stateManager.TryGetValue(identifier, out state);
return GlamourerApiEc.Success; return GlamourerApiEc.Success;
} }
@ -54,12 +55,10 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags) internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags)
=> (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch => (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch
{ {
ApplyFlag.Equipment => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, 0, CrestExtensions.All, 0), ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment),
ApplyFlag.Customization => design.TemporarilyRestrictApplication(0, CustomizeFlagExtensions.All, 0, ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations),
CustomizeParameterExtensions.All), ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.All),
ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, _ => design.TemporarilyRestrictApplication(ApplicationCollection.None),
CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All),
_ => design.TemporarilyRestrictApplication(0, 0, 0, 0),
}; };
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]

View file

@ -28,8 +28,7 @@ public static class ApplicationTypeExtensions
(ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
]; ];
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat( public static ApplicationCollection Collection(this ApplicationType type)
this ApplicationType type, IDesignStandIn designStandIn)
{ {
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0) var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0) | (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
@ -37,18 +36,18 @@ public static class ApplicationTypeExtensions
| (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0); | (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0);
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0; var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0; var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0; var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0) var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0) | (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0); | (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0;
if (designStandIn is not DesignBase design) return new ApplicationCollection(equipFlags, bonusFlags, customizeFlags, crestFlags, parameterFlags, metaFlags);
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta);
} }
public static ApplicationCollection ApplyWhat(this ApplicationType type, IDesignStandIn designStandIn)
=> designStandIn is not DesignBase design ? type.Collection() : type.Collection().Restrict(design.Application);
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand; public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet; public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet;
public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger; public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger;

View file

@ -61,6 +61,6 @@ public class AutoDesign
return ret; return ret;
} }
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat() public ApplicationCollection ApplyWhat()
=> Type.ApplyWhat(Design); => Type.ApplyWhat(Design);
} }

View file

@ -0,0 +1,61 @@
using Glamourer.GameData;
using ImGuiNET;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs;
public record struct ApplicationCollection(
EquipFlag Equip,
BonusItemFlag BonusItem,
CustomizeFlag Customize,
CrestFlag Crest,
CustomizeParameterFlag Parameters,
MetaFlag Meta)
{
public static readonly ApplicationCollection All = new(EquipFlagExtensions.All, BonusExtensions.All,
CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All);
public static readonly ApplicationCollection None = new(0, 0, 0, 0, 0, 0);
public static readonly ApplicationCollection Equipment = new(EquipFlagExtensions.All, BonusExtensions.All,
0, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
public static readonly ApplicationCollection Customizations = new(0, 0, CustomizeFlagExtensions.AllRelevant, 0,
CustomizeParameterExtensions.All, MetaFlag.Wetness);
public static readonly ApplicationCollection Default = new(EquipFlagExtensions.All, BonusExtensions.All,
CustomizeFlagExtensions.AllRelevant, CrestExtensions.AllRelevant, 0, MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState);
public static ApplicationCollection FromKeys()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{
(false, false) => All,
(true, true) => All,
(true, false) => Equipment,
(false, true) => Customizations,
};
public void RemoveEquip()
{
Equip = 0;
BonusItem = 0;
Crest = 0;
Meta &= ~(MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState);
}
public void RemoveCustomize()
{
Customize = 0;
Parameters = 0;
Meta &= MetaFlag.Wetness;
}
public ApplicationCollection Restrict(ApplicationCollection old)
=> new(old.Equip & Equip, old.BonusItem & BonusItem, old.Customize & Customize, old.Crest & Crest,
old.Parameters & Parameters, old.Meta & Meta);
public ApplicationCollection CloneSecure()
=> new(Equip & EquipFlagExtensions.All, BonusItem & BonusExtensions.All,
(Customize & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType, Crest & CrestExtensions.AllRelevant,
Parameters & CustomizeParameterExtensions.All, Meta & MetaExtensions.All);
}

View file

@ -5,16 +5,9 @@ using Penumbra.GameData.Enums;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public readonly struct ApplicationRules( public readonly struct ApplicationRules(ApplicationCollection application, bool materials)
EquipFlag equip,
CustomizeFlag customize,
CrestFlag crest,
CustomizeParameterFlag parameters,
MetaFlag meta,
bool materials)
{ {
public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, public static readonly ApplicationRules All = new(ApplicationCollection.All, true);
CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All, true);
public static ApplicationRules FromModifiers(ActorState state) public static ApplicationRules FromModifiers(ActorState state)
=> FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); => FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
@ -23,54 +16,43 @@ public readonly struct ApplicationRules(
=> NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift); => NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
public static ApplicationRules AllButParameters(ActorState state) public static ApplicationRules AllButParameters(ActorState state)
=> new(All.Equip, All.Customize, All.Crest, ComputeParameters(state.ModelData, state.BaseData, All.Parameters), All.Meta, true); => new(ApplicationCollection.All with { Parameters = ComputeParameters(state.ModelData, state.BaseData, All.Parameters) }, true);
public static ApplicationRules AllWithConfig(Configuration config) public static ApplicationRules AllWithConfig(Configuration config)
=> new(All.Equip, All.Customize, All.Crest, config.UseAdvancedParameters ? All.Parameters : 0, All.Meta, config.UseAdvancedDyes); => new(ApplicationCollection.All with { Parameters = config.UseAdvancedParameters ? All.Parameters : 0 }, config.UseAdvancedDyes);
public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift) public static ApplicationRules NpcFromModifiers(bool ctrl, bool shift)
=> new(ctrl || !shift ? EquipFlagExtensions.All : 0, {
!ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0, var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
0, var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
0, var visor = equip != 0 ? MetaFlag.VisorState : 0;
ctrl || !shift ? MetaFlag.VisorState : 0, false); return new ApplicationRules(new ApplicationCollection(equip, 0, customize, 0, 0, visor), false);
}
public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift) public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift)
{ {
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0; var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0; var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
var bonus = equip == 0 ? 0 : BonusExtensions.All;
var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant; var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant;
var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All; var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All;
var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0; var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0;
if (equip != 0) if (equip != 0)
meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState; meta |= MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState;
return new ApplicationRules(equip, customize, crest, ComputeParameters(state.ModelData, state.BaseData, parameters), meta, equip != 0); var collection = new ApplicationCollection(equip, bonus, customize, crest,
ComputeParameters(state.ModelData, state.BaseData, parameters), meta);
return new ApplicationRules(collection, equip != 0);
} }
public void Apply(DesignBase design) public void Apply(DesignBase design)
{ => design.Application = application;
design.ApplyEquip = Equip;
design.ApplyCustomize = Customize;
design.ApplyCrest = Crest;
design.ApplyParameters = Parameters;
design.ApplyMeta = Meta;
}
public EquipFlag Equip public EquipFlag Equip
=> equip & EquipFlagExtensions.All; => application.Equip & EquipFlagExtensions.All;
public CustomizeFlag Customize
=> customize & CustomizeFlagExtensions.AllRelevant;
public CrestFlag Crest
=> crest & CrestExtensions.AllRelevant;
public CustomizeParameterFlag Parameters public CustomizeParameterFlag Parameters
=> parameters & CustomizeParameterExtensions.All; => application.Parameters & CustomizeParameterExtensions.All;
public MetaFlag Meta
=> meta & MetaExtensions.All;
public bool Materials public bool Materials
=> materials; => materials;

View file

@ -44,8 +44,8 @@ public class DesignBase
{ {
_designData = designData; _designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
ApplyEquip = equipFlags & EquipFlagExtensions.All; Application.Equip = equipFlags & EquipFlagExtensions.All;
ApplyMeta = 0; Application.Meta = 0;
CustomizeSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }
@ -54,11 +54,7 @@ public class DesignBase
_designData = clone._designData; _designData = clone._designData;
_materials = clone._materials.Clone(); _materials = clone._materials.Clone();
CustomizeSet = clone.CustomizeSet; CustomizeSet = clone.CustomizeSet;
ApplyCustomize = clone.ApplyCustomizeRaw; Application = clone.Application.CloneSecure();
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All;
ApplyCrest = clone.ApplyCrest & CrestExtensions.All;
ApplyMeta = clone.ApplyMeta & MetaExtensions.All;
} }
/// <summary> Ensure that the customization set is updated when the design data changes. </summary> /// <summary> Ensure that the customization set is updated when the design data changes. </summary>
@ -70,26 +66,19 @@ public class DesignBase
#region Application Data #region Application Data
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
public CustomizeSet CustomizeSet { get; private set; } public CustomizeSet CustomizeSet { get; private set; }
public CustomizeParameterFlag ApplyParameters { get; internal set; } public ApplicationCollection Application = ApplicationCollection.Default;
internal CustomizeFlag ApplyCustomize internal CustomizeFlag ApplyCustomize
{ {
get => _applyCustomize.FixApplication(CustomizeSet); get => Application.Customize.FixApplication(CustomizeSet);
set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType; set => Application.Customize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
} }
internal CustomizeFlag ApplyCustomizeExcludingBodyType internal CustomizeFlag ApplyCustomizeExcludingBodyType
=> _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType; => Application.Customize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType;
internal CustomizeFlag ApplyCustomizeRaw
=> _applyCustomize;
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
private bool _writeProtected; private bool _writeProtected;
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize) public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
@ -103,18 +92,18 @@ public class DesignBase
} }
public bool DoApplyMeta(MetaIndex index) public bool DoApplyMeta(MetaIndex index)
=> ApplyMeta.HasFlag(index.ToFlag()); => Application.Meta.HasFlag(index.ToFlag());
public bool WriteProtected() public bool WriteProtected()
=> _writeProtected; => _writeProtected;
public bool SetApplyMeta(MetaIndex index, bool value) public bool SetApplyMeta(MetaIndex index, bool value)
{ {
var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag(); var newFlag = value ? Application.Meta | index.ToFlag() : Application.Meta & ~index.ToFlag();
if (newFlag == ApplyMeta) if (newFlag == Application.Meta)
return false; return false;
ApplyMeta = newFlag; Application.Meta = newFlag;
return true; return true;
} }
@ -128,103 +117,100 @@ public class DesignBase
} }
public bool DoApplyEquip(EquipSlot slot) public bool DoApplyEquip(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToFlag()); => Application.Equip.HasFlag(slot.ToFlag());
public bool DoApplyStain(EquipSlot slot) public bool DoApplyStain(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToStainFlag()); => Application.Equip.HasFlag(slot.ToStainFlag());
public bool DoApplyCustomize(CustomizeIndex idx) public bool DoApplyCustomize(CustomizeIndex idx)
=> ApplyCustomize.HasFlag(idx.ToFlag()); => Application.Customize.HasFlag(idx.ToFlag());
public bool DoApplyCrest(CrestFlag slot) public bool DoApplyCrest(CrestFlag slot)
=> ApplyCrest.HasFlag(slot); => Application.Crest.HasFlag(slot);
public bool DoApplyParameter(CustomizeParameterFlag flag) public bool DoApplyParameter(CustomizeParameterFlag flag)
=> ApplyParameters.HasFlag(flag); => Application.Parameters.HasFlag(flag);
public bool DoApplyBonusItem(BonusItemFlag slot)
=> Application.BonusItem.HasFlag(slot);
internal bool SetApplyEquip(EquipSlot slot, bool value) internal bool SetApplyEquip(EquipSlot slot, bool value)
{ {
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); var newValue = value ? Application.Equip | slot.ToFlag() : Application.Equip & ~slot.ToFlag();
if (newValue == ApplyEquip) if (newValue == Application.Equip)
return false; return false;
ApplyEquip = newValue; Application.Equip = newValue;
return true;
}
internal bool SetApplyBonusItem(BonusItemFlag slot, bool value)
{
var newValue = value ? Application.BonusItem | slot : Application.BonusItem & ~slot;
if (newValue == Application.BonusItem)
return false;
Application.BonusItem = newValue;
return true; return true;
} }
internal bool SetApplyStain(EquipSlot slot, bool value) internal bool SetApplyStain(EquipSlot slot, bool value)
{ {
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag(); var newValue = value ? Application.Equip | slot.ToStainFlag() : Application.Equip & ~slot.ToStainFlag();
if (newValue == ApplyEquip) if (newValue == Application.Equip)
return false; return false;
ApplyEquip = newValue; Application.Equip = newValue;
return true; 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 ? Application.Customize | idx.ToFlag() : Application.Customize & ~idx.ToFlag();
if (newValue == _applyCustomize) if (newValue == Application.Customize)
return false; return false;
_applyCustomize = newValue; Application.Customize = newValue;
return true; return true;
} }
internal bool SetApplyCrest(CrestFlag slot, bool value) internal bool SetApplyCrest(CrestFlag slot, bool value)
{ {
var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot; var newValue = value ? Application.Crest | slot : Application.Crest & ~slot;
if (newValue == ApplyCrest) if (newValue == Application.Crest)
return false; return false;
ApplyCrest = newValue; Application.Crest = newValue;
return true; return true;
} }
internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value) internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value)
{ {
var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag; var newValue = value ? Application.Parameters | flag : Application.Parameters & ~flag;
if (newValue == ApplyParameters) if (newValue == Application.Parameters)
return false; return false;
ApplyParameters = newValue; Application.Parameters = newValue;
return true; return true;
} }
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions)
CustomizeParameterFlag parameterFlags) => new(this, restrictions);
=> new(this, equipFlags, customizeFlags, crestFlags, parameterFlags);
internal readonly struct FlagRestrictionResetter : IDisposable internal readonly struct FlagRestrictionResetter : IDisposable
{ {
private readonly DesignBase _design; private readonly DesignBase _design;
private readonly EquipFlag _oldEquipFlags; private readonly ApplicationCollection _oldFlags;
private readonly CustomizeFlag _oldCustomizeFlags;
private readonly CrestFlag _oldCrestFlags;
private readonly CustomizeParameterFlag _oldParameterFlags;
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, public FlagRestrictionResetter(DesignBase d, ApplicationCollection restrictions)
CustomizeParameterFlag parameterFlags)
{ {
_design = d; _design = d;
_oldEquipFlags = d.ApplyEquip; _oldFlags = d.Application;
_oldCustomizeFlags = d.ApplyCustomizeRaw; _design.Application = restrictions.Restrict(_oldFlags);
_oldCrestFlags = d.ApplyCrest;
_oldParameterFlags = d.ApplyParameters;
d.ApplyEquip &= equipFlags;
d.ApplyCustomize &= customizeFlags;
d.ApplyCrest &= crestFlags;
d.ApplyParameters &= parameterFlags;
} }
public void Dispose() public void Dispose()
{ => _design.Application = _oldFlags;
_design.ApplyEquip = _oldEquipFlags;
_design.ApplyCustomize = _oldCustomizeFlags;
_design.ApplyCrest = _oldCrestFlags;
_design.ApplyParameters = _oldParameterFlags;
}
} }
private CustomizeSet SetCustomizationSet(CustomizeService customize) private CustomizeSet SetCustomizationSet(CustomizeService customize)
@ -285,6 +271,22 @@ public class DesignBase
}); });
} }
protected JObject SerializeBonusItems()
{
var ret = new JObject();
foreach (var slot in BonusExtensions.AllFlags)
{
var item = _designData.BonusItem(slot);
ret[slot.ToString()] = new JObject()
{
["BonusId"] = item.ModelId.Id,
["Apply"] = DoApplyBonusItem(slot),
};
}
return ret;
}
protected JObject SerializeCustomize() protected JObject SerializeCustomize()
{ {
var ret = new JObject() var ret = new JObject()
@ -299,7 +301,7 @@ public class DesignBase
ret[idx.ToString()] = new JObject() ret[idx.ToString()] = new JObject()
{ {
["Value"] = customize[idx].Value, ["Value"] = customize[idx].Value,
["Apply"] = ApplyCustomizeRaw.HasFlag(idx.ToFlag()), ["Apply"] = Application.Customize.HasFlag(idx.ToFlag()),
}; };
} }
else else
@ -382,7 +384,7 @@ public class DesignBase
{ {
var k = uint.Parse(key.Name, NumberStyles.HexNumber); var k = uint.Parse(key.Name, NumberStyles.HexNumber);
var v = value.ToObject<MaterialValueDesign>(); var v = value.ToObject<MaterialValueDesign>();
if (!MaterialValueIndex.FromKey(k, out var idx)) if (!MaterialValueIndex.FromKey(k, out _))
{ {
Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.", Glamourer.Messager.NotificationMessage($"Invalid material value key {k} for design {name}, skipped.",
NotificationType.Warning); NotificationType.Warning);
@ -429,7 +431,7 @@ public class DesignBase
{ {
if (parameters == null) if (parameters == null)
{ {
design.ApplyParameters = 0; design.Application.Parameters = 0;
design.GetDesignDataRef().Parameters = default; design.GetDesignDataRef().Parameters = default;
return; return;
} }
@ -490,7 +492,7 @@ public class DesignBase
return true; return true;
} }
design.ApplyParameters &= ~flag; design.Application.Parameters &= ~flag;
design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero; design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero;
return false; return false;
} }
@ -669,11 +671,12 @@ public class DesignBase
{ {
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags, _designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
out var writeProtected, out var applyMeta); out var writeProtected, out var applyMeta);
ApplyEquip = equipFlags; Application.Equip = equipFlags;
ApplyCustomize = customizeFlags; ApplyCustomize = customizeFlags;
ApplyParameters = 0; Application.Parameters = 0;
ApplyCrest = 0; Application.Crest = 0;
ApplyMeta = applyMeta; Application.Meta = applyMeta;
Application.BonusItem = 0;
SetWriteProtected(writeProtected); SetWriteProtected(writeProtected);
CustomizeSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }

View file

@ -270,7 +270,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
public static uint AutoColor(DesignBase design) public static uint AutoColor(DesignBase design)
{ {
var customize = design.ApplyCustomizeExcludingBodyType == 0; var customize = design.ApplyCustomizeExcludingBodyType == 0;
var equip = design.ApplyEquip == 0; var equip = design.Application.Equip == 0;
return (customize, equip) switch return (customize, equip) switch
{ {
(true, true) => ColorId.StateDesign.Value(), (true, true) => ColorId.StateDesign.Value(),

View file

@ -72,16 +72,11 @@ public class DesignConverter(
? Design.LoadDesign(_customize, _items, _linkLoader, jObject) ? Design.LoadDesign(_customize, _items, _linkLoader, jObject)
: DesignBase.LoadDesignBase(_customize, _items, jObject); : DesignBase.LoadDesignBase(_customize, _items, jObject);
ret.SetApplyMeta(MetaIndex.Wetness, customize);
if (!customize) if (!customize)
ret.ApplyCustomize = 0; ret.Application.RemoveCustomize();
if (!equip) if (!equip)
{ ret.Application.RemoveEquip();
ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
}
return ret; return ret;
} }
@ -155,16 +150,11 @@ public class DesignConverter(
return null; return null;
} }
ret.SetApplyMeta(MetaIndex.Wetness, customize);
if (!customize) if (!customize)
ret.ApplyCustomize = 0; ret.Application.RemoveCustomize();
if (!equip) if (!equip)
{ ret.Application.RemoveEquip();
ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
}
return ret; return ret;
} }

View file

@ -9,7 +9,10 @@ namespace Glamourer.Designs;
public unsafe struct DesignData public unsafe struct DesignData
{ {
public const int EquipmentByteSize = 10 * CharacterArmor.Size; public const int NumEquipment = 10;
public const int EquipmentByteSize = NumEquipment * CharacterArmor.Size;
public const int NumBonusItems = 1;
public const int NumWeapons = 2;
private string _nameHead = string.Empty; private string _nameHead = string.Empty;
private string _nameBody = string.Empty; private string _nameBody = string.Empty;
@ -23,10 +26,14 @@ public unsafe struct DesignData
private string _nameLFinger = string.Empty; private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty; private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty; private string _nameOffhand = string.Empty;
private string _nameFaceWear = string.Empty; private string _nameGlasses = string.Empty;
private fixed uint _itemIds[12];
private fixed uint _iconIds[12]; private fixed uint _itemIds[NumEquipment + NumWeapons];
private fixed byte _equipmentBytes[EquipmentByteSize + 16]; private fixed uint _iconIds[NumEquipment + NumWeapons + NumBonusItems];
private fixed byte _equipmentBytes[EquipmentByteSize + NumWeapons * CharacterWeapon.Size];
private fixed ushort _bonusIds[NumBonusItems];
private fixed ushort _bonusModelIds[NumBonusItems];
private fixed byte _bonusVariants[NumBonusItems];
public CustomizeParameterData Parameters; public CustomizeParameterData Parameters;
public CustomizeArray Customize = CustomizeArray.Default; public CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId; public uint ModelId;
@ -52,7 +59,7 @@ public unsafe struct DesignData
|| name.IsContained(_nameLFinger) || name.IsContained(_nameLFinger)
|| name.IsContained(_nameMainhand) || name.IsContained(_nameMainhand)
|| name.IsContained(_nameOffhand) || name.IsContained(_nameOffhand)
|| name.IsContained(_nameFaceWear); || name.IsContained(_nameGlasses);
public readonly StainIds Stain(EquipSlot slot) public readonly StainIds Stain(EquipSlot slot)
{ {
@ -101,6 +108,15 @@ public unsafe struct DesignData
} }
} }
public readonly BonusItem BonusItem(BonusItemFlag slot)
=> slot switch
{
// @formatter:off
BonusItemFlag.Glasses => new BonusItem(_nameGlasses, _iconIds[12], _bonusIds[0], _bonusModelIds[0], _bonusVariants[0], BonusItemFlag.Glasses),
_ => Penumbra.GameData.Structs.BonusItem.Empty(slot),
// @formatter:on
};
public readonly CharacterArmor Armor(EquipSlot slot) public readonly CharacterArmor Armor(EquipSlot slot)
{ {
fixed (byte* ptr = _equipmentBytes) fixed (byte* ptr = _equipmentBytes)
@ -134,7 +150,7 @@ public unsafe struct DesignData
public bool SetItem(EquipSlot slot, EquipItem item) public bool SetItem(EquipSlot slot, EquipItem item)
{ {
var index = slot.ToIndex(); var index = slot.ToIndex();
if (index > 11) if (index > NumEquipment + NumWeapons)
return false; return false;
_itemIds[index] = item.ItemId.Id; _itemIds[index] = item.ItemId.Id;
@ -173,6 +189,25 @@ public unsafe struct DesignData
return true; return true;
} }
public bool SetBonusItem(BonusItemFlag slot, BonusItem item)
{
var index = slot.ToIndex();
if (index > NumBonusItems)
return false;
_iconIds[NumEquipment + NumWeapons + index] = item.Icon.Id;
_bonusIds[index] = item.Id.Id;
_bonusModelIds[index] = item.ModelId.Id;
_bonusVariants[index] = item.Variant.Id;
switch (index)
{
case 0:
_nameGlasses = item.Name;
return true;
default: return false;
}
}
public bool SetStain(EquipSlot slot, StainIds stains) public bool SetStain(EquipSlot slot, StainIds stains)
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
@ -323,7 +358,7 @@ public unsafe struct DesignData
_nameWrists = string.Empty; _nameWrists = string.Empty;
_nameRFinger = string.Empty; _nameRFinger = string.Empty;
_nameLFinger = string.Empty; _nameLFinger = string.Empty;
_nameFaceWear = string.Empty; _nameGlasses = string.Empty;
return true; return true;
} }

View file

@ -165,6 +165,11 @@ public class DesignEditor(
} }
} }
public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default)
{
}
/// <inheritdoc/> /// <inheritdoc/>
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default) public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings _ = default)
{ {

View file

@ -347,6 +347,18 @@ public sealed class DesignManager : DesignEditor
DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot); DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
} }
/// <summary> Change whether to apply a specific equipment piece. </summary>
public void ChangeApplyBonusItem(Design design, BonusItemFlag slot, bool value)
{
if (!design.SetApplyBonusItem(slot, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of {slot} bonus item to {value}.");
DesignChanged.Invoke(DesignChanged.Type.ApplyBonus, design, slot);
}
/// <summary> Change whether to apply a specific stain. </summary> /// <summary> Change whether to apply a specific stain. </summary>
public void ChangeApplyStain(Design design, EquipSlot slot, bool value) public void ChangeApplyStain(Design design, EquipSlot slot, bool value)
{ {

View file

@ -64,6 +64,9 @@ public interface IDesignEditor
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
=> ChangeEquip(data, slot, item, null, settings); => ChangeEquip(data, slot, item, null, settings);
/// <summary> Change a bonus item. </summary>
public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default);
/// <summary> Change the stain for any equipment piece. </summary> /// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default) public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stains, settings); => ChangeEquip(data, slot, null, stains, settings);

View file

@ -42,14 +42,15 @@ public class DesignMerger(
if (!data.IsHuman) if (!data.IsHuman)
continue; continue;
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design); var collection = type.ApplyWhat(design);
ReduceMeta(data, applyMeta, ret, source); ReduceMeta(data, collection.Meta, ret, source);
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership, startBodyType); ReduceCustomize(data, collection.Customize, ref fixFlags, ret, source, respectOwnership, startBodyType);
ReduceEquip(data, equipFlags, ret, source, respectOwnership); ReduceEquip(data, collection.Equip, ret, source, respectOwnership);
ReduceMainhands(data, jobs, equipFlags, ret, source, respectOwnership); ReduceBonusItems(data, collection.BonusItem, ret, source, respectOwnership);
ReduceOffhands(data, jobs, equipFlags, ret, source, respectOwnership); ReduceMainhands(data, jobs, collection.Equip, ret, source, respectOwnership);
ReduceCrests(data, crestFlags, ret, source); ReduceOffhands(data, jobs, collection.Equip, ret, source, respectOwnership);
ReduceParameters(data, parameterFlags, ret, source); ReduceCrests(data, collection.Crest, ret, source);
ReduceParameters(data, collection.Parameters, ret, source);
ReduceMods(design as Design, ret, modAssociations); ReduceMods(design as Design, ret, modAssociations);
if (type.HasFlag(ApplicationType.GearCustomization)) if (type.HasFlag(ApplicationType.GearCustomization))
ReduceMaterials(design, ret); ReduceMaterials(design, ret);
@ -83,7 +84,7 @@ public class DesignMerger(
private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source) private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source)
{ {
applyMeta &= ~ret.Design.ApplyMeta; applyMeta &= ~ret.Design.Application.Meta;
if (applyMeta == 0) if (applyMeta == 0)
return; return;
@ -100,7 +101,7 @@ public class DesignMerger(
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source) private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source)
{ {
crestFlags &= ~ret.Design.ApplyCrest; crestFlags &= ~ret.Design.Application.Crest;
if (crestFlags == 0) if (crestFlags == 0)
return; return;
@ -118,7 +119,7 @@ public class DesignMerger(
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret, private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
StateSource source) StateSource source)
{ {
parameterFlags &= ~ret.Design.ApplyParameters; parameterFlags &= ~ret.Design.Application.Parameters;
if (parameterFlags == 0) if (parameterFlags == 0)
return; return;
@ -136,7 +137,7 @@ public class DesignMerger(
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
bool respectOwnership) bool respectOwnership)
{ {
equipFlags &= ~ret.Design.ApplyEquip; equipFlags &= ~ret.Design.Application.Equip;
if (equipFlags == 0) if (equipFlags == 0)
return; return;
@ -174,6 +175,22 @@ public class DesignMerger(
} }
} }
private void ReduceBonusItems(in DesignData design, BonusItemFlag bonusItems, MergedDesign ret, StateSource source, bool respectOwnership)
{
bonusItems &= ~ret.Design.Application.BonusItem;
if (bonusItems == 0)
return;
foreach (var slot in BonusExtensions.AllFlags.Where(b => bonusItems.HasFlag(b)))
{
var item = design.BonusItem(slot);
if (!respectOwnership || true) // TODO: maybe check unlocks
ret.Design.GetDesignDataRef().SetBonusItem(slot, item);
ret.Design.SetApplyBonusItem(slot, true);
ret.Sources[slot] = source;
}
}
private void ReduceMainhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source, private void ReduceMainhands(in DesignData design, JobFlag allowedJobs, EquipFlag equipFlags, MergedDesign ret, StateSource source,
bool respectOwnership) bool respectOwnership)
{ {

View file

@ -65,11 +65,7 @@ public sealed class MergedDesign
public MergedDesign(DesignManager designManager) public MergedDesign(DesignManager designManager)
{ {
Design = designManager.CreateTemporary(); Design = designManager.CreateTemporary();
Design.ApplyEquip = 0; Design.Application = ApplicationCollection.None;
Design.ApplyCustomize = 0;
Design.ApplyCrest = 0;
Design.ApplyParameters = 0;
Design.ApplyMeta = 0;
} }
public MergedDesign(DesignBase design) public MergedDesign(DesignBase design)

View file

@ -15,7 +15,7 @@ namespace Glamourer.Events;
/// </list> /// </list>
/// </summary> /// </summary>
public sealed class BonusSlotUpdating() public sealed class BonusSlotUpdating()
: EventWrapperRef34<Model, BonusEquipFlag, CharacterArmor, ulong, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating)) : EventWrapperRef34<Model, BonusItemFlag, CharacterArmor, ulong, BonusSlotUpdating.Priority>(nameof(BonusSlotUpdating))
{ {
public enum Priority public enum Priority
{ {

View file

@ -89,6 +89,9 @@ public sealed class DesignChanged()
/// <summary> An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. </summary> /// <summary> An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. </summary>
ApplyEquip, ApplyEquip,
/// <summary> An existing design changed whether a specific bonus item is applied. Data is the slot of the item [BonusItemFlag]. </summary>
ApplyBonus,
/// <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,

View file

@ -179,8 +179,7 @@ public sealed class DesignQuickBar : Window, IDisposable
return; return;
} }
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks); _stateManager.ApplyDesign(state, design, ApplySettings.ManualWithLinks);
} }

View file

@ -0,0 +1,57 @@
using Glamourer.Designs;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public struct BonusDrawData(BonusItemFlag slot, in DesignData designData)
{
private IDesignEditor _editor;
private object _object;
public readonly BonusItemFlag Slot = slot;
public bool Locked;
public bool DisplayApplication;
public bool AllowRevert;
public readonly bool IsDesign
=> _object is Design;
public readonly bool IsState
=> _object is ActorState;
public readonly void SetItem(BonusItem item)
=> _editor.ChangeBonusItem(_object, Slot, item, ApplySettings.Manual);
public readonly void SetApplyItem(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyBonusItem(design, Slot, value);
}
public BonusItem CurrentItem = designData.BonusItem(slot);
public BonusItem GameItem = default;
public bool CurrentApply;
public static BonusDrawData FromDesign(DesignManager manager, Design design, BonusItemFlag slot)
=> new(slot, design.DesignData)
{
_editor = manager,
_object = design,
CurrentApply = design.DoApplyBonusItem(slot),
Locked = design.WriteProtected(),
DisplayApplication = true,
};
public static BonusDrawData FromState(StateManager manager, ActorState state, BonusItemFlag slot)
=> new(slot, state.ModelData)
{
_editor = manager,
_object = state,
Locked = state.IsLocked,
DisplayApplication = false,
GameItem = state.BaseData.BonusItem(slot),
AllowRevert = true,
};
}

View file

@ -0,0 +1,123 @@
using Dalamud.Plugin.Services;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public sealed class BonusItemCombo : FilterComboCache<BonusItem>
{
private readonly FavoriteManager _favorites;
public readonly string Label;
private BonusItemId _currentItem;
private float _innerWidth;
public PrimaryId CustomSetId { get; private set; }
public Variant CustomVariant { get; private set; }
public BonusItemCombo(IDataManager gameData, ItemManager items, BonusItemFlag slot, Logger log, FavoriteManager favorites)
: base(() => GetItems(favorites, items, slot), MouseWheelType.Control, log)
{
_favorites = favorites;
Label = GetLabel(gameData, slot);
_currentItem = 0;
SearchByParts = true;
}
protected override void DrawList(float width, float itemHeight)
{
base.DrawList(width, itemHeight);
if (NewSelection != null && Items.Count > NewSelection.Value)
CurrentSelection = Items[NewSelection.Value];
}
protected override int UpdateCurrentSelected(int currentSelected)
{
if (CurrentSelection.Id == _currentItem)
return currentSelected;
CurrentSelectionIdx = Items.IndexOf(i => i.Id == _currentItem);
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
return base.UpdateCurrentSelected(CurrentSelectionIdx);
}
public bool Draw(string previewName, BonusItemId previewIdx, float width, float innerWidth)
{
_innerWidth = innerWidth;
_currentItem = previewIdx;
CustomVariant = 0;
return Draw($"##{Label}", previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
}
protected override float GetFilterWidth()
=> _innerWidth - 2 * ImGui.GetStyle().FramePadding.X;
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var obj = Items[globalIdx];
var name = ToString(obj);
if (UiHelpers.DrawFavoriteStar(_favorites, obj) && CurrentSelectionIdx == globalIdx)
{
CurrentSelectionIdx = -1;
_currentItem = obj.Id;
CurrentSelection = default;
}
ImGui.SameLine();
var ret = ImGui.Selectable(name, selected);
ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.Variant.Id})");
return ret;
}
protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString());
protected override string ToString(BonusItem obj)
=> obj.Name;
private static string GetLabel(IDataManager gameData, BonusItemFlag slot)
{
var sheet = gameData.GetExcelSheet<Addon>()!;
return slot switch
{
BonusItemFlag.Glasses => sheet.GetRow(16050)?.Text.ToString() ?? "Facewear",
BonusItemFlag.UnkSlot => sheet.GetRow(16051)?.Text.ToString() ?? "Facewear",
_ => string.Empty,
};
}
private static List<BonusItem> GetItems(FavoriteManager favorites, ItemManager items, BonusItemFlag slot)
{
var nothing = BonusItem.Empty(slot);
if (slot is not BonusItemFlag.Glasses)
return [nothing];
return items.DictBonusItems.Values.OrderByDescending(favorites.Contains).ThenBy(i => i.Id.Id).Prepend(nothing).ToList();
}
protected override void OnClosePopup()
{
// If holding control while the popup closes, try to parse the input as a full pair of set id and variant, and set a custom item for that.
if (!ImGui.GetIO().KeyCtrl)
return;
var split = Filter.Text.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (split.Length != 2 || !ushort.TryParse(split[0], out var setId) || !byte.TryParse(split[1], out var variant))
return;
CustomSetId = setId;
CustomVariant = variant;
}
}

View file

@ -24,6 +24,7 @@ public class EquipmentDrawer
private readonly GlamourerColorCombo _stainCombo; private readonly GlamourerColorCombo _stainCombo;
private readonly DictStain _stainData; private readonly DictStain _stainData;
private readonly ItemCombo[] _itemCombo; private readonly ItemCombo[] _itemCombo;
private readonly BonusItemCombo[] _bonusItemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo; private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly CodeService _codes; private readonly CodeService _codes;
private readonly TextureService _textures; private readonly TextureService _textures;
@ -46,6 +47,7 @@ public class EquipmentDrawer
_stainData = items.Stains; _stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites); _stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray(); _itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
_bonusItemCombo = BonusExtensions.AllFlags.Select(f => new BonusItemCombo(gameData, items, f, Glamourer.Log, favorites)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2); _weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
foreach (var type in Enum.GetValues<FullEquipType>()) foreach (var type in Enum.GetValues<FullEquipType>())
{ {
@ -100,6 +102,21 @@ public class EquipmentDrawer
DrawEquipNormal(equipDrawData); DrawEquipNormal(equipDrawData);
} }
public void DrawBonusItem(BonusDrawData bonusDrawData)
{
if (_config.HideApplyCheckmarks)
bonusDrawData.DisplayApplication = false;
using var id = ImRaii.PushId(100 + (int)bonusDrawData.Slot);
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
if (_config.SmallEquip)
DrawBonusItemSmall(bonusDrawData);
else
DrawBonusItemNormal(bonusDrawData);
}
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
{ {
if (mainhand.CurrentItem.PrimaryId.Id == 0) if (mainhand.CurrentItem.PrimaryId.Id == 0)
@ -302,6 +319,25 @@ public class EquipmentDrawer
ImGui.TextUnformatted(label); ImGui.TextUnformatted(label);
} }
private void DrawBonusItemSmall(in BonusDrawData bonusDrawData)
{
ImGui.Dummy(new Vector2(StainId.NumStains * ImUtf8.FrameHeight + (StainId.NumStains - 1) * ImUtf8.ItemSpacing.X, ImUtf8.FrameHeight));
ImGui.SameLine();
DrawBonusItem(bonusDrawData, out var label, true, false, false);
if (bonusDrawData.DisplayApplication)
{
ImGui.SameLine();
DrawApply(bonusDrawData);
}
else if (bonusDrawData.IsState)
{
_advancedDyes.DrawButton(bonusDrawData.Slot);
}
ImGui.SameLine();
ImGui.TextUnformatted(label);
}
private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) private void DrawWeaponsSmall(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
{ {
DrawStain(mainhand, true); DrawStain(mainhand, true);
@ -382,6 +418,27 @@ public class EquipmentDrawer
} }
} }
private void DrawBonusItemNormal(in BonusDrawData bonusDrawData)
{
ImGui.Dummy(_iconSize with { Y = ImUtf8.FrameHeight });
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
ImGui.SameLine();
DrawBonusItem(bonusDrawData, out var label, false, right, left);
if (bonusDrawData.DisplayApplication)
{
ImGui.SameLine();
DrawApply(bonusDrawData);
}
else if (bonusDrawData.IsState)
{
_advancedDyes.DrawButton(bonusDrawData.Slot);
}
ImGui.SameLine();
ImGui.TextUnformatted(label);
}
private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) private void DrawWeaponsNormal(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
@ -491,6 +548,25 @@ public class EquipmentDrawer
data.SetItem(item); data.SetItem(item);
} }
private void DrawBonusItem(in BonusDrawData data, out string label, bool small, bool clear, bool open)
{
var combo = _bonusItemCombo[data.Slot.ToIndex()];
label = combo.Label;
if (!data.Locked && open)
UiHelpers.OpenCombo($"##{combo.Label}");
using var disabled = ImRaii.Disabled(data.Locked);
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.Id, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_requiredComboWidth);
if (change)
data.SetItem(combo.CurrentSelection);
else if (combo.CustomVariant.Id > 0)
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, BonusItem.Empty(data.Slot), out var item))
data.SetItem(item);
}
private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear, private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear,
in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable<T> in T currentItem, in T revertItem, in T clearItem, out T? item) where T : IEquatable<T>
{ {
@ -590,6 +666,13 @@ public class EquipmentDrawer
data.SetApplyItem(enabled); data.SetApplyItem(enabled);
} }
private static void DrawApply(in BonusDrawData data)
{
if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this bonus item when applying the Design.", data.CurrentApply, out var enabled,
data.Locked))
data.SetApplyItem(enabled);
}
private static void DrawApplyStain(in EquipDrawData data) private static void DrawApplyStain(in EquipDrawData data)
{ {
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain, if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this dye to the item when applying the Design.", data.CurrentApplyStain,

View file

@ -46,6 +46,9 @@ public sealed unsafe class AdvancedDyePopup(
public void DrawButton(EquipSlot slot) public void DrawButton(EquipSlot slot)
=> DrawButton(MaterialValueIndex.FromSlot(slot)); => DrawButton(MaterialValueIndex.FromSlot(slot));
public void DrawButton(BonusItemFlag slot)
=> DrawButton(MaterialValueIndex.FromSlot(slot));
private void DrawButton(MaterialValueIndex index) private void DrawButton(MaterialValueIndex index)
{ {
if (!config.UseAdvancedDyes) if (!config.UseAdvancedDyes)

View file

@ -215,6 +215,12 @@ public class ActorPanel
var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
_equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose());
foreach (var slot in BonusExtensions.AllFlags)
{
var data = BonusDrawData.FromState(_stateManager, _state!, slot);
_equipmentDrawer.DrawBonusItem(data);
}
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawEquipmentMetaToggles(); DrawEquipmentMetaToggles();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));

View file

@ -277,13 +277,13 @@ public class SetPanel(
var size = new Vector2(ImGui.GetFrameHeight()); var size = new Vector2(ImGui.GetFrameHeight());
size.X += ImGuiHelpers.GlobalScale; size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat(); var collection = design.ApplyWhat();
var sb = new StringBuilder(); var sb = new StringBuilder();
var designData = design.Design.GetDesignData(default); var designData = design.Design.GetDesignData(default);
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{ {
var flag = slot.ToFlag(); var flag = slot.ToFlag();
if (!equipFlags.HasFlag(flag)) if (!collection.Equip.HasFlag(flag))
continue; continue;
var item = designData.Item(slot); var item = designData.Item(slot);
@ -308,7 +308,7 @@ public class SetPanel(
foreach (var type in CustomizationExtensions.All) foreach (var type in CustomizationExtensions.All)
{ {
var flag = type.ToFlag(); var flag = type.ToFlag();
if (!customizeFlags.HasFlag(flag)) if (!collection.Customize.HasFlag(flag))
continue; continue;
if (flag.RequiresRedraw()) if (flag.RequiresRedraw())

View file

@ -25,7 +25,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
continue; continue;
DrawDesign(design, _designFileSystem); DrawDesign(design, _designFileSystem);
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.ApplyMeta, var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.Application.Equip, design.Application.Customize, design.Application.Meta,
design.WriteProtected()); design.WriteProtected());
using var font = ImRaii.PushFont(UiBuilder.MonoFont); using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.TextWrapped(base64); ImGuiUtil.TextWrapped(base64);

View file

@ -112,11 +112,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
public DesignBase CreateDesign(in MirageManager.GlamourPlate plate) public DesignBase CreateDesign(in MirageManager.GlamourPlate plate)
{ {
var design = _design.CreateTemporary(); var design = _design.CreateTemporary();
design.ApplyCustomize = 0; design.Application = ApplicationCollection.None;
design.ApplyCrest = 0;
design.ApplyMeta = 0;
design.ApplyParameters = 0;
design.ApplyEquip = 0;
foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex()) foreach (var (slot, index) in EquipSlotExtensions.FullSlots.WithIndex())
{ {
var itemId = plate.ItemIds[index]; var itemId = plate.ItemIds[index];
@ -129,7 +125,7 @@ public unsafe class GlamourPlatePanel : IGameDataDrawer
design.GetDesignDataRef().SetItem(slot, item); design.GetDesignDataRef().SetItem(slot, item);
design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index)); design.GetDesignDataRef().SetStain(slot, StainIds.FromGlamourPlate(plate, index));
design.ApplyEquip |= slot.ToBothFlags(); design.Application.Equip |= slot.ToBothFlags();
} }
return design; return design;

View file

@ -21,7 +21,7 @@ public unsafe class ModelEvaluationPanel(
UpdateSlotService _updateSlotService, UpdateSlotService _updateSlotService,
ChangeCustomizeService _changeCustomizeService, ChangeCustomizeService _changeCustomizeService,
CrestService _crestService, CrestService _crestService,
DictGlasses _glasses) : IGameDataDrawer DictBonusItems bonusItems) : IGameDataDrawer
{ {
public string Label public string Label
=> "Model Evaluation"; => "Model Evaluation";
@ -57,6 +57,16 @@ public unsafe class ModelEvaluationPanel(
ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}"); ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}");
if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1) if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1)
ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}"); ImGui.TextUnformatted($"ModelChara2 {actor.AsCharacter->CharacterData.ModelCharaId_2}");
ImGuiUtil.DrawTableColumn("Character Mode");
ImGuiUtil.DrawTableColumn($"{actor.AsCharacter->Mode}");
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGuiUtil.DrawTableColumn("Animation");
ImGuiUtil.DrawTableColumn($"{((ushort*)&actor.AsCharacter->Timeline)[0x78]}");
ImGui.TableNextColumn();
ImGui.TableNextColumn();
} }
ImGuiUtil.DrawTableColumn("Mainhand"); ImGuiUtil.DrawTableColumn("Mainhand");
@ -226,7 +236,7 @@ public unsafe class ModelEvaluationPanel(
_updateSlotService.UpdateEquipSlot(model, slot, actor.GetArmor(slot)); _updateSlotService.UpdateEquipSlot(model, slot, actor.GetArmor(slot));
} }
foreach (var slot in BonusSlotExtensions.AllFlags) foreach (var slot in BonusExtensions.AllFlags)
{ {
using var id2 = ImRaii.PushId((int)slot.ToModelIndex()); using var id2 = ImRaii.PushId((int)slot.ToModelIndex());
ImGuiUtil.DrawTableColumn(slot.ToName()); ImGuiUtil.DrawTableColumn(slot.ToName());
@ -236,9 +246,9 @@ public unsafe class ModelEvaluationPanel(
} }
else else
{ {
var glassesId = actor.GetBonusSlot(slot); var glassesId = actor.GetBonusItem(slot);
if (_glasses.TryGetValue(glassesId, out var glasses)) if (bonusItems.TryGetValue(glassesId, out var glasses))
ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})"); ImGuiUtil.DrawTableColumn($"{glasses.ModelId.Id},{glasses.Variant.Id} ({glassesId})");
else else
ImGuiUtil.DrawTableColumn($"{glassesId}"); ImGuiUtil.DrawTableColumn($"{glassesId}");
} }

View file

@ -112,6 +112,13 @@ public class DesignPanel
var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand); var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand);
var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand); var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand);
_equipmentDrawer.DrawWeapons(mainhand, offhand, true); _equipmentDrawer.DrawWeapons(mainhand, offhand, true);
foreach (var slot in BonusExtensions.AllFlags)
{
var data = BonusDrawData.FromDesign(_manager, _selector.Selected!, slot);
_equipmentDrawer.DrawBonusItem(data);
}
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
DrawEquipmentMetaToggles(); DrawEquipmentMetaToggles();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
@ -149,7 +156,7 @@ public class DesignPanel
if (!h) if (!h)
return; return;
if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.ApplyCustomizeRaw, if (_customizationDrawer.Draw(_selector.Selected!.DesignData.Customize, _selector.Selected.Application.Customize,
_selector.Selected!.WriteProtected(), false)) _selector.Selected!.WriteProtected(), false))
foreach (var idx in Enum.GetValues<CustomizeIndex>()) foreach (var idx in Enum.GetValues<CustomizeIndex>())
{ {
@ -224,7 +231,7 @@ public class DesignPanel
private void DrawCrestApplication() private void DrawCrestApplication()
{ {
using var id = ImRaii.PushId("Crests"); using var id = ImRaii.PushId("Crests");
var flags = (uint)_selector.Selected!.ApplyCrest; var flags = (uint)_selector.Selected!.Application.Crest;
var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant); var bigChange = ImGui.CheckboxFlags("Apply All Crests", ref flags, (uint)CrestExtensions.AllRelevant);
foreach (var flag in CrestExtensions.AllRelevantSet) foreach (var flag in CrestExtensions.AllRelevantSet)
{ {
@ -255,7 +262,7 @@ public class DesignPanel
{ {
void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots) void ApplyEquip(string label, EquipFlag allFlags, bool stain, IEnumerable<EquipSlot> slots)
{ {
var flags = (uint)(allFlags & _selector.Selected!.ApplyEquip); var flags = (uint)(allFlags & _selector.Selected!.Application.Equip);
using var id = ImRaii.PushId(label); using var id = ImRaii.PushId(label);
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags); var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags);
if (stain) if (stain)
@ -302,7 +309,7 @@ public class DesignPanel
{ {
using var id = ImRaii.PushId("Meta"); using var id = ImRaii.PushId("Meta");
const uint all = (uint)MetaExtensions.All; const uint all = (uint)MetaExtensions.All;
var flags = (uint)_selector.Selected!.ApplyMeta; var flags = (uint)_selector.Selected!.Application.Meta;
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
var labels = new[] var labels = new[]
@ -324,7 +331,7 @@ public class DesignPanel
private void DrawParameterApplication() private void DrawParameterApplication()
{ {
using var id = ImRaii.PushId("Parameter"); using var id = ImRaii.PushId("Parameter");
var flags = (uint)_selector.Selected!.ApplyParameters; var flags = (uint)_selector.Selected!.Application.Parameters;
var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All); var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All);
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
{ {
@ -408,8 +415,7 @@ public class DesignPanel
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks);
} }
} }
@ -427,8 +433,7 @@ public class DesignPanel
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags(); using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks); _state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks);
} }
} }

View file

@ -1,6 +1,5 @@
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
@ -98,15 +97,6 @@ public static class UiHelpers
return (currentValue != newValue, currentApply != newApply); return (currentValue != newValue, currentApply != newApply);
} }
public static (EquipFlag, CustomizeFlag, CrestFlag, CustomizeParameterFlag) ConvertKeysToFlags()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{
(false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All),
(true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All),
(true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All, 0),
(false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All),
};
public static (bool, bool) ConvertKeysToBool() public static (bool, bool) ConvertKeysToBool()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{ {
@ -126,16 +116,36 @@ public static class UiHelpers
using var c = ImRaii.PushColor(ImGuiCol.Text, using var c = ImRaii.PushColor(ImGuiCol.Text,
hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value());
ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString());
if (ImGui.IsItemClicked()) if (!ImGui.IsItemClicked())
{ return false;
if (favorite) if (favorite)
favorites.Remove(item); favorites.Remove(item);
else else
favorites.TryAdd(item); favorites.TryAdd(item);
return true; return true;
} }
public static bool DrawFavoriteStar(FavoriteManager favorites, BonusItem item)
{
var favorite = favorites.Contains(item);
var hovering = ImGui.IsMouseHoveringRect(ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()));
using var font = ImRaii.PushFont(UiBuilder.IconFont);
using var c = ImRaii.PushColor(ImGuiCol.Text,
hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value());
ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString());
if (!ImGui.IsItemClicked())
return false; return false;
if (favorite)
favorites.Remove(item);
else
favorites.TryAdd(item);
return true;
} }
public static bool DrawFavoriteStar(FavoriteManager favorites, StainId stain) public static bool DrawFavoriteStar(FavoriteManager favorites, StainId stain)
@ -149,15 +159,14 @@ public static class UiHelpers
hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value()); hovering ? ColorId.FavoriteStarHovered.Value() : favorite ? ColorId.FavoriteStarOn.Value() : ColorId.FavoriteStarOff.Value());
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString()); ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString());
if (ImGui.IsItemClicked()) if (!ImGui.IsItemClicked())
{ return false;
if (favorite) if (favorite)
favorites.Remove(stain); favorites.Remove(stain);
else else
favorites.TryAdd(stain); favorites.TryAdd(stain);
return true; return true;
}
return false;
} }
} }

View file

@ -42,6 +42,12 @@ public readonly record struct MaterialValueIndex(
return Invalid; return Invalid;
} }
public static MaterialValueIndex FromSlot(BonusItemFlag slot)
{
var idx = slot.ToIndex();
return idx > 2 ? Invalid : new MaterialValueIndex(DrawObjectType.Human, (byte)(idx + 16), 0, 0);
}
public EquipSlot ToEquipSlot() public EquipSlot ToEquipSlot()
=> DrawObject switch => DrawObject switch
{ {

View file

@ -37,17 +37,13 @@ public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManage
} }
var design = designManager.CreateEmpty(fullPath, true); var design = designManager.CreateEmpty(fullPath, true);
design.ApplyCustomize = 0; design.Application = ApplicationCollection.None;
design.ApplyEquip = 0;
design.ApplyCrest = 0;
designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false);
designManager.ChangeApplyMeta(design, MetaIndex.HatState, false);
designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false);
foreach (var flag in flags.Iterate()) foreach (var flag in flags.Iterate())
{ {
designManager.ChangeApplyParameter(design, flag, true); designManager.ChangeApplyParameter(design, flag, true);
designManager.ChangeCustomizeParameter(design, flag, palette[flag]); designManager.ChangeCustomizeParameter(design, flag, palette[flag]);
} }
Glamourer.Log.Information($"Added design for palette {name} at {fullPath}."); Glamourer.Log.Information($"Added design for palette {name} at {fullPath}.");
} }
} }

View file

@ -14,14 +14,14 @@ public unsafe class UpdateSlotService : IDisposable
{ {
public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly EquipSlotUpdating EquipSlotUpdatingEvent;
public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent;
private readonly DictGlasses _glasses; private readonly DictBonusItems _bonusItems;
public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop, public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop,
DictGlasses glasses) DictBonusItems bonusItems)
{ {
EquipSlotUpdatingEvent = equipSlotUpdating; EquipSlotUpdatingEvent = equipSlotUpdating;
BonusSlotUpdatingEvent = bonusSlotUpdating; BonusSlotUpdatingEvent = bonusSlotUpdating;
_glasses = glasses; _bonusItems = bonusItems;
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_flagSlotForUpdateHook.Enable(); _flagSlotForUpdateHook.Enable();
_flagBonusSlotForUpdateHook.Enable(); _flagBonusSlotForUpdateHook.Enable();
@ -41,7 +41,7 @@ public unsafe class UpdateSlotService : IDisposable
FlagSlotForUpdateInterop(drawObject, slot, data); FlagSlotForUpdateInterop(drawObject, slot, data);
} }
public void UpdateBonusSlot(Model drawObject, BonusEquipFlag slot, CharacterArmor data) public void UpdateBonusSlot(Model drawObject, BonusItemFlag slot, CharacterArmor data)
{ {
if (!drawObject.IsCharacterBase) if (!drawObject.IsCharacterBase)
return; return;
@ -53,13 +53,13 @@ public unsafe class UpdateSlotService : IDisposable
_flagBonusSlotForUpdateHook.Original(drawObject.Address, index, &data); _flagBonusSlotForUpdateHook.Original(drawObject.Address, index, &data);
} }
public void UpdateGlasses(Model drawObject, GlassesId id) public void UpdateGlasses(Model drawObject, BonusItemId id)
{ {
if (!_glasses.TryGetValue(id, out var glasses)) if (!_bonusItems.TryGetValue(id, out var glasses))
return; return;
var armor = new CharacterArmor(glasses.Id, glasses.Variant, StainIds.None); var armor = new CharacterArmor(glasses.ModelId, glasses.Variant, StainIds.None);
_flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusEquipFlag.Glasses.ToIndex(), &armor); _flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusItemFlag.Glasses.ToIndex(), &armor);
} }
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains) public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor armor, StainIds stains)

View file

@ -20,12 +20,13 @@ public class ItemManager
public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet; public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet;
public readonly DictStain Stains; public readonly DictStain Stains;
public readonly ItemData ItemData; public readonly ItemData ItemData;
public readonly DictBonusItems DictBonusItems;
public readonly RestrictedGear RestrictedGear; public readonly RestrictedGear RestrictedGear;
public readonly EquipItem DefaultSword; public readonly EquipItem DefaultSword;
public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification, public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification,
ItemData itemData, DictStain stains, RestrictedGear restrictedGear) ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems)
{ {
_config = config; _config = config;
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!; ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
@ -33,6 +34,7 @@ public class ItemManager
ItemData = itemData; ItemData = itemData;
Stains = stains; Stains = stains;
RestrictedGear = restrictedGear; RestrictedGear = restrictedGear;
DictBonusItems = dictBonusItems;
DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword
} }
@ -124,6 +126,22 @@ public class ItemManager
} }
} }
public BonusItem Identify(BonusItemFlag slot, PrimaryId id, Variant variant)
{
var index = slot.ToIndex();
if (index == uint.MaxValue)
return new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot);
if (id.Id == 0)
return BonusItem.Empty(slot);
return ObjectIdentification.Identify(id, variant, slot)
.FirstOrDefault(new BonusItem($"Invalid ({id.Id}-{variant})", 0, 0, id, variant, slot));
}
public BonusItem Resolve(BonusItemFlag slot, BonusItemId id)
=> IsBonusItemValid(slot, id, out var item) ? item : new BonusItem($"Invalid ({id.Id})", 0, id, 0, 0, slot);
/// <summary> Return the default offhand for a given mainhand, that is for both handed weapons, return the correct offhand part, and for everything else Nothing. </summary> /// <summary> Return the default offhand for a given mainhand, that is for both handed weapons, return the correct offhand part, and for everything else Nothing. </summary>
public EquipItem GetDefaultOffhand(EquipItem mainhand) public EquipItem GetDefaultOffhand(EquipItem mainhand)
{ {
@ -161,6 +179,18 @@ public class ItemManager
return item.Valid; return item.Valid;
} }
/// <summary> Returns whether a bonus item id represents a valid item for a slot and gives the item. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool IsBonusItemValid(BonusItemFlag slot, BonusItemId itemId, out BonusItem item)
{
if (itemId.Id != 0)
return DictBonusItems.TryGetValue(itemId, out item) && slot == item.Slot;
item = BonusItem.Empty(slot);
return true;
}
/// <summary> /// <summary>
/// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.) /// Check whether an item id resolves to an existing item of the correct slot (which should not be weapons.)
/// The returned item is either the resolved correct item, or the Nothing item for that slot. /// The returned item is either the resolved correct item, or the Nothing item for that slot.

View file

@ -151,6 +151,18 @@ public class InternalStateEditor(
return true; return true;
} }
/// <summary> Change a single bonus item. </summary>
public bool ChangeBonusItem(ActorState state, BonusItemFlag slot, BonusItem item, StateSource source, out BonusItem oldItem, uint key = 0)
{
oldItem = state.ModelData.BonusItem(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetBonusItem(slot, item);
state.Sources[slot] = source;
return true;
}
/// <summary> Change a single piece of equipment including stain. </summary> /// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem, public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem,
out StainIds oldStains, uint key = 0) out StainIds oldStains, uint key = 0)

View file

@ -125,6 +125,33 @@ public class StateApplier(
return data; return data;
} }
public void ChangeBonusItem(ActorData data, BonusItemFlag slot, PrimaryId id, Variant variant)
{
var item = new CharacterArmor(id, variant, StainIds.None);
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
{
var mdl = actor.Model;
if (!mdl.IsHuman)
continue;
_updateSlot.UpdateBonusSlot(actor.Model, slot, item);
}
}
/// <inheritdoc cref="ChangeBonusItem(ActorData,BonusItemFlag,PrimaryId,Variant)"/>
public ActorData ChangeBonusItem(ActorState state, BonusItemFlag slot, bool apply)
{
// If the source is not IPC we do not want to apply restrictions.
var data = GetData(state);
if (apply)
{
var item = state.ModelData.BonusItem(slot);
ChangeBonusItem(data, slot, item.ModelId, item.Variant);
}
return data;
}
/// <summary> /// <summary>
/// Change the stain of a single piece of armor or weapon. /// Change the stain of a single piece of armor or weapon.

View file

@ -89,6 +89,18 @@ public class StateEditor(
StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot)); StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot));
} }
public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default)
{
var state = (ActorState)data;
if (!Editor.ChangeBonusItem(state, slot, item, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange());
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, (old, item, slot));
}
/// <inheritdoc/> /// <inheritdoc/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings) public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings)
{ {
@ -226,7 +238,7 @@ public class StateEditor(
out _, settings.Key); out _, settings.Key);
} }
var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw; var customizeFlags = mergedDesign.Design.Application.Customize;
if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan)) if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan))
customizeFlags |= CustomizeFlag.Race; customizeFlags |= CustomizeFlag.Race;
@ -245,7 +257,7 @@ public class StateEditor(
state.Sources[parameter] = StateSource.Game; state.Sources[parameter] = StateSource.Game;
} }
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) foreach (var parameter in mergedDesign.Design.Application.Parameters.Iterate())
{ {
if (settings.RespectManual && state.Sources[parameter].IsManual()) if (settings.RespectManual && state.Sources[parameter].IsManual())
continue; continue;
@ -273,6 +285,13 @@ public class StateEditor(
Source(slot.ToState(true)), out _, settings.Key); Source(slot.ToState(true)), out _, settings.Key);
} }
foreach (var slot in BonusExtensions.AllFlags)
{
if (mergedDesign.Design.DoApplyBonusItem(slot))
if (!settings.RespectManual || !state.Sources[slot].IsManual())
Editor.ChangeBonusItem(state, slot, mergedDesign.Design.DesignData.BonusItem(slot), Source(slot), out _, settings.Key);
}
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
{ {
if (mergedDesign.Design.DoApplyStain(weaponSlot)) if (mergedDesign.Design.DoApplyStain(weaponSlot))

View file

@ -38,6 +38,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
_ => Invalid, _ => Invalid,
}; };
public static implicit operator StateIndex(BonusItemFlag flag)
=> flag switch
{
BonusItemFlag.Glasses => new StateIndex(BonusItemGlasses),
_ => Invalid,
};
public static implicit operator StateIndex(CustomizeIndex index) public static implicit operator StateIndex(CustomizeIndex index)
=> index switch => index switch
{ {
@ -198,23 +205,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
public const int ParamFacePaintUvOffset = ParamFacePaintUvMultiplier + 1; public const int ParamFacePaintUvOffset = ParamFacePaintUvMultiplier + 1;
public const int ParamDecalColor = ParamFacePaintUvOffset + 1; public const int ParamDecalColor = ParamFacePaintUvOffset + 1;
public const int Size = ParamDecalColor + 1; public const int BonusItemGlasses = ParamDecalColor + 1;
public const int Size = BonusItemGlasses + 1;
public static IEnumerable<StateIndex> All public static IEnumerable<StateIndex> All
=> Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i)); => Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i));
public bool GetApply(DesignBase data)
=> GetFlag() switch
{
EquipFlag e => data.ApplyEquip.HasFlag(e),
CustomizeFlag c => data.ApplyCustomize.HasFlag(c),
MetaFlag m => data.ApplyMeta.HasFlag(m),
CrestFlag c => data.ApplyCrest.HasFlag(c),
CustomizeParameterFlag c => data.ApplyParameters.HasFlag(c),
bool v => v,
_ => false,
};
public string ToName() public string ToName()
=> GetFlag() switch => GetFlag() switch
{ {
@ -223,6 +220,7 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
MetaFlag m => m.ToIndex().ToName(), MetaFlag m => m.ToIndex().ToName(),
CrestFlag c => c.ToLabel(), CrestFlag c => c.ToLabel(),
CustomizeParameterFlag c => c.ToName(), CustomizeParameterFlag c => c.ToName(),
BonusItemFlag b => b.ToName(),
bool v => "Model ID", bool v => "Model ID",
_ => "Unknown", _ => "Unknown",
}; };
@ -317,6 +315,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset, ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset,
ParamDecalColor => CustomizeParameterFlag.DecalColor, ParamDecalColor => CustomizeParameterFlag.DecalColor,
BonusItemGlasses => BonusItemFlag.Glasses,
_ => -1, _ => -1,
}; };
@ -411,6 +411,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset], ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset],
ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor], ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor],
BonusItemGlasses => data.BonusItem(BonusItemFlag.Glasses),
_ => null, _ => null,
}; };
} }

View file

@ -33,6 +33,7 @@ public class StateListener : IDisposable
private readonly CustomizeService _customizations; private readonly CustomizeService _customizations;
private readonly PenumbraService _penumbra; private readonly PenumbraService _penumbra;
private readonly EquipSlotUpdating _equipSlotUpdating; private readonly EquipSlotUpdating _equipSlotUpdating;
private readonly BonusSlotUpdating _bonusSlotUpdating;
private readonly WeaponLoading _weaponLoading; private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility; private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState; private readonly VisorStateChanged _visorState;
@ -52,10 +53,11 @@ public class StateListener : IDisposable
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config,
EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility, EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans, WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier,
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose, FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects,
ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService) GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition,
CrestService crestService, BonusSlotUpdating bonusSlotUpdating)
{ {
_manager = manager; _manager = manager;
_items = items; _items = items;
@ -78,6 +80,7 @@ public class StateListener : IDisposable
_customizations = customizations; _customizations = customizations;
_condition = condition; _condition = condition;
_crestService = crestService; _crestService = crestService;
_bonusSlotUpdating = bonusSlotUpdating;
Subscribe(); Subscribe();
} }
@ -227,6 +230,35 @@ public class StateListener : IDisposable
(_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender); (_, armor) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
} }
private void OnBonusSlotUpdating(Model model, BonusItemFlag slot, ref CharacterArmor item, ref ulong returnValue)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (actor.Identifier(_actors, out var identifier)
&& _manager.TryGetValue(identifier, out var state))
switch (UpdateBaseData(actor, state, slot, item))
{
// Base data changed equipment while actors were not there.
// Update model state if not on fixed design.
case UpdateState.Change:
var apply = false;
if (!state.Sources[slot].IsFixed())
_manager.ChangeBonusItem(state, slot, state.BaseData.BonusItem(slot), ApplySettings.Game);
else
apply = true;
if (apply)
item = state.ModelData.BonusItem(slot).ToArmor();
break;
// Use current model data.
case UpdateState.NoChange:
item = state.ModelData.BonusItem(slot).ToArmor();
break;
case UpdateState.Transformed: break;
}
}
private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items) private void OnMovedEquipment((EquipSlot, uint, StainIds)[] items)
{ {
_objects.Update(); _objects.Update();
@ -403,6 +435,28 @@ public class StateListener : IDisposable
} }
} }
private UpdateState UpdateBaseData(Actor actor, ActorState state, BonusItemFlag slot, CharacterArmor item)
{
var actorItemId = actor.GetBonusItem(slot);
if (!_items.IsBonusItemValid(slot, actorItemId, out var actorItem))
return UpdateState.NoChange;
// The actor item does not correspond to the model item, thus the actor is transformed.
if (actorItem.ModelId != item.Set || actorItem.Variant != item.Variant)
return UpdateState.Transformed;
var baseData = state.BaseData.BonusItem(slot);
var change = UpdateState.NoChange;
if (baseData.Id != actorItem.Id || baseData.ModelId != item.Set || baseData.Variant != item.Variant)
{
var identified = _items.Identify(slot, item.Set, item.Variant);
state.BaseData.SetBonusItem(slot, identified);
change = UpdateState.Change;
}
return change;
}
/// <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)
{ {
@ -700,6 +754,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase; _penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase; _penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
_equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener); _equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener);
_bonusSlotUpdating.Subscribe(OnBonusSlotUpdating, BonusSlotUpdating.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);
@ -716,6 +771,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase; _penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase; _penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
_equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating); _equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating);
_bonusSlotUpdating.Unsubscribe(OnBonusSlotUpdating);
_movedEquipment.Unsubscribe(OnMovedEquipment); _movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading); _weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange); _visorState.Unsubscribe(OnVisorChange);

View file

@ -160,6 +160,13 @@ public sealed class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot)); ret.SetCrest(slot, CrestService.GetModelCrest(actor, slot));
foreach (var slot in BonusExtensions.AllFlags)
{
var data = model.GetBonus(slot);
var item = Items.Identify(slot, data.Set, data.Variant);
ret.SetBonusItem(slot, item);
}
} }
else else
{ {
@ -181,6 +188,13 @@ public sealed class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
ret.SetCrest(slot, actor.GetCrest(slot)); ret.SetCrest(slot, actor.GetCrest(slot));
foreach (var slot in BonusExtensions.AllFlags)
{
var id = actor.GetBonusItem(slot);
var item = Items.Resolve(slot, id);
ret.SetBonusItem(slot, item);
}
} }
// Set the weapons regardless of source. // Set the weapons regardless of source.
@ -241,6 +255,9 @@ public sealed class StateManager(
state.Sources[slot, false] = StateSource.Game; state.Sources[slot, false] = StateSource.Game;
} }
foreach (var slot in BonusExtensions.AllFlags)
state.Sources[slot] = StateSource.Game;
foreach (var type in Enum.GetValues<MetaIndex>()) foreach (var type in Enum.GetValues<MetaIndex>())
state.Sources[type] = StateSource.Game; state.Sources[type] = StateSource.Game;
@ -328,6 +345,12 @@ public sealed class StateManager(
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
} }
foreach (var slot in BonusExtensions.AllFlags)
{
var item = state.ModelData.BonusItem(slot);
Applier.ChangeBonusItem(actors, slot, item.ModelId, item.Variant);
}
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));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors; var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
@ -364,6 +387,15 @@ public sealed class StateManager(
} }
} }
foreach (var slot in BonusExtensions.AllFlags)
{
if (state.Sources[slot] is StateSource.Fixed)
{
state.Sources[slot] = StateSource.Game;
state.ModelData.SetBonusItem(slot, state.BaseData.BonusItem(slot));
}
}
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
{ {
if (state.Sources[slot] is StateSource.Fixed) if (state.Sources[slot] is StateSource.Fixed)

View file

@ -25,6 +25,7 @@ public class FavoriteManager : ISavable
private readonly HashSet<ItemId> _favorites = []; private readonly HashSet<ItemId> _favorites = [];
private readonly HashSet<StainId> _favoriteColors = []; private readonly HashSet<StainId> _favoriteColors = [];
private readonly HashSet<FavoriteHairStyle> _favoriteHairStyles = []; private readonly HashSet<FavoriteHairStyle> _favoriteHairStyles = [];
private readonly HashSet<BonusItemId> _favoriteBonusItems = [];
public FavoriteManager(SaveService saveService) public FavoriteManager(SaveService saveService)
{ {
@ -62,6 +63,7 @@ public class FavoriteManager : ISavable
_favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i)); _favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i));
_favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i)); _favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i));
_favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t))); _favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t)));
_favoriteBonusItems.UnionWith(load!.FavoriteBonusItems.Select(b => new BonusItemId(b)));
break; break;
default: throw new Exception($"Unknown Version {load?.Version ?? 0}"); default: throw new Exception($"Unknown Version {load?.Version ?? 0}");
@ -109,6 +111,11 @@ public class FavoriteManager : ISavable
foreach (var hairStyle in _favoriteHairStyles) foreach (var hairStyle in _favoriteHairStyles)
j.WriteValue(hairStyle.ToValue()); j.WriteValue(hairStyle.ToValue());
j.WriteEndArray(); j.WriteEndArray();
j.WriteStartArray();
j.WritePropertyName(nameof(LoadIntermediary.FavoriteBonusItems));
foreach (var item in _favoriteBonusItems)
j.WriteValue(item.Id);
j.WriteEndArray();
j.WriteEndObject(); j.WriteEndObject();
} }
@ -124,9 +131,6 @@ public class FavoriteManager : ISavable
return true; return true;
} }
public bool TryAdd(Stain stain)
=> TryAdd(stain.RowIndex);
public bool TryAdd(StainId stain) public bool TryAdd(StainId stain)
{ {
if (stain.Id == 0 || !_favoriteColors.Add(stain)) if (stain.Id == 0 || !_favoriteColors.Add(stain))
@ -136,6 +140,15 @@ public class FavoriteManager : ISavable
return true; return true;
} }
public bool TryAdd(BonusItem bonusItem)
{
if (bonusItem.Id == 0 || !_favoriteBonusItems.Add(bonusItem.Id))
return false;
Save();
return true;
}
public bool TryAdd(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) public bool TryAdd(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value)
{ {
if (!TypeAllowed(type) || !_favoriteHairStyles.Add(new FavoriteHairStyle(gender, race, type, value))) if (!TypeAllowed(type) || !_favoriteHairStyles.Add(new FavoriteHairStyle(gender, race, type, value)))
@ -157,9 +170,6 @@ public class FavoriteManager : ISavable
return true; return true;
} }
public bool Remove(Stain stain)
=> Remove(stain.RowIndex);
public bool Remove(StainId stain) public bool Remove(StainId stain)
{ {
if (!_favoriteColors.Remove(stain)) if (!_favoriteColors.Remove(stain))
@ -169,6 +179,15 @@ public class FavoriteManager : ISavable
return true; return true;
} }
public bool Remove(BonusItem bonusItem)
{
if (!_favoriteBonusItems.Remove(bonusItem.Id))
return false;
Save();
return true;
}
public bool Remove(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) public bool Remove(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value)
{ {
if (!_favoriteHairStyles.Remove(new FavoriteHairStyle(gender, race, type, value))) if (!_favoriteHairStyles.Remove(new FavoriteHairStyle(gender, race, type, value)))
@ -181,15 +200,12 @@ public class FavoriteManager : ISavable
public bool Contains(EquipItem item) public bool Contains(EquipItem item)
=> _favorites.Contains(item.ItemId); => _favorites.Contains(item.ItemId);
public bool Contains(Stain stain)
=> _favoriteColors.Contains(stain.RowIndex);
public bool Contains(ItemId item)
=> _favorites.Contains(item);
public bool Contains(StainId stain) public bool Contains(StainId stain)
=> _favoriteColors.Contains(stain); => _favoriteColors.Contains(stain);
public bool Contains(BonusItem bonusItem)
=> _favoriteBonusItems.Contains(bonusItem.Id);
public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value) public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value)
=> _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value)); => _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value));
@ -199,5 +215,6 @@ public class FavoriteManager : ISavable
public uint[] FavoriteItems = []; public uint[] FavoriteItems = [];
public byte[] FavoriteColors = []; public byte[] FavoriteColors = [];
public uint[] FavoriteHairStyles = []; public uint[] FavoriteHairStyles = [];
public ushort[] FavoriteBonusItems = [];
} }
} }

@ -1 +1 @@
Subproject commit d83303ccc3ec5d7237f5da621e9c2433ad28f9e1 Subproject commit 8928015f38f951810a9a6fbb44fb4a0cb9a712dd