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,7 +35,8 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
state = null;
return GlamourerApiEc.ActorNotFound;
}
stateManager.TryGetValue(identifier, out state);
stateManager.TryGetValue(identifier, out state);
return GlamourerApiEc.Success;
}
@ -54,12 +55,10 @@ public class ApiHelpers(ObjectManager objects, StateManager stateManager, ActorM
internal static DesignBase.FlagRestrictionResetter Restrict(DesignBase design, ApplyFlag flags)
=> (flags & (ApplyFlag.Equipment | ApplyFlag.Customization)) switch
{
ApplyFlag.Equipment => design.TemporarilyRestrictApplication(EquipFlagExtensions.All, 0, CrestExtensions.All, 0),
ApplyFlag.Customization => design.TemporarilyRestrictApplication(0, CustomizeFlagExtensions.All, 0,
CustomizeParameterExtensions.All),
ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(EquipFlagExtensions.All,
CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All),
_ => design.TemporarilyRestrictApplication(0, 0, 0, 0),
ApplyFlag.Equipment => design.TemporarilyRestrictApplication(ApplicationCollection.Equipment),
ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.Customizations),
ApplyFlag.Equipment | ApplyFlag.Customization => design.TemporarilyRestrictApplication(ApplicationCollection.All),
_ => design.TemporarilyRestrictApplication(ApplicationCollection.None),
};
[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."),
];
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat(
this ApplicationType type, IDesignStandIn designStandIn)
public static ApplicationCollection Collection(this ApplicationType type)
{
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
@ -37,18 +36,18 @@ public static class ApplicationTypeExtensions
| (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0);
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
var crestFlags = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
var metaFlags = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
var bonusFlags = type.HasFlag(ApplicationType.Armor) ? BonusExtensions.All : 0;
if (designStandIn is not DesignBase design)
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta);
return new ApplicationCollection(equipFlags, bonusFlags, customizeFlags, crestFlags, parameterFlags, metaFlags);
}
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 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;

View file

@ -61,6 +61,6 @@ public class AutoDesign
return ret;
}
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat()
public ApplicationCollection ApplyWhat()
=> 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;
public readonly struct ApplicationRules(
EquipFlag equip,
CustomizeFlag customize,
CrestFlag crest,
CustomizeParameterFlag parameters,
MetaFlag meta,
bool materials)
public readonly struct ApplicationRules(ApplicationCollection application, bool materials)
{
public static readonly ApplicationRules All = new(EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant,
CrestExtensions.AllRelevant, CustomizeParameterExtensions.All, MetaExtensions.All, true);
public static readonly ApplicationRules All = new(ApplicationCollection.All, true);
public static ApplicationRules FromModifiers(ActorState state)
=> FromModifiers(state, ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
@ -23,54 +16,43 @@ public readonly struct ApplicationRules(
=> NpcFromModifiers(ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift);
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)
=> 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)
=> new(ctrl || !shift ? EquipFlagExtensions.All : 0,
!ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0,
0,
0,
ctrl || !shift ? MetaFlag.VisorState : 0, false);
{
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
var visor = equip != 0 ? MetaFlag.VisorState : 0;
return new ApplicationRules(new ApplicationCollection(equip, 0, customize, 0, 0, visor), false);
}
public static ApplicationRules FromModifiers(ActorState state, bool ctrl, bool shift)
{
var equip = ctrl || !shift ? EquipFlagExtensions.All : 0;
var customize = !ctrl || shift ? CustomizeFlagExtensions.AllRelevant : 0;
var bonus = equip == 0 ? 0 : BonusExtensions.All;
var crest = equip == 0 ? 0 : CrestExtensions.AllRelevant;
var parameters = customize == 0 ? 0 : CustomizeParameterExtensions.All;
var meta = state.ModelData.IsWet() ? MetaFlag.Wetness : 0;
if (equip != 0)
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)
{
design.ApplyEquip = Equip;
design.ApplyCustomize = Customize;
design.ApplyCrest = Crest;
design.ApplyParameters = Parameters;
design.ApplyMeta = Meta;
}
=> design.Application = application;
public EquipFlag Equip
=> equip & EquipFlagExtensions.All;
public CustomizeFlag Customize
=> customize & CustomizeFlagExtensions.AllRelevant;
public CrestFlag Crest
=> crest & CrestExtensions.AllRelevant;
=> application.Equip & EquipFlagExtensions.All;
public CustomizeParameterFlag Parameters
=> parameters & CustomizeParameterExtensions.All;
public MetaFlag Meta
=> meta & MetaExtensions.All;
=> application.Parameters & CustomizeParameterExtensions.All;
public bool Materials
=> materials;

View file

@ -42,23 +42,19 @@ public class DesignBase
/// <summary> Used when importing .cma or .chara files. </summary>
internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
{
_designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
ApplyEquip = equipFlags & EquipFlagExtensions.All;
ApplyMeta = 0;
CustomizeSet = SetCustomizationSet(customize);
_designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
Application.Equip = equipFlags & EquipFlagExtensions.All;
Application.Meta = 0;
CustomizeSet = SetCustomizationSet(customize);
}
internal DesignBase(DesignBase clone)
{
_designData = clone._designData;
_materials = clone._materials.Clone();
CustomizeSet = clone.CustomizeSet;
ApplyCustomize = clone.ApplyCustomizeRaw;
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All;
ApplyCrest = clone.ApplyCrest & CrestExtensions.All;
ApplyMeta = clone.ApplyMeta & MetaExtensions.All;
_designData = clone._designData;
_materials = clone._materials.Clone();
CustomizeSet = clone.CustomizeSet;
Application = clone.Application.CloneSecure();
}
/// <summary> Ensure that the customization set is updated when the design data changes. </summary>
@ -70,27 +66,20 @@ public class DesignBase
#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
{
get => _applyCustomize.FixApplication(CustomizeSet);
set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
get => Application.Customize.FixApplication(CustomizeSet);
set => Application.Customize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
}
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)
{
@ -103,18 +92,18 @@ public class DesignBase
}
public bool DoApplyMeta(MetaIndex index)
=> ApplyMeta.HasFlag(index.ToFlag());
=> Application.Meta.HasFlag(index.ToFlag());
public bool WriteProtected()
=> _writeProtected;
public bool SetApplyMeta(MetaIndex index, bool value)
{
var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag();
if (newFlag == ApplyMeta)
var newFlag = value ? Application.Meta | index.ToFlag() : Application.Meta & ~index.ToFlag();
if (newFlag == Application.Meta)
return false;
ApplyMeta = newFlag;
Application.Meta = newFlag;
return true;
}
@ -128,103 +117,100 @@ public class DesignBase
}
public bool DoApplyEquip(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToFlag());
=> Application.Equip.HasFlag(slot.ToFlag());
public bool DoApplyStain(EquipSlot slot)
=> ApplyEquip.HasFlag(slot.ToStainFlag());
=> Application.Equip.HasFlag(slot.ToStainFlag());
public bool DoApplyCustomize(CustomizeIndex idx)
=> ApplyCustomize.HasFlag(idx.ToFlag());
=> Application.Customize.HasFlag(idx.ToFlag());
public bool DoApplyCrest(CrestFlag slot)
=> ApplyCrest.HasFlag(slot);
=> Application.Crest.HasFlag(slot);
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)
{
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
if (newValue == ApplyEquip)
var newValue = value ? Application.Equip | slot.ToFlag() : Application.Equip & ~slot.ToFlag();
if (newValue == Application.Equip)
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;
}
internal bool SetApplyStain(EquipSlot slot, bool value)
{
var newValue = value ? ApplyEquip | slot.ToStainFlag() : ApplyEquip & ~slot.ToStainFlag();
if (newValue == ApplyEquip)
var newValue = value ? Application.Equip | slot.ToStainFlag() : Application.Equip & ~slot.ToStainFlag();
if (newValue == Application.Equip)
return false;
ApplyEquip = newValue;
Application.Equip = newValue;
return true;
}
internal bool SetApplyCustomize(CustomizeIndex idx, bool value)
{
var newValue = value ? _applyCustomize | idx.ToFlag() : _applyCustomize & ~idx.ToFlag();
if (newValue == _applyCustomize)
var newValue = value ? Application.Customize | idx.ToFlag() : Application.Customize & ~idx.ToFlag();
if (newValue == Application.Customize)
return false;
_applyCustomize = newValue;
Application.Customize = newValue;
return true;
}
internal bool SetApplyCrest(CrestFlag slot, bool value)
{
var newValue = value ? ApplyCrest | slot : ApplyCrest & ~slot;
if (newValue == ApplyCrest)
var newValue = value ? Application.Crest | slot : Application.Crest & ~slot;
if (newValue == Application.Crest)
return false;
ApplyCrest = newValue;
Application.Crest = newValue;
return true;
}
internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value)
{
var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag;
if (newValue == ApplyParameters)
var newValue = value ? Application.Parameters | flag : Application.Parameters & ~flag;
if (newValue == Application.Parameters)
return false;
ApplyParameters = newValue;
Application.Parameters = newValue;
return true;
}
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
CustomizeParameterFlag parameterFlags)
=> new(this, equipFlags, customizeFlags, crestFlags, parameterFlags);
internal FlagRestrictionResetter TemporarilyRestrictApplication(ApplicationCollection restrictions)
=> new(this, restrictions);
internal readonly struct FlagRestrictionResetter : IDisposable
{
private readonly DesignBase _design;
private readonly EquipFlag _oldEquipFlags;
private readonly CustomizeFlag _oldCustomizeFlags;
private readonly CrestFlag _oldCrestFlags;
private readonly CustomizeParameterFlag _oldParameterFlags;
private readonly DesignBase _design;
private readonly ApplicationCollection _oldFlags;
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
CustomizeParameterFlag parameterFlags)
public FlagRestrictionResetter(DesignBase d, ApplicationCollection restrictions)
{
_design = d;
_oldEquipFlags = d.ApplyEquip;
_oldCustomizeFlags = d.ApplyCustomizeRaw;
_oldCrestFlags = d.ApplyCrest;
_oldParameterFlags = d.ApplyParameters;
d.ApplyEquip &= equipFlags;
d.ApplyCustomize &= customizeFlags;
d.ApplyCrest &= crestFlags;
d.ApplyParameters &= parameterFlags;
_design = d;
_oldFlags = d.Application;
_design.Application = restrictions.Restrict(_oldFlags);
}
public void Dispose()
{
_design.ApplyEquip = _oldEquipFlags;
_design.ApplyCustomize = _oldCustomizeFlags;
_design.ApplyCrest = _oldCrestFlags;
_design.ApplyParameters = _oldParameterFlags;
}
=> _design.Application = _oldFlags;
}
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()
{
var ret = new JObject()
@ -299,7 +301,7 @@ public class DesignBase
ret[idx.ToString()] = new JObject()
{
["Value"] = customize[idx].Value,
["Apply"] = ApplyCustomizeRaw.HasFlag(idx.ToFlag()),
["Apply"] = Application.Customize.HasFlag(idx.ToFlag()),
};
}
else
@ -382,7 +384,7 @@ public class DesignBase
{
var k = uint.Parse(key.Name, NumberStyles.HexNumber);
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.",
NotificationType.Warning);
@ -429,7 +431,7 @@ public class DesignBase
{
if (parameters == null)
{
design.ApplyParameters = 0;
design.Application.Parameters = 0;
design.GetDesignDataRef().Parameters = default;
return;
}
@ -490,7 +492,7 @@ public class DesignBase
return true;
}
design.ApplyParameters &= ~flag;
design.Application.Parameters &= ~flag;
design.GetDesignDataRef().Parameters[flag] = CustomizeParameterValue.Zero;
return false;
}
@ -669,11 +671,12 @@ public class DesignBase
{
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
out var writeProtected, out var applyMeta);
ApplyEquip = equipFlags;
ApplyCustomize = customizeFlags;
ApplyParameters = 0;
ApplyCrest = 0;
ApplyMeta = applyMeta;
Application.Equip = equipFlags;
ApplyCustomize = customizeFlags;
Application.Parameters = 0;
Application.Crest = 0;
Application.Meta = applyMeta;
Application.BonusItem = 0;
SetWriteProtected(writeProtected);
CustomizeSet = SetCustomizationSet(customize);
}

View file

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

View file

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

View file

@ -9,24 +9,31 @@ namespace Glamourer.Designs;
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 _nameBody = string.Empty;
private string _nameHands = string.Empty;
private string _nameLegs = string.Empty;
private string _nameFeet = string.Empty;
private string _nameEars = string.Empty;
private string _nameNeck = string.Empty;
private string _nameWrists = string.Empty;
private string _nameRFinger = string.Empty;
private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty;
private string _nameFaceWear = string.Empty;
private fixed uint _itemIds[12];
private fixed uint _iconIds[12];
private fixed byte _equipmentBytes[EquipmentByteSize + 16];
private string _nameHead = string.Empty;
private string _nameBody = string.Empty;
private string _nameHands = string.Empty;
private string _nameLegs = string.Empty;
private string _nameFeet = string.Empty;
private string _nameEars = string.Empty;
private string _nameNeck = string.Empty;
private string _nameWrists = string.Empty;
private string _nameRFinger = string.Empty;
private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty;
private string _nameGlasses = string.Empty;
private fixed uint _itemIds[NumEquipment + NumWeapons];
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 CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId;
@ -52,7 +59,7 @@ public unsafe struct DesignData
|| name.IsContained(_nameLFinger)
|| name.IsContained(_nameMainhand)
|| name.IsContained(_nameOffhand)
|| name.IsContained(_nameFaceWear);
|| name.IsContained(_nameGlasses);
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)
{
fixed (byte* ptr = _equipmentBytes)
@ -134,7 +150,7 @@ public unsafe struct DesignData
public bool SetItem(EquipSlot slot, EquipItem item)
{
var index = slot.ToIndex();
if (index > 11)
if (index > NumEquipment + NumWeapons)
return false;
_itemIds[index] = item.ItemId.Id;
@ -173,6 +189,25 @@ public unsafe struct DesignData
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)
=> slot.ToIndex() switch
{
@ -313,17 +348,17 @@ public unsafe struct DesignData
MemoryUtility.MemSet(ptr, 0, 10 * 2);
}
_nameHead = string.Empty;
_nameBody = string.Empty;
_nameHands = string.Empty;
_nameLegs = string.Empty;
_nameFeet = string.Empty;
_nameEars = string.Empty;
_nameNeck = string.Empty;
_nameWrists = string.Empty;
_nameRFinger = string.Empty;
_nameLFinger = string.Empty;
_nameFaceWear = string.Empty;
_nameHead = string.Empty;
_nameBody = string.Empty;
_nameHands = string.Empty;
_nameLegs = string.Empty;
_nameFeet = string.Empty;
_nameEars = string.Empty;
_nameNeck = string.Empty;
_nameWrists = string.Empty;
_nameRFinger = string.Empty;
_nameLFinger = string.Empty;
_nameGlasses = string.Empty;
return true;
}

View file

@ -165,6 +165,11 @@ public class DesignEditor(
}
}
public void ChangeBonusItem(object data, BonusItemFlag slot, BonusItem item, ApplySettings settings = default)
{
}
/// <inheritdoc/>
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);
}
/// <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>
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)
=> 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>
public void ChangeStains(object data, EquipSlot slot, StainIds stains, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stains, settings);

View file

@ -42,14 +42,15 @@ public class DesignMerger(
if (!data.IsHuman)
continue;
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design);
ReduceMeta(data, applyMeta, ret, source);
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership, startBodyType);
ReduceEquip(data, equipFlags, ret, source, respectOwnership);
ReduceMainhands(data, jobs, equipFlags, ret, source, respectOwnership);
ReduceOffhands(data, jobs, equipFlags, ret, source, respectOwnership);
ReduceCrests(data, crestFlags, ret, source);
ReduceParameters(data, parameterFlags, ret, source);
var collection = type.ApplyWhat(design);
ReduceMeta(data, collection.Meta, ret, source);
ReduceCustomize(data, collection.Customize, ref fixFlags, ret, source, respectOwnership, startBodyType);
ReduceEquip(data, collection.Equip, ret, source, respectOwnership);
ReduceBonusItems(data, collection.BonusItem, ret, source, respectOwnership);
ReduceMainhands(data, jobs, collection.Equip, ret, source, respectOwnership);
ReduceOffhands(data, jobs, collection.Equip, ret, source, respectOwnership);
ReduceCrests(data, collection.Crest, ret, source);
ReduceParameters(data, collection.Parameters, ret, source);
ReduceMods(design as Design, ret, modAssociations);
if (type.HasFlag(ApplicationType.GearCustomization))
ReduceMaterials(design, ret);
@ -83,7 +84,7 @@ public class DesignMerger(
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)
return;
@ -100,7 +101,7 @@ public class DesignMerger(
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)
return;
@ -118,7 +119,7 @@ public class DesignMerger(
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
StateSource source)
{
parameterFlags &= ~ret.Design.ApplyParameters;
parameterFlags &= ~ret.Design.Application.Parameters;
if (parameterFlags == 0)
return;
@ -136,7 +137,7 @@ public class DesignMerger(
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
bool respectOwnership)
{
equipFlags &= ~ret.Design.ApplyEquip;
equipFlags &= ~ret.Design.Application.Equip;
if (equipFlags == 0)
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,
bool respectOwnership)
{

View file

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

View file

@ -15,7 +15,7 @@ namespace Glamourer.Events;
/// </list>
/// </summary>
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
{

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>
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>
ApplyStain,

View file

@ -179,8 +179,7 @@ public sealed class DesignQuickBar : Window, IDisposable
return;
}
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
using var _ = design!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_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

@ -72,4 +72,4 @@ public struct EquipDrawData(EquipSlot slot, in DesignData designData)
GameStains = state.BaseData.Stain(slot),
AllowRevert = true,
};
}
}

View file

@ -24,6 +24,7 @@ public class EquipmentDrawer
private readonly GlamourerColorCombo _stainCombo;
private readonly DictStain _stainData;
private readonly ItemCombo[] _itemCombo;
private readonly BonusItemCombo[] _bonusItemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly CodeService _codes;
private readonly TextureService _textures;
@ -37,16 +38,17 @@ public class EquipmentDrawer
public EquipmentDrawer(FavoriteManager favorites, IDataManager gameData, ItemManager items, CodeService codes, TextureService textures,
Configuration config, GPoseService gPose, AdvancedDyePopup advancedDyes)
{
_items = items;
_codes = codes;
_textures = textures;
_config = config;
_gPose = gPose;
_advancedDyes = advancedDyes;
_stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(gameData, items, e, Glamourer.Log, favorites)).ToArray();
_weaponCombo = new Dictionary<FullEquipType, WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
_items = items;
_codes = codes;
_textures = textures;
_config = config;
_gPose = gPose;
_advancedDyes = advancedDyes;
_stainData = items.Stains;
_stainCombo = new GlamourerColorCombo(DefaultWidth - 20, _stainData, favorites);
_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);
foreach (var type in Enum.GetValues<FullEquipType>())
{
if (type.ToSlot() is EquipSlot.MainHand)
@ -100,6 +102,21 @@ public class EquipmentDrawer
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)
{
if (mainhand.CurrentItem.PrimaryId.Id == 0)
@ -302,6 +319,25 @@ public class EquipmentDrawer
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)
{
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)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing,
@ -491,6 +548,25 @@ public class EquipmentDrawer
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,
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);
}
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)
{
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)
=> DrawButton(MaterialValueIndex.FromSlot(slot));
public void DrawButton(BonusItemFlag slot)
=> DrawButton(MaterialValueIndex.FromSlot(slot));
private void DrawButton(MaterialValueIndex index)
{
if (!config.UseAdvancedDyes)

View file

@ -215,6 +215,12 @@ public class ActorPanel
var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
_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));
DrawEquipmentMetaToggles();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));

View file

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

View file

@ -25,7 +25,7 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
continue;
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());
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.TextWrapped(base64);

View file

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

View file

@ -21,7 +21,7 @@ public unsafe class ModelEvaluationPanel(
UpdateSlotService _updateSlotService,
ChangeCustomizeService _changeCustomizeService,
CrestService _crestService,
DictGlasses _glasses) : IGameDataDrawer
DictBonusItems bonusItems) : IGameDataDrawer
{
public string Label
=> "Model Evaluation";
@ -57,6 +57,16 @@ public unsafe class ModelEvaluationPanel(
ImGui.TextUnformatted($"Transformation Id: {actor.AsCharacter->CharacterData.TransformationId}");
if (actor.AsCharacter->CharacterData.ModelCharaId_2 != -1)
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");
@ -226,7 +236,7 @@ public unsafe class ModelEvaluationPanel(
_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());
ImGuiUtil.DrawTableColumn(slot.ToName());
@ -236,9 +246,9 @@ public unsafe class ModelEvaluationPanel(
}
else
{
var glassesId = actor.GetBonusSlot(slot);
if (_glasses.TryGetValue(glassesId, out var glasses))
ImGuiUtil.DrawTableColumn($"{glasses.Id.Id},{glasses.Variant.Id} ({glassesId})");
var glassesId = actor.GetBonusItem(slot);
if (bonusItems.TryGetValue(glassesId, out var glasses))
ImGuiUtil.DrawTableColumn($"{glasses.ModelId.Id},{glasses.Variant.Id} ({glassesId})");
else
ImGuiUtil.DrawTableColumn($"{glassesId}");
}

View file

@ -112,6 +112,13 @@ public class DesignPanel
var mainhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.MainHand);
var offhand = EquipDrawData.FromDesign(_manager, _selector.Selected!, EquipSlot.OffHand);
_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));
DrawEquipmentMetaToggles();
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
@ -149,7 +156,7 @@ public class DesignPanel
if (!h)
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))
foreach (var idx in Enum.GetValues<CustomizeIndex>())
{
@ -224,7 +231,7 @@ public class DesignPanel
private void DrawCrestApplication()
{
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);
foreach (var flag in CrestExtensions.AllRelevantSet)
{
@ -255,7 +262,7 @@ public class DesignPanel
{
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);
var bigChange = ImGui.CheckboxFlags($"Apply All {label}", ref flags, (uint)allFlags);
if (stain)
@ -302,7 +309,7 @@ public class DesignPanel
{
using var id = ImRaii.PushId("Meta");
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 labels = new[]
@ -324,7 +331,7 @@ public class DesignPanel
private void DrawParameterApplication()
{
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);
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
@ -408,8 +415,7 @@ public class DesignPanel
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks);
}
}
@ -427,8 +433,7 @@ public class DesignPanel
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
using var _ = _selector.Selected!.TemporarilyRestrictApplication(ApplicationCollection.FromKeys());
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.ManualWithLinks);
}
}

View file

@ -1,6 +1,5 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
@ -98,15 +97,6 @@ public static class UiHelpers
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()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{
@ -126,16 +116,36 @@ public static class UiHelpers
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())
{
if (favorite)
favorites.Remove(item);
else
favorites.TryAdd(item);
return true;
}
if (!ImGui.IsItemClicked())
return false;
if (favorite)
favorites.Remove(item);
else
favorites.TryAdd(item);
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;
if (favorite)
favorites.Remove(item);
else
favorites.TryAdd(item);
return true;
return false;
}
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());
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(FontAwesomeIcon.Star.ToIconString());
if (ImGui.IsItemClicked())
{
if (favorite)
favorites.Remove(stain);
else
favorites.TryAdd(stain);
return true;
}
if (!ImGui.IsItemClicked())
return false;
if (favorite)
favorites.Remove(stain);
else
favorites.TryAdd(stain);
return true;
return false;
}
}
}

View file

@ -42,6 +42,12 @@ public readonly record struct MaterialValueIndex(
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()
=> DrawObject switch
{

View file

@ -37,17 +37,13 @@ public class PaletteImport(IDalamudPluginInterface pluginInterface, DesignManage
}
var design = designManager.CreateEmpty(fullPath, true);
design.ApplyCustomize = 0;
design.ApplyEquip = 0;
design.ApplyCrest = 0;
designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false);
designManager.ChangeApplyMeta(design, MetaIndex.HatState, false);
designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false);
design.Application = ApplicationCollection.None;
foreach (var flag in flags.Iterate())
{
designManager.ChangeApplyParameter(design, flag, true);
designManager.ChangeCustomizeParameter(design, flag, palette[flag]);
}
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 BonusSlotUpdating BonusSlotUpdatingEvent;
private readonly DictGlasses _glasses;
private readonly DictBonusItems _bonusItems;
public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, IGameInteropProvider interop,
DictGlasses glasses)
DictBonusItems bonusItems)
{
EquipSlotUpdatingEvent = equipSlotUpdating;
BonusSlotUpdatingEvent = bonusSlotUpdating;
_glasses = glasses;
_bonusItems = bonusItems;
interop.InitializeFromAttributes(this);
_flagSlotForUpdateHook.Enable();
_flagBonusSlotForUpdateHook.Enable();
@ -41,7 +41,7 @@ public unsafe class UpdateSlotService : IDisposable
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)
return;
@ -53,13 +53,13 @@ public unsafe class UpdateSlotService : IDisposable
_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;
var armor = new CharacterArmor(glasses.Id, glasses.Variant, StainIds.None);
_flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusEquipFlag.Glasses.ToIndex(), &armor);
var armor = new CharacterArmor(glasses.ModelId, glasses.Variant, StainIds.None);
_flagBonusSlotForUpdateHook.Original(drawObject.Address, BonusItemFlag.Glasses.ToIndex(), &armor);
}
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 DictStain Stains;
public readonly ItemData ItemData;
public readonly DictBonusItems DictBonusItems;
public readonly RestrictedGear RestrictedGear;
public readonly EquipItem DefaultSword;
public ItemManager(Configuration config, IDataManager gameData, ObjectIdentification objectIdentification,
ItemData itemData, DictStain stains, RestrictedGear restrictedGear)
ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems)
{
_config = config;
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
@ -33,6 +34,7 @@ public class ItemManager
ItemData = itemData;
Stains = stains;
RestrictedGear = restrictedGear;
DictBonusItems = dictBonusItems;
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>
public EquipItem GetDefaultOffhand(EquipItem mainhand)
{
@ -161,6 +179,18 @@ public class ItemManager
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>
/// 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.

View file

@ -151,6 +151,18 @@ public class InternalStateEditor(
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>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainIds stains, StateSource source, out EquipItem oldItem,
out StainIds oldStains, uint key = 0)

View file

@ -125,6 +125,33 @@ public class StateApplier(
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>
/// 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));
}
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/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainIds? stains, ApplySettings settings)
{
@ -226,7 +238,7 @@ public class StateEditor(
out _, settings.Key);
}
var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw;
var customizeFlags = mergedDesign.Design.Application.Customize;
if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan))
customizeFlags |= CustomizeFlag.Race;
@ -245,7 +257,7 @@ public class StateEditor(
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())
continue;
@ -273,6 +285,13 @@ public class StateEditor(
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)
{
if (mergedDesign.Design.DoApplyStain(weaponSlot))

View file

@ -38,6 +38,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
_ => Invalid,
};
public static implicit operator StateIndex(BonusItemFlag flag)
=> flag switch
{
BonusItemFlag.Glasses => new StateIndex(BonusItemGlasses),
_ => Invalid,
};
public static implicit operator StateIndex(CustomizeIndex index)
=> index switch
{
@ -198,23 +205,13 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
public const int ParamFacePaintUvOffset = ParamFacePaintUvMultiplier + 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
=> 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()
=> GetFlag() switch
{
@ -223,6 +220,7 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
MetaFlag m => m.ToIndex().ToName(),
CrestFlag c => c.ToLabel(),
CustomizeParameterFlag c => c.ToName(),
BonusItemFlag b => b.ToName(),
bool v => "Model ID",
_ => "Unknown",
};
@ -317,6 +315,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset,
ParamDecalColor => CustomizeParameterFlag.DecalColor,
BonusItemGlasses => BonusItemFlag.Glasses,
_ => -1,
};
@ -411,6 +411,8 @@ public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIn
ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset],
ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor],
BonusItemGlasses => data.BonusItem(BonusItemFlag.Glasses),
_ => null,
};
}

View file

@ -32,7 +32,8 @@ public class StateListener : IDisposable
private readonly ItemManager _items;
private readonly CustomizeService _customizations;
private readonly PenumbraService _penumbra;
private readonly EquipSlotUpdating _equipSlotUpdating;
private readonly EquipSlotUpdating _equipSlotUpdating;
private readonly BonusSlotUpdating _bonusSlotUpdating;
private readonly WeaponLoading _weaponLoading;
private readonly HeadGearVisibilityChanged _headGearVisibility;
private readonly VisorStateChanged _visorState;
@ -52,17 +53,18 @@ public class StateListener : IDisposable
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config,
EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition, CrestService crestService)
EquipSlotUpdating equipSlotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState,
WeaponVisibilityChanged weaponVisibility, HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier,
FunModule funModule, HumanModelList humans, StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects,
GPoseService gPose, ChangeCustomizeService changeCustomizeService, CustomizeService customizations, ICondition condition,
CrestService crestService, BonusSlotUpdating bonusSlotUpdating)
{
_manager = manager;
_items = items;
_penumbra = penumbra;
_actors = actors;
_config = config;
_equipSlotUpdating = equipSlotUpdating;
_equipSlotUpdating = equipSlotUpdating;
_weaponLoading = weaponLoading;
_visorState = visorState;
_weaponVisibility = weaponVisibility;
@ -78,6 +80,7 @@ public class StateListener : IDisposable
_customizations = customizations;
_condition = condition;
_crestService = crestService;
_bonusSlotUpdating = bonusSlotUpdating;
Subscribe();
}
@ -227,6 +230,35 @@ public class StateListener : IDisposable
(_, 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)
{
_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>
private void HandleEquipSlot(Actor actor, ActorState state, EquipSlot slot, ref CharacterArmor armor)
{
@ -700,6 +754,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase += OnCreatedCharacterBase;
_equipSlotUpdating.Subscribe(OnEquipSlotUpdating, EquipSlotUpdating.Priority.StateListener);
_bonusSlotUpdating.Subscribe(OnBonusSlotUpdating, BonusSlotUpdating.Priority.StateListener);
_movedEquipment.Subscribe(OnMovedEquipment, MovedEquipment.Priority.StateListener);
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
@ -716,6 +771,7 @@ public class StateListener : IDisposable
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
_penumbra.CreatedCharacterBase -= OnCreatedCharacterBase;
_equipSlotUpdating.Unsubscribe(OnEquipSlotUpdating);
_bonusSlotUpdating.Unsubscribe(OnBonusSlotUpdating);
_movedEquipment.Unsubscribe(OnMovedEquipment);
_weaponLoading.Unsubscribe(OnWeaponLoading);
_visorState.Unsubscribe(OnVisorChange);

View file

@ -160,6 +160,13 @@ public sealed class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet)
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
{
@ -181,6 +188,13 @@ public sealed class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet)
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.
@ -241,6 +255,9 @@ public sealed class StateManager(
state.Sources[slot, false] = StateSource.Game;
}
foreach (var slot in BonusExtensions.AllFlags)
state.Sources[slot] = StateSource.Game;
foreach (var type in Enum.GetValues<MetaIndex>())
state.Sources[type] = StateSource.Game;
@ -328,6 +345,12 @@ public sealed class StateManager(
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;
Applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
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)
{
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<StainId> _favoriteColors = [];
private readonly HashSet<FavoriteHairStyle> _favoriteHairStyles = [];
private readonly HashSet<BonusItemId> _favoriteBonusItems = [];
public FavoriteManager(SaveService saveService)
{
@ -62,6 +63,7 @@ public class FavoriteManager : ISavable
_favorites.UnionWith(load!.FavoriteItems.Select(i => (ItemId)i));
_favoriteColors.UnionWith(load!.FavoriteColors.Select(i => (StainId)i));
_favoriteHairStyles.UnionWith(load!.FavoriteHairStyles.Select(t => new FavoriteHairStyle(t)));
_favoriteBonusItems.UnionWith(load!.FavoriteBonusItems.Select(b => new BonusItemId(b)));
break;
default: throw new Exception($"Unknown Version {load?.Version ?? 0}");
@ -109,6 +111,11 @@ public class FavoriteManager : ISavable
foreach (var hairStyle in _favoriteHairStyles)
j.WriteValue(hairStyle.ToValue());
j.WriteEndArray();
j.WriteStartArray();
j.WritePropertyName(nameof(LoadIntermediary.FavoriteBonusItems));
foreach (var item in _favoriteBonusItems)
j.WriteValue(item.Id);
j.WriteEndArray();
j.WriteEndObject();
}
@ -124,9 +131,6 @@ public class FavoriteManager : ISavable
return true;
}
public bool TryAdd(Stain stain)
=> TryAdd(stain.RowIndex);
public bool TryAdd(StainId stain)
{
if (stain.Id == 0 || !_favoriteColors.Add(stain))
@ -136,6 +140,15 @@ public class FavoriteManager : ISavable
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)
{
if (!TypeAllowed(type) || !_favoriteHairStyles.Add(new FavoriteHairStyle(gender, race, type, value)))
@ -157,9 +170,6 @@ public class FavoriteManager : ISavable
return true;
}
public bool Remove(Stain stain)
=> Remove(stain.RowIndex);
public bool Remove(StainId stain)
{
if (!_favoriteColors.Remove(stain))
@ -169,6 +179,15 @@ public class FavoriteManager : ISavable
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)
{
if (!_favoriteHairStyles.Remove(new FavoriteHairStyle(gender, race, type, value)))
@ -181,23 +200,21 @@ public class FavoriteManager : ISavable
public bool Contains(EquipItem item)
=> _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)
=> _favoriteColors.Contains(stain);
public bool Contains(BonusItem bonusItem)
=> _favoriteBonusItems.Contains(bonusItem.Id);
public bool Contains(Gender gender, SubRace race, CustomizeIndex type, CustomizeValue value)
=> _favoriteHairStyles.Contains(new FavoriteHairStyle(gender, race, type, value));
private class LoadIntermediary
{
public int Version = CurrentVersion;
public uint[] FavoriteItems = [];
public byte[] FavoriteColors = [];
public uint[] FavoriteHairStyles = [];
public int Version = CurrentVersion;
public uint[] FavoriteItems = [];
public byte[] FavoriteColors = [];
public uint[] FavoriteHairStyles = [];
public ushort[] FavoriteBonusItems = [];
}
}

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