This commit is contained in:
Ottermandias 2024-01-20 15:13:23 +01:00
parent 1a409d475a
commit c7430e59b3
35 changed files with 1118 additions and 376 deletions

View file

@ -0,0 +1,62 @@
using Glamourer.Designs;
using Glamourer.GameData;
using Penumbra.GameData.Enums;
namespace Glamourer.Automation;
[Flags]
public enum ApplicationType : byte
{
Armor = 0x01,
Customizations = 0x02,
Weapons = 0x04,
GearCustomization = 0x08,
Accessories = 0x10,
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
}
public static class ApplicationTypeExtensions
{
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor,
bool
ApplyWeapon, bool ApplyWet) ApplyWhat(this ApplicationType type, DesignBase? design)
{
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
| (type.HasFlag(ApplicationType.Accessories) ? AccessoryFlags : 0)
| (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;
if (design == null)
return (equipFlags, customizeFlags, crestFlag, parameterFlags, type.HasFlag(ApplicationType.Armor),
type.HasFlag(ApplicationType.Armor),
type.HasFlag(ApplicationType.Weapons), type.HasFlag(ApplicationType.Customizations));
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
parameterFlags & design.ApplyParameters,
type.HasFlag(ApplicationType.Armor) && design.DoApplyHatVisible(),
type.HasFlag(ApplicationType.Armor) && design.DoApplyVisorToggle(),
type.HasFlag(ApplicationType.Weapons) && design.DoApplyWeaponVisible(),
type.HasFlag(ApplicationType.Customizations) && design.DoApplyWetness());
}
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;
public const EquipFlag StainFlags = EquipFlag.MainhandStain
| EquipFlag.OffhandStain
| EquipFlag.HeadStain
| EquipFlag.BodyStain
| EquipFlag.HandsStain
| EquipFlag.LegsStain
| EquipFlag.FeetStain
| EquipFlag.EarsStain
| EquipFlag.NeckStain
| EquipFlag.WristStain
| EquipFlag.RFingerStain
| EquipFlag.LFingerStain;
}

View file

@ -12,22 +12,10 @@ public class AutoDesign
{ {
public const string RevertName = "Revert"; public const string RevertName = "Revert";
[Flags] public Design? Design;
public enum Type : byte public JobGroup Jobs;
{ public ApplicationType Type;
Armor = 0x01, public short GearsetIndex = -1;
Customizations = 0x02,
Weapons = 0x04,
GearCustomization = 0x08,
Accessories = 0x10,
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
}
public Design? Design;
public JobGroup Jobs;
public Type ApplicationType;
public short GearsetIndex = -1;
public string Name(bool incognito) public string Name(bool incognito)
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text; => Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
@ -41,10 +29,10 @@ public class AutoDesign
public AutoDesign Clone() public AutoDesign Clone()
=> new() => new()
{ {
Design = Design, Design = Design,
ApplicationType = ApplicationType, Type = Type,
Jobs = Jobs, Jobs = Jobs,
GearsetIndex = GearsetIndex, GearsetIndex = GearsetIndex,
}; };
public unsafe bool IsActive(Actor actor) public unsafe bool IsActive(Actor actor)
@ -64,9 +52,9 @@ public class AutoDesign
public JObject Serialize() public JObject Serialize()
=> new() => new()
{ {
["Design"] = Design?.Identifier.ToString(), ["Design"] = Design?.Identifier.ToString(),
["ApplicationType"] = (uint)ApplicationType, ["Type"] = (uint)Type,
["Conditions"] = CreateConditionObject(), ["Conditions"] = CreateConditionObject(),
}; };
private JObject CreateConditionObject() private JObject CreateConditionObject()
@ -82,42 +70,5 @@ public class AutoDesign
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool
ApplyWeapon, bool ApplyWet) ApplyWhat() ApplyWeapon, bool ApplyWet) ApplyWhat()
{ => Type.ApplyWhat(Design);
var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0)
| (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0)
| (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0)
| (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0);
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0;
var parameterFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeParameterExtensions.All : 0;
var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0;
if (Revert)
return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor),
ApplicationType.HasFlag(Type.Armor),
ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations));
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest,
parameterFlags & Design.ApplyParameters,
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(),
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(),
ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(),
ApplicationType.HasFlag(Type.Customizations) && Design.DoApplyWetness());
}
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;
public const EquipFlag StainFlags = EquipFlag.MainhandStain
| EquipFlag.OffhandStain
| EquipFlag.HeadStain
| EquipFlag.BodyStain
| EquipFlag.HandsStain
| EquipFlag.LegsStain
| EquipFlag.FeetStain
| EquipFlag.EarsStain
| EquipFlag.NeckStain
| EquipFlag.WristStain
| EquipFlag.RFingerStain
| EquipFlag.LFingerStain;
} }

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
@ -15,7 +16,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Automation; namespace Glamourer.Automation;
public class AutoDesignApplier : IDisposable public sealed class AutoDesignApplier : IDisposable
{ {
private readonly Configuration _config; private readonly Configuration _config;
private readonly AutoDesignManager _manager; private readonly AutoDesignManager _manager;
@ -30,6 +31,7 @@ public class AutoDesignApplier : IDisposable
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons; private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState; private readonly IClientState _clientState;
private ActorState? _jobChangeState; private ActorState? _jobChangeState;
@ -182,7 +184,7 @@ public class AutoDesignApplier : IDisposable
} }
else if (_state.TryGetValue(id, out var state)) else if (_state.TryGetValue(id, out var state))
{ {
state.RemoveFixedDesignSources(); state.Source.RemoveFixedDesignSources();
} }
} }
} }
@ -196,9 +198,9 @@ public class AutoDesignApplier : IDisposable
{ {
if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld) if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld)
foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value)) foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value))
state.RemoveFixedDesignSources(); state.Source.RemoveFixedDesignSources();
else if (_state.TryGetValue(id, out var state)) else if (_state.TryGetValue(id, out var state))
state.RemoveFixedDesignSources(); state.Source.RemoveFixedDesignSources();
} }
} }
} }
@ -276,7 +278,7 @@ public class AutoDesignApplier : IDisposable
if (set.BaseState == AutoDesignSet.Base.Game) if (set.BaseState == AutoDesignSet.Base.Game)
_state.ResetStateFixed(state, respectManual); _state.ResetStateFixed(state, respectManual);
else if (!respectManual) else if (!respectManual)
state.RemoveFixedDesignSources(); state.Source.RemoveFixedDesignSources();
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
return; return;
@ -286,7 +288,7 @@ public class AutoDesignApplier : IDisposable
if (!design.IsActive(actor)) if (!design.IsActive(actor))
continue; continue;
if (design.ApplicationType is 0) if (design.Type is 0)
continue; continue;
ref readonly var data = ref design.GetDesignData(state); ref readonly var data = ref design.GetDesignData(state);
@ -342,7 +344,7 @@ public class AutoDesignApplier : IDisposable
if (!crestFlags.HasFlag(slot)) if (!crestFlags.HasFlag(slot))
continue; continue;
if (!respectManual || state[slot] is not StateChanged.Source.Manual) if (!respectManual || state.Source[slot] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, slot, design.Crest(slot), source); _state.ChangeCrest(state, slot, design.Crest(slot), source);
totalCrestFlags |= slot; totalCrestFlags |= slot;
} }
@ -360,7 +362,7 @@ public class AutoDesignApplier : IDisposable
if (!parameterFlags.HasFlag(flag)) if (!parameterFlags.HasFlag(flag))
continue; continue;
if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending) if (!respectManual || state.Source[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending)
_state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source); _state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source);
totalParameterFlags |= flag; totalParameterFlags |= flag;
} }
@ -381,7 +383,7 @@ public class AutoDesignApplier : IDisposable
var item = design.Item(slot); var item = design.Item(slot);
if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _)) if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _))
{ {
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual) if (!respectManual || state.Source[slot, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, item, source); _state.ChangeItem(state, slot, item, source);
totalEquipFlags |= flag; totalEquipFlags |= flag;
} }
@ -390,7 +392,7 @@ public class AutoDesignApplier : IDisposable
var stainFlag = slot.ToStainFlag(); var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag)) if (equipFlags.HasFlag(stainFlag))
{ {
if (!respectManual || state[slot, true] is not StateChanged.Source.Manual) if (!respectManual || state.Source[slot, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, slot, design.Stain(slot), source); _state.ChangeStain(state, slot, design.Stain(slot), source);
totalEquipFlags |= stainFlag; totalEquipFlags |= stainFlag;
} }
@ -400,7 +402,7 @@ public class AutoDesignApplier : IDisposable
{ {
var item = design.Item(EquipSlot.MainHand); var item = design.Item(EquipSlot.MainHand);
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _);
var checkState = !respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual; var checkState = !respectManual || state.Source[EquipSlot.MainHand, false] is not StateChanged.Source.Manual;
if (checkUnlock && checkState) if (checkUnlock && checkState)
{ {
if (fromJobChange) if (fromJobChange)
@ -420,7 +422,7 @@ public class AutoDesignApplier : IDisposable
{ {
var item = design.Item(EquipSlot.OffHand); var item = design.Item(EquipSlot.OffHand);
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _); var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _);
var checkState = !respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual; var checkState = !respectManual || state.Source[EquipSlot.OffHand, false] is not StateChanged.Source.Manual;
if (checkUnlock && checkState) if (checkUnlock && checkState)
{ {
if (fromJobChange) if (fromJobChange)
@ -438,14 +440,14 @@ public class AutoDesignApplier : IDisposable
if (equipFlags.HasFlag(EquipFlag.MainhandStain)) if (equipFlags.HasFlag(EquipFlag.MainhandStain))
{ {
if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual) if (!respectManual || state.Source[EquipSlot.MainHand, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source);
totalEquipFlags |= EquipFlag.MainhandStain; totalEquipFlags |= EquipFlag.MainhandStain;
} }
if (equipFlags.HasFlag(EquipFlag.OffhandStain)) if (equipFlags.HasFlag(EquipFlag.OffhandStain))
{ {
if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual) if (!respectManual || state.Source[EquipSlot.OffHand, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source); _state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source);
totalEquipFlags |= EquipFlag.OffhandStain; totalEquipFlags |= EquipFlag.OffhandStain;
} }
@ -467,7 +469,7 @@ public class AutoDesignApplier : IDisposable
if (customizeFlags.HasFlag(CustomizeFlag.Clan)) if (customizeFlags.HasFlag(CustomizeFlag.Clan))
{ {
if (!respectManual || state[CustomizeIndex.Clan] is not StateChanged.Source.Manual) if (!respectManual || state.Source[CustomizeIndex.Clan] is not StateChanged.Source.Manual)
fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan); fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan);
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race); customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race; totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race;
@ -475,7 +477,7 @@ public class AutoDesignApplier : IDisposable
if (customizeFlags.HasFlag(CustomizeFlag.Gender)) if (customizeFlags.HasFlag(CustomizeFlag.Gender))
{ {
if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual) if (!respectManual || state.Source[CustomizeIndex.Gender] is not StateChanged.Source.Manual)
fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender); fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender);
customizeFlags &= ~CustomizeFlag.Gender; customizeFlags &= ~CustomizeFlag.Gender;
totalCustomizeFlags |= CustomizeFlag.Gender; totalCustomizeFlags |= CustomizeFlag.Gender;
@ -486,7 +488,7 @@ public class AutoDesignApplier : IDisposable
if (customizeFlags.HasFlag(CustomizeFlag.Face)) if (customizeFlags.HasFlag(CustomizeFlag.Face))
{ {
if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual) if (!respectManual || state.Source[CustomizeIndex.Face] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source); _state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source);
customizeFlags &= ~CustomizeFlag.Face; customizeFlags &= ~CustomizeFlag.Face;
totalCustomizeFlags |= CustomizeFlag.Face; totalCustomizeFlags |= CustomizeFlag.Face;
@ -506,7 +508,7 @@ public class AutoDesignApplier : IDisposable
if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _))
continue; continue;
if (!respectManual || state[index] is not StateChanged.Source.Manual) if (!respectManual || state.Source[index] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, index, value, source); _state.ChangeCustomize(state, index, value, source);
totalCustomizeFlags |= flag; totalCustomizeFlags |= flag;
} }
@ -518,28 +520,28 @@ public class AutoDesignApplier : IDisposable
{ {
if (applyHat && (totalMetaFlags & 0x01) == 0) if (applyHat && (totalMetaFlags & 0x01) == 0)
{ {
if (!respectManual || state[ActorState.MetaIndex.HatState] is not StateChanged.Source.Manual) if (!respectManual || state.Source[MetaIndex.HatState] is not StateChanged.Source.Manual)
_state.ChangeHatState(state, design.IsHatVisible(), source); _state.ChangeHatState(state, design.IsHatVisible(), source);
totalMetaFlags |= 0x01; totalMetaFlags |= 0x01;
} }
if (applyVisor && (totalMetaFlags & 0x02) == 0) if (applyVisor && (totalMetaFlags & 0x02) == 0)
{ {
if (!respectManual || state[ActorState.MetaIndex.VisorState] is not StateChanged.Source.Manual) if (!respectManual || state.Source[MetaIndex.VisorState] is not StateChanged.Source.Manual)
_state.ChangeVisorState(state, design.IsVisorToggled(), source); _state.ChangeVisorState(state, design.IsVisorToggled(), source);
totalMetaFlags |= 0x02; totalMetaFlags |= 0x02;
} }
if (applyWeapon && (totalMetaFlags & 0x04) == 0) if (applyWeapon && (totalMetaFlags & 0x04) == 0)
{ {
if (!respectManual || state[ActorState.MetaIndex.WeaponState] is not StateChanged.Source.Manual) if (!respectManual || state.Source[MetaIndex.WeaponState] is not StateChanged.Source.Manual)
_state.ChangeWeaponState(state, design.IsWeaponVisible(), source); _state.ChangeWeaponState(state, design.IsWeaponVisible(), source);
totalMetaFlags |= 0x04; totalMetaFlags |= 0x04;
} }
if (applyWet && (totalMetaFlags & 0x08) == 0) if (applyWet && (totalMetaFlags & 0x08) == 0)
{ {
if (!respectManual || state[ActorState.MetaIndex.Wetness] is not StateChanged.Source.Manual) if (!respectManual || state.Source[MetaIndex.Wetness] is not StateChanged.Source.Manual)
_state.ChangeWetness(state, design.IsWet(), source); _state.ChangeWetness(state, design.IsWet(), source);
totalMetaFlags |= 0x08; totalMetaFlags |= 0x08;
} }

View file

@ -232,7 +232,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var newDesign = new AutoDesign() var newDesign = new AutoDesign()
{ {
Design = design, Design = design,
ApplicationType = AutoDesign.Type.All, Type = ApplicationType.All,
Jobs = _jobs.JobGroups[1], Jobs = _jobs.JobGroups[1],
}; };
set.Designs.Add(newDesign); set.Designs.Add(newDesign);
@ -328,21 +328,21 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index)); _event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index));
} }
public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type) public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType)
{ {
if (which >= set.Designs.Count || which < 0) if (which >= set.Designs.Count || which < 0)
return; return;
type &= AutoDesign.Type.All; applicationType &= ApplicationType.All;
var design = set.Designs[which]; var design = set.Designs[which];
if (design.ApplicationType == type) if (design.Type == applicationType)
return; return;
var old = design.ApplicationType; var old = design.Type;
design.ApplicationType = type; design.Type = applicationType;
Save(); Save();
Glamourer.Log.Debug($"Changed application type from {old} to {type} for associated design {which + 1} in design set."); Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set.");
_event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, type)); _event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType));
} }
public string ToFilename(FilenameService fileNames) public string ToFilename(FilenameService fileNames)
@ -490,12 +490,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
} }
} }
var applicationType = (AutoDesign.Type)(jObj["ApplicationType"]?.ToObject<uint>() ?? 0); // ApplicationType is a migration from an older property name.
var applicationType = (ApplicationType)(jObj["Type"]?.ToObject<uint>() ?? jObj["ApplicationType"]?.ToObject<uint>() ?? 0);
var ret = new AutoDesign() var ret = new AutoDesign
{ {
Design = design, Design = design,
ApplicationType = applicationType & AutoDesign.Type.All, Type = applicationType & ApplicationType.All,
}; };
return ParseConditions(setName, jObj, ret) ? ret : null; return ParseConditions(setName, jObj, ret) ? ret : null;
} }
@ -550,7 +551,24 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private ActorIdentifier[] GetGroup(ActorIdentifier identifier) private ActorIdentifier[] GetGroup(ActorIdentifier identifier)
{ {
if (!identifier.IsValid) if (!identifier.IsValid)
return Array.Empty<ActorIdentifier>(); return [];
return identifier.Type switch
{
IdentifierType.Player =>
[
identifier.CreatePermanent(),
],
IdentifierType.Retainer =>
[
_actors.CreateRetainer(identifier.PlayerName,
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
? ActorIdentifier.RetainerType.Mannequin
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
],
IdentifierType.Npc => CreateNpcs(_actors, identifier),
_ => [],
};
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier) static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
{ {
@ -566,23 +584,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
identifier.Kind, identifier.Kind,
kvp.Key)).ToArray(); kvp.Key)).ToArray();
} }
return identifier.Type switch
{
IdentifierType.Player => new[]
{
identifier.CreatePermanent(),
},
IdentifierType.Retainer => new[]
{
_actors.CreateRetainer(identifier.PlayerName,
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
? ActorIdentifier.RetainerType.Mannequin
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
},
IdentifierType.Npc => CreateNpcs(_actors, identifier),
_ => Array.Empty<ActorIdentifier>(),
};
} }
private void OnDesignChange(DesignChanged.Type type, Design design, object? data) private void OnDesignChange(DesignChanged.Type type, Design design, object? data)

View file

@ -3,12 +3,12 @@ using Penumbra.GameData.Actors;
namespace Glamourer.Automation; namespace Glamourer.Automation;
public class AutoDesignSet public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<AutoDesign> designs)
{ {
public readonly List<AutoDesign> Designs; public readonly List<AutoDesign> Designs = designs;
public string Name; public string Name = name;
public ActorIdentifier[] Identifiers; public ActorIdentifier[] Identifiers = identifiers;
public bool Enabled; public bool Enabled;
public Base BaseState = Base.Current; public Base BaseState = Base.Current;
@ -32,13 +32,6 @@ public class AutoDesignSet
: this(name, identifiers, new List<AutoDesign>()) : this(name, identifiers, new List<AutoDesign>())
{ } { }
public AutoDesignSet(string name, ActorIdentifier[] identifiers, List<AutoDesign> designs)
{
Name = name;
Identifiers = identifiers;
Designs = designs;
}
public enum Base : byte public enum Base : byte
{ {
Current, Current,

View file

@ -56,7 +56,7 @@ public class FixedDesignMigrator(JobService jobs)
autoManager.AddDesign(set, leaf.Value); autoManager.AddDesign(set, leaf.Value);
autoManager.ChangeJobCondition(set, set.Designs.Count - 1, design.Item2); autoManager.ChangeJobCondition(set, set.Designs.Count - 1, design.Item2);
autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? AutoDesign.Type.All : 0); autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? ApplicationType.All : 0);
} }
} }
} }

View file

@ -1,4 +1,6 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Automation;
using Glamourer.Designs.Links;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -35,10 +37,11 @@ public sealed class Design : DesignBase, ISavable
public DateTimeOffset LastEdit { get; internal set; } public DateTimeOffset LastEdit { get; internal set; }
public LowerString Name { get; internal set; } = LowerString.Empty; public LowerString Name { get; internal set; } = LowerString.Empty;
public string Description { get; internal set; } = string.Empty; public string Description { get; internal set; } = string.Empty;
public string[] Tags { get; internal set; } = Array.Empty<string>(); public string[] Tags { get; internal set; } = [];
public int Index { get; internal set; } public int Index { get; internal set; }
public string Color { get; internal set; } = string.Empty; public string Color { get; internal set; } = string.Empty;
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = new(); public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
public LinkContainer Links { get; private set; } = [];
public string Incognito public string Incognito
=> Identifier.ToString()[..8]; => Identifier.ToString()[..8];
@ -64,6 +67,7 @@ public sealed class Design : DesignBase, ISavable
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(), ["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(), ["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(),
}; };
return ret; return ret;
} }
@ -95,24 +99,18 @@ public sealed class Design : DesignBase, ISavable
#region Deserialization #region Deserialization
public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json) public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
{ {
var version = json["FileVersion"]?.ToObject<int>() ?? 0; var version = json["FileVersion"]?.ToObject<int>() ?? 0;
return version switch return version switch
{ {
FileVersion => LoadDesignV1(customizations, items, json), FileVersion => LoadDesignV1(customizations, items, linkLoader, json),
_ => throw new Exception("The design to be loaded has no valid Version."), _ => throw new Exception("The design to be loaded has no valid Version."),
}; };
} }
private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json) private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
{ {
static string[] ParseTags(JObject json)
{
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
return tags.OrderBy(t => t).Distinct().ToArray();
}
var creationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate"); var creationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
var design = new Design(customizations, items) var design = new Design(customizations, items)
@ -131,8 +129,15 @@ public sealed class Design : DesignBase, ISavable
LoadEquip(items, json["Equipment"], design, design.Name, true); LoadEquip(items, json["Equipment"], design, design.Name, true);
LoadMods(json["Mods"], design); LoadMods(json["Mods"], design);
LoadParameters(json["Parameters"], design, design.Name); LoadParameters(json["Parameters"], design, design.Name);
LoadLinks(linkLoader, json["Links"], design);
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty; design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
return design; return design;
static string[] ParseTags(JObject json)
{
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
return tags.OrderBy(t => t).Distinct().ToArray();
}
} }
private static void LoadMods(JToken? mods, Design design) private static void LoadMods(JToken? mods, Design design)
@ -161,6 +166,29 @@ public sealed class Design : DesignBase, ISavable
} }
} }
private static void LoadLinks(DesignLinkLoader linkLoader, JToken? links, Design design)
{
if (links is not JObject obj)
return;
Parse(obj["Before"] as JArray, LinkOrder.Before);
Parse(obj["After"] as JArray, LinkOrder.After);
return;
void Parse(JArray? array, LinkOrder order)
{
if (array == null)
return;
foreach (var obj in array.OfType<JObject>())
{
var identifier = obj["Design"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Design");
var type = (ApplicationType)(obj["Type"]?.ToObject<uint>() ?? 0);
linkLoader.AddObject(design, new LinkData(identifier, type, order));
}
}
}
#endregion #endregion
#region ISavable #region ISavable

View file

@ -1,4 +1,5 @@
using Glamourer.GameData; using Glamourer.Designs.Links;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Utility; using Glamourer.Utility;
@ -10,7 +11,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans) public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader)
{ {
public const byte Version = 6; public const byte Version = 6;
@ -75,7 +76,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
case (byte)'{': case (byte)'{':
var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes)); var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes));
ret = jObj1["Identifier"] != null ret = jObj1["Identifier"] != null
? Design.LoadDesign(_customize, _items, jObj1) ? Design.LoadDesign(_customize, _items, _linkLoader, jObj1)
: DesignBase.LoadDesignBase(_customize, _items, jObj1); : DesignBase.LoadDesignBase(_customize, _items, jObj1);
break; break;
case 1: case 1:
@ -90,7 +91,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
var jObj2 = JObject.Parse(decompressed); var jObj2 = JObject.Parse(decompressed);
Debug.Assert(version == 3); Debug.Assert(version == 3);
ret = jObj2["Identifier"] != null ret = jObj2["Identifier"] != null
? Design.LoadDesign(_customize, _items, jObj2) ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
: DesignBase.LoadDesignBase(_customize, _items, jObj2); : DesignBase.LoadDesignBase(_customize, _items, jObj2);
break; break;
} }
@ -101,7 +102,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
var jObj2 = JObject.Parse(decompressed); var jObj2 = JObject.Parse(decompressed);
Debug.Assert(version == 5); Debug.Assert(version == 5);
ret = jObj2["Identifier"] != null ret = jObj2["Identifier"] != null
? Design.LoadDesign(_customize, _items, jObj2) ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
: DesignBase.LoadDesignBase(_customize, _items, jObj2); : DesignBase.LoadDesignBase(_customize, _items, jObj2);
break; break;
} }
@ -111,7 +112,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
var jObj2 = JObject.Parse(decompressed); var jObj2 = JObject.Parse(decompressed);
Debug.Assert(version == 6); Debug.Assert(version == 6);
ret = jObj2["Identifier"] != null ret = jObj2["Identifier"] != null
? Design.LoadDesign(_customize, _items, jObj2) ? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
: DesignBase.LoadDesignBase(_customize, _items, jObj2); : DesignBase.LoadDesignBase(_customize, _items, jObj2);
break; break;
} }

View file

@ -1,4 +1,5 @@
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Designs.Links;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData; using Glamourer.GameData;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
@ -20,30 +21,33 @@ public class DesignManager
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly DesignChanged _event; private readonly DesignChanged _event;
private readonly List<Design> _designs = []; private readonly DesignStorage _designs;
private readonly Dictionary<Guid, DesignData> _undoStore = []; private readonly Dictionary<Guid, DesignData> _undoStore = [];
public IReadOnlyList<Design> Designs public IReadOnlyList<Design> Designs
=> _designs; => _designs;
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations, public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
DesignChanged @event, HumanModelList humans) DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader)
{ {
_saveService = saveService; _designs = storage;
_items = items; _saveService = saveService;
_customizations = customizations; _items = items;
_event = @event; _customizations = customizations;
_humans = humans; _event = @event;
_humans = humans;
LoadDesigns(designLinkLoader);
CreateDesignFolder(saveService); CreateDesignFolder(saveService);
LoadDesigns();
MigrateOldDesigns(); MigrateOldDesigns();
designLinkLoader.SetAllObjects();
} }
/// <summary> /// <summary>
/// Clear currently loaded designs and load all designs anew from file. /// Clear currently loaded designs and load all designs anew from file.
/// Invalid data is fixed, but changes are not saved until manual changes. /// Invalid data is fixed, but changes are not saved until manual changes.
/// </summary> /// </summary>
public void LoadDesigns() public void LoadDesigns(DesignLinkLoader linkLoader)
{ {
_humans.Awaiter.Wait(); _humans.Awaiter.Wait();
_customizations.Awaiter.Wait(); _customizations.Awaiter.Wait();
@ -59,7 +63,7 @@ public class DesignManager
{ {
var text = File.ReadAllText(f.FullName); var text = File.ReadAllText(f.FullName);
var data = JObject.Parse(text); var data = JObject.Parse(text);
var design = Design.LoadDesign(_customizations, _items, data); var design = Design.LoadDesign(_customizations, _items, linkLoader, data);
designs.Value!.Add((design, f.FullName)); designs.Value!.Add((design, f.FullName));
} }
catch (Exception ex) catch (Exception ex)
@ -497,14 +501,14 @@ public class DesignManager
} }
/// <summary> Change the bool value of one of the meta flags. </summary> /// <summary> Change the bool value of one of the meta flags. </summary>
public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value) public void ChangeMeta(Design design, MetaIndex metaIndex, bool value)
{ {
var change = metaIndex switch var change = metaIndex switch
{ {
ActorState.MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value), MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value),
ActorState.MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value), MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value),
ActorState.MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value), MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value),
ActorState.MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value), MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value),
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
}; };
if (!change) if (!change)
@ -517,14 +521,14 @@ public class DesignManager
} }
/// <summary> Change the application value of one of the meta flags. </summary> /// <summary> Change the application value of one of the meta flags. </summary>
public void ChangeApplyMeta(Design design, ActorState.MetaIndex metaIndex, bool value) public void ChangeApplyMeta(Design design, MetaIndex metaIndex, bool value)
{ {
var change = metaIndex switch var change = metaIndex switch
{ {
ActorState.MetaIndex.Wetness => design.SetApplyWetness(value), MetaIndex.Wetness => design.SetApplyWetness(value),
ActorState.MetaIndex.HatState => design.SetApplyHatVisible(value), MetaIndex.HatState => design.SetApplyHatVisible(value),
ActorState.MetaIndex.VisorState => design.SetApplyVisorToggle(value), MetaIndex.VisorState => design.SetApplyVisorToggle(value),
ActorState.MetaIndex.WeaponState => design.SetApplyWeaponVisible(value), MetaIndex.WeaponState => design.SetApplyWeaponVisible(value),
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null), _ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
}; };
if (!change) if (!change)

View file

@ -0,0 +1,6 @@
using OtterGui.Services;
namespace Glamourer.Designs;
public class DesignStorage : List<Design>, IService
{}

View file

@ -0,0 +1,18 @@
using Glamourer.Automation;
namespace Glamourer.Designs.Links;
public record struct DesignLink(Design Link, ApplicationType Type);
public readonly record struct LinkData(Guid Identity, ApplicationType Type, LinkOrder Order)
{
public override string ToString()
=> Identity.ToString();
}
public enum LinkOrder : byte
{
Self,
After,
Before,
};

View file

@ -0,0 +1,27 @@
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Services;
namespace Glamourer.Designs.Links;
public sealed class DesignLinkLoader(DesignStorage designStorage, MessageService messager)
: DelayedReferenceLoader<Design, LinkData>(messager), IService
{
protected override bool TryGetObject(LinkData data, [NotNullWhen(true)] out Design? obj)
=> designStorage.FindFirst(d => d.Identifier == data.Identity, out obj);
protected override bool SetObject(Design parent, Design child, LinkData data, out string error)
=> LinkContainer.AddLink(parent, child, data.Type, data.Order, out error);
protected override void HandleChildNotFound(Design parent, LinkData data)
{
Messager.AddMessage(new Notification(
$"Could not find the design {data.Identity}. If this design was deleted, please re-save {parent.Identifier}.",
NotificationType.Warning));
}
protected override void HandleChildNotSet(Design parent, Design child, string error)
=> Messager.AddMessage(new Notification($"Could not link {child.Identifier} to {parent.Identifier}: {error}",
NotificationType.Warning));
}

View file

@ -0,0 +1,74 @@
using Glamourer.Automation;
using Glamourer.Events;
using Glamourer.Services;
using OtterGui.Services;
namespace Glamourer.Designs.Links;
public sealed class DesignLinkManager : IService, IDisposable
{
private readonly DesignStorage _storage;
private readonly DesignChanged _event;
private readonly SaveService _saveService;
public DesignLinkManager(DesignStorage storage, DesignChanged @event, SaveService saveService)
{
_storage = storage;
_event = @event;
_saveService = saveService;
_event.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignLinkManager);
}
public void Dispose()
=> _event.Unsubscribe(OnDesignChanged);
public void MoveDesignLink(Design parent, int idxFrom, LinkOrder orderFrom, int idxTo, LinkOrder orderTo)
{
if (!parent.Links.Reorder(idxFrom, orderFrom, idxTo, orderTo))
return;
parent.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(parent);
Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}.");
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
}
public void AddDesignLink(Design parent, Design child, LinkOrder order)
{
if (!LinkContainer.AddLink(parent, child, ApplicationType.All, order, out _))
return;
parent.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(parent);
Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}.");
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
}
public void RemoveDesignLink(Design parent, int idx, LinkOrder order)
{
if (!parent.Links.Remove(idx, order))
return;
parent.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(parent);
Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}.");
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
}
private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _)
{
if (type is not DesignChanged.Type.Deleted)
return;
foreach (var design in _storage)
{
if (design.Links.Remove(deletedDesign))
{
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion.");
_saveService.QueueSave(design);
}
}
}
}

View file

@ -0,0 +1,265 @@
using Glamourer.Automation;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs.Links;
using WeaponDict = Dictionary<FullEquipType, (EquipItem, StateChanged.Source)>;
public sealed class MergedDesign
{
public MergedDesign(DesignManager designManager)
{
Design = designManager.CreateTemporary();
Design.ApplyEquip = 0;
Design.ApplyCustomize = 0;
Design.ApplyCrest = 0;
Design.ApplyParameters = 0;
Design.SetApplyWetness(false);
Design.SetApplyVisorToggle(false);
Design.SetApplyWeaponVisible(false);
Design.SetApplyHatVisible(false);
}
public readonly DesignBase Design;
public readonly WeaponDict Weapons = new(4);
public readonly StateSource Source = new();
}
public class DesignMerger(DesignManager designManager, CustomizeService _customize)
{
public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef)
{
var ret = new MergedDesign(designManager);
CustomizeFlag fixFlags = 0;
foreach (var (design, type) in designs)
{
if (type is 0)
continue;
ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef();
var source = design == null ? StateChanged.Source.Game : StateChanged.Source.Manual;
if (!data.IsHuman)
continue;
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = type.ApplyWhat(design);
ReduceMeta(data, applyHat, applyVisor, applyWeapon, applyWet, ret, source);
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source);
ReduceEquip(data, equipFlags, ret, source);
ReduceMainhands(data, equipFlags, ret, source);
ReduceOffhands(data, equipFlags, ret, source);
ReduceCrests(data, crestFlags, ret, source);
ReduceParameters(data, parameterFlags, ret, source);
}
ApplyFixFlags(ret, fixFlags);
return ret;
}
private static void ReduceMeta(in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet, MergedDesign ret,
StateChanged.Source source)
{
if (applyHat && !ret.Design.DoApplyHatVisible())
{
ret.Design.SetApplyHatVisible(true);
ret.Design.GetDesignDataRef().SetHatVisible(design.IsHatVisible());
ret.Source[MetaIndex.HatState] = source;
}
if (applyVisor && !ret.Design.DoApplyVisorToggle())
{
ret.Design.SetApplyVisorToggle(true);
ret.Design.GetDesignDataRef().SetVisor(design.IsVisorToggled());
ret.Source[MetaIndex.VisorState] = source;
}
if (applyWeapon && !ret.Design.DoApplyWeaponVisible())
{
ret.Design.SetApplyWeaponVisible(true);
ret.Design.GetDesignDataRef().SetWeaponVisible(design.IsWeaponVisible());
ret.Source[MetaIndex.WeaponState] = source;
}
if (applyWet && !ret.Design.DoApplyWetness())
{
ret.Design.SetApplyWetness(true);
ret.Design.GetDesignDataRef().SetIsWet(design.IsWet());
ret.Source[MetaIndex.Wetness] = source;
}
}
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateChanged.Source source)
{
crestFlags &= ~ret.Design.ApplyCrest;
if (crestFlags == 0)
return;
foreach (var slot in CrestExtensions.AllRelevantSet)
{
if (!crestFlags.HasFlag(slot))
continue;
ret.Design.GetDesignDataRef().SetCrest(slot, design.Crest(slot));
ret.Design.SetApplyCrest(slot, true);
ret.Source[slot] = source;
}
}
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
StateChanged.Source source)
{
parameterFlags &= ~ret.Design.ApplyParameters;
if (parameterFlags == 0)
return;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
if (!parameterFlags.HasFlag(flag))
continue;
ret.Design.GetDesignDataRef().Parameters.Set(flag, design.Parameters[flag]);
ret.Design.SetApplyParameter(flag, true);
ret.Source[flag] = source;
}
}
private static void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source)
{
equipFlags &= ~ret.Design.ApplyEquip;
if (equipFlags == 0)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var flag = slot.ToFlag();
if (equipFlags.HasFlag(flag))
{
ret.Design.GetDesignDataRef().SetItem(slot, design.Item(slot));
ret.Design.SetApplyEquip(slot, true);
ret.Source[slot, false] = source;
}
var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag))
{
ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot));
ret.Design.SetApplyStain(slot, true);
ret.Source[slot, true] = source;
}
}
foreach (var slot in EquipSlotExtensions.WeaponSlots)
{
var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag))
{
ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot));
ret.Design.SetApplyStain(slot, true);
ret.Source[slot, true] = source;
}
}
}
private static void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source)
{
if (!equipFlags.HasFlag(EquipFlag.Mainhand))
return;
ret.Design.SetApplyEquip(EquipSlot.MainHand, true);
var weapon = design.Item(EquipSlot.MainHand);
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
}
private static void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateChanged.Source source)
{
if (!equipFlags.HasFlag(EquipFlag.Offhand))
return;
ret.Design.SetApplyEquip(EquipSlot.OffHand, true);
var weapon = design.Item(EquipSlot.OffHand);
if (weapon.Valid)
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
}
private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret,
StateChanged.Source source)
{
customizeFlags &= ~ret.Design.ApplyCustomizeRaw;
if (customizeFlags == 0)
return;
// Skip anything not human.
if (!ret.Design.DesignData.IsHuman || !design.IsHuman)
return;
var customize = ret.Design.DesignData.Customize;
if (customizeFlags.HasFlag(CustomizeFlag.Clan))
{
fixFlags |= _customize.ChangeClan(ref customize, design.Customize.Clan);
ret.Design.SetApplyCustomize(CustomizeIndex.Clan, true);
ret.Design.SetApplyCustomize(CustomizeIndex.Race, true);
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
ret.Source[CustomizeIndex.Clan] = source;
ret.Source[CustomizeIndex.Race] = source;
}
if (customizeFlags.HasFlag(CustomizeFlag.Gender))
{
fixFlags |= _customize.ChangeGender(ref customize, design.Customize.Gender);
ret.Design.SetApplyCustomize(CustomizeIndex.Gender, true);
customizeFlags &= ~CustomizeFlag.Gender;
ret.Source[CustomizeIndex.Gender] = source;
}
if (customizeFlags.HasFlag(CustomizeFlag.Face))
{
customize[CustomizeIndex.Face] = design.Customize.Face;
ret.Design.SetApplyCustomize(CustomizeIndex.Face, true);
customizeFlags &= ~CustomizeFlag.Face;
ret.Source[CustomizeIndex.Face] = source;
}
var set = _customize.Manager.GetSet(customize.Clan, customize.Gender);
var face = customize.Face;
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
var flag = index.ToFlag();
if (!customizeFlags.HasFlag(flag))
continue;
var value = design.Customize[index];
if (!CustomizeService.IsCustomizationValid(set, face, index, value, out var data))
continue;
customize[index] = data?.Value ?? value;
ret.Design.SetApplyCustomize(index, true);
ret.Source[index] = source;
fixFlags &= ~flag;
}
}
private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags)
{
if (fixFlags == 0)
return;
var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan)
? ret.Source[CustomizeIndex.Clan]
: ret.Source[CustomizeIndex.Gender];
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
var flag = index.ToFlag();
if (!fixFlags.HasFlag(flag))
continue;
ret.Source[index] = source;
ret.Design.SetApplyCustomize(index, true);
}
}
}

View file

@ -0,0 +1,177 @@
using Glamourer.Automation;
using Newtonsoft.Json.Linq;
using OtterGui.Filesystem;
namespace Glamourer.Designs.Links;
public sealed class LinkContainer : List<DesignLink>
{
public List<DesignLink> Before
=> this;
public readonly List<DesignLink> After = [];
public new int Count
=> base.Count + After.Count;
public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder)
{
var fromList = fromOrder switch
{
LinkOrder.Before => Before,
LinkOrder.After => After,
_ => throw new ArgumentException("Invalid link order."),
};
var toList = toOrder switch
{
LinkOrder.Before => Before,
LinkOrder.After => After,
_ => throw new ArgumentException("Invalid link order."),
};
if (fromList == toList)
return fromList.Move(fromIndex, toIndex);
if (fromIndex < 0 || fromIndex >= fromList.Count)
return false;
toIndex = Math.Clamp(toIndex, 0, toList.Count);
toList.Insert(toIndex, fromList[fromIndex]);
fromList.RemoveAt(fromIndex);
return true;
}
public bool Remove(int idx, LinkOrder order)
{
var list = order switch
{
LinkOrder.Before => Before,
LinkOrder.After => After,
_ => throw new ArgumentException("Invalid link order."),
};
if (idx < 0 || idx >= list.Count)
return false;
list.RemoveAt(idx);
return true;
}
public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error)
{
if (parent == child)
{
error = $"Can not link {parent.Identifier} with itself.";
return false;
}
if (parent.Links.Contains(child))
{
error = $"Design {parent.Identifier} already contains a direct link to {child.Identifier}.";
return false;
}
if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order))
{
error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the parent already links to the child in the opposite direction.";
return false;
}
if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order))
{
error = $"Adding {child.Identifier} to {parent.Identifier}s links would create a circle, the child already links to the parent in the opposite direction.";
return false;
}
error = string.Empty;
return true;
}
public static bool AddLink(Design parent, Design child, ApplicationType type, LinkOrder order, out string error)
{
if (!CanAddLink(parent, child, order, out error))
return false;
var list = order switch
{
LinkOrder.Before => parent.Links.Before,
LinkOrder.After => parent.Links.After,
_ => null,
};
if (list == null)
{
error = $"Order {order} is invalid.";
return false;
}
type &= ApplicationType.All;
list.Add(new DesignLink(child, type));
error = string.Empty;
return true;
}
public bool Contains(Design child)
=> Before.Any(l => l.Link == child) || After.Any(l => l.Link == child);
public bool Remove(Design child)
=> Before.RemoveAll(l => l.Link == child) + After.RemoveAll(l => l.Link == child) > 0;
public static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(Design design)
{
var set = new HashSet<Design>(design.Links.Count * 4);
return GetAllLinks(new DesignLink(design, ApplicationType.All), LinkOrder.Self, set);
}
private static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(DesignLink design, LinkOrder currentOrder, ISet<Design> visited)
{
if (design.Link.Links.Count == 0)
{
if (visited.Add(design.Link))
yield return (design, currentOrder);
yield break;
}
foreach (var link in design.Link.Links.Before
.Where(l => !visited.Contains(l.Link))
.SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.After ? LinkOrder.After : LinkOrder.Before, visited)))
yield return link;
if (visited.Add(design.Link))
yield return (design, currentOrder);
foreach (var link in design.Link.Links.After.Where(l => !visited.Contains(l.Link))
.SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.Before ? LinkOrder.Before : LinkOrder.After, visited)))
yield return link;
}
public JObject Serialize()
{
var before = new JArray();
foreach (var link in Before)
{
before.Add(new JObject
{
["Design"] = link.Link.Identifier,
["Type"] = (uint)link.Type,
});
}
var after = new JArray();
foreach (var link in After)
{
before.Add(new JObject
{
["Design"] = link.Link.Identifier,
["Type"] = (uint)link.Type,
});
}
return new JObject
{
[nameof(Before)] = before,
[nameof(After)] = after,
};
}
}

View file

@ -12,7 +12,7 @@ namespace Glamourer.Events;
/// <item>Parameter is any additional data depending on the type of change. </item> /// <item>Parameter is any additional data depending on the type of change. </item>
/// </list> /// </list>
/// </summary> /// </summary>
public sealed class DesignChanged() public sealed class DesignChanged()
: EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged)) : EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged))
{ {
public enum Type public enum Type
@ -50,6 +50,9 @@ public sealed class DesignChanged()
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary> /// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
RemovedMod, RemovedMod,
/// <summary> An existing design had a link to a different design added, removed or moved. Data is null. </summary>
ChangedLink,
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary> /// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize, Customize,
@ -92,6 +95,9 @@ public sealed class DesignChanged()
public enum Priority public enum Priority
{ {
/// <seealso cref="Designs.Links.DesignLinkManager.OnDesignChange"/>
DesignLinkManager = 1,
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/> /// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
AutoDesignManager = 1, AutoDesignManager = 1,

View file

@ -141,7 +141,7 @@ public class ActorPanel(
if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw))
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual); _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state));
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
} }
@ -187,21 +187,21 @@ public class ActorPanel(
{ {
using (_ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (_ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (_ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
} }
} }

View file

@ -369,13 +369,13 @@ public class SetPanel(
private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine) private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine)
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale)); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
var newType = design.ApplicationType; var newType = design.Type;
var newTypeInt = (uint)newType; var newTypeInt = (uint)newType;
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value())) using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
{ {
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All)) if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All))
newType = (AutoDesign.Type)newTypeInt; newType = (ApplicationType)newTypeInt;
} }
style.Pop(); style.Pop();
@ -385,7 +385,7 @@ public class SetPanel(
void Box(int idx) void Box(int idx)
{ {
var (type, description) = Types[idx]; var (type, description) = Types[idx];
var value = design.ApplicationType.HasFlag(type); var value = design.Type.HasFlag(type);
if (ImGui.Checkbox($"##{(byte)type}", ref value)) if (ImGui.Checkbox($"##{(byte)type}", ref value))
newType = value ? newType | type : newType & ~type; newType = value ? newType | type : newType & ~type;
ImGuiUtil.HoverTooltip(description); ImGuiUtil.HoverTooltip(description);
@ -429,14 +429,14 @@ public class SetPanel(
} }
private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[] private static readonly IReadOnlyList<(ApplicationType, string)> Types = new[]
{ {
(AutoDesign.Type.Customizations, (ApplicationType.Customizations,
"Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."), "Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."),
(AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."), (ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
(AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."), (ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
(AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."), (ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
(AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."), (ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
}; };
private sealed class JobGroupCombo : FilterComboCache<JobGroup> private sealed class JobGroupCombo : FilterComboCache<JobGroup>

View file

@ -70,44 +70,44 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})"; return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
} }
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Source[MetaIndex.ModelId]);
ImGui.TableNextRow(); ImGui.TableNextRow();
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]); PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Source[MetaIndex.Wetness]);
ImGui.TableNextRow(); ImGui.TableNextRow();
if (state.BaseData.IsHuman && state.ModelData.IsHuman) if (state.BaseData.IsHuman && state.ModelData.IsHuman)
{ {
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]); PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Source[MetaIndex.HatState]);
ImGui.TableNextRow(); ImGui.TableNextRow();
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(), PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
state[ActorState.MetaIndex.VisorState]); state.Source[MetaIndex.VisorState]);
ImGui.TableNextRow(); ImGui.TableNextRow();
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(), PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
state[ActorState.MetaIndex.WeaponState]); state.Source[MetaIndex.WeaponState]);
ImGui.TableNextRow(); ImGui.TableNextRow();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
{ {
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]); PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Source[slot, false]);
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString()); ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
ImGuiUtil.DrawTableColumn(state[slot, true].ToString()); ImGuiUtil.DrawTableColumn(state.Source[slot, true].ToString());
} }
foreach (var type in Enum.GetValues<CustomizeIndex>()) foreach (var type in Enum.GetValues<CustomizeIndex>())
{ {
PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]); PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Source[type]);
ImGui.TableNextRow(); ImGui.TableNextRow();
} }
foreach (var crest in CrestExtensions.AllRelevantSet) foreach (var crest in CrestExtensions.AllRelevantSet)
{ {
PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]); PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Source[crest]);
ImGui.TableNextRow(); ImGui.TableNextRow();
} }
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
{ {
PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]); PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Source[flag]);
ImGui.TableNextRow(); ImGui.TableNextRow();
} }
} }

View file

@ -42,7 +42,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr
foreach (var (design, designIdx) in set.Designs.WithIndex()) foreach (var (design, designIdx) in set.Designs.WithIndex())
{ {
ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})"); ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})");
ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}"); ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}");
} }
} }
} }

View file

@ -0,0 +1,120 @@
using Dalamud.Interface;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Services;
namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService
{
private int _dragDropIndex = -1;
private LinkOrder _dragDropOrder = LinkOrder.Self;
private int _dragDropTargetIndex = -1;
private LinkOrder _dragDropTargetOrder = LinkOrder.Self;
public void Draw()
{
using var header = ImRaii.CollapsingHeader("Design Links");
if (!header)
return;
var width = ImGui.GetContentRegionAvail().X / 2;
DrawList(_selector.Selected!.Links.Before, LinkOrder.Before, width);
ImGui.SameLine();
DrawList(_selector.Selected!.Links.After, LinkOrder.After, width);
if (_dragDropTargetIndex < 0
|| _dragDropIndex < 0)
return;
_linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
_dragDropIndex = -1;
_dragDropTargetIndex = -1;
_dragDropOrder = LinkOrder.Self;
_dragDropTargetOrder = LinkOrder.Self;
}
private void DrawList(IReadOnlyList<DesignLink> list, LinkOrder order, float width)
{
using var id = ImRaii.PushId((int)order);
using var table = ImRaii.Table("table", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter,
new Vector2(width, list.Count * ImGui.GetFrameHeightWithSpacing()));
if (!table)
return;
var buttonSize = new Vector2(ImGui.GetFrameHeight());
for (var i = 0; i < list.Count; ++i)
{
id.Push(i);
ImGui.TableNextColumn();
var delete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this link.", false, true);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted($"#{i:D2}");
var (design, flags) = list[i];
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text);
DrawDragDrop(design, order, i);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(flags.ToString());
if (delete)
_linkManager.RemoveDesignLink(_selector.Selected!, i--, order);
}
ImGui.TableNextColumn();
string tt;
bool canAdd;
if (_combo.Design == null)
{
tt = "Select a design first.";
canAdd = false;
}
else
{
canAdd = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, order, out var error);
tt = canAdd ? $"Add a link to {_combo.Design.Name}." : $"Can not add a link to {_combo.Design.Name}: {error}";
}
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, !canAdd, true))
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, order);
ImGui.TableNextColumn();
ImGui.TableNextColumn();
_combo.Draw(200);
}
private void DrawDragDrop(Design design, LinkOrder order, int index)
{
using (var source = ImRaii.DragDropSource())
{
if (source)
{
ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0);
ImGui.TextUnformatted($"Reordering {design.Name}...");
_dragDropIndex = index;
_dragDropOrder = order;
}
}
using var target = ImRaii.DragDropTarget();
if (!target)
return;
if (!ImGuiUtil.IsDropping("DraggingLink"))
return;
_dragDropTargetIndex = index;
_dragDropTargetOrder = order;
}
}

View file

@ -31,7 +31,8 @@ public class DesignPanel(
DesignConverter _converter, DesignConverter _converter,
ImportService _importService, ImportService _importService,
MultiDesignPanel _multiDesignPanel, MultiDesignPanel _multiDesignPanel,
CustomizeParameterDrawer _parameterDrawer) CustomizeParameterDrawer _parameterDrawer,
DesignLinkDrawer _designLinkDrawer)
{ {
private readonly FileDialogManager _fileDialog = new(); private readonly FileDialogManager _fileDialog = new();
@ -119,21 +120,21 @@ public class DesignPanel(
{ {
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!));
} }
} }
@ -158,7 +159,7 @@ public class DesignPanel(
_manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]); _manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]);
} }
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!));
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
} }
@ -166,6 +167,7 @@ public class DesignPanel(
{ {
if (!_config.UseAdvancedParameters) if (!_config.UseAdvancedParameters)
return; return;
using var h = ImRaii.CollapsingHeader("Advanced Customizations"); using var h = ImRaii.CollapsingHeader("Advanced Customizations");
if (!h) if (!h)
return; return;
@ -259,20 +261,20 @@ public class DesignPanel(
} }
} }
ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[] ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[]
{ {
EquipSlot.MainHand, EquipSlot.MainHand,
EquipSlot.OffHand, EquipSlot.OffHand,
}); });
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots); ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots); ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
ImGui.NewLine(); ImGui.NewLine();
ApplyEquip("Dyes", AutoDesign.StainFlags, true, ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true,
EquipSlotExtensions.FullSlots); EquipSlotExtensions.FullSlots);
ImGui.NewLine(); ImGui.NewLine();
@ -294,19 +296,19 @@ public class DesignPanel(
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all); var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible(); var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible();
if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange) if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.HatState, apply);
apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle(); apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle();
if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange) if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.VisorState, apply);
apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible(); apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible();
if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange) if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.WeaponState, apply);
apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness(); apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness();
if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); _manager.ChangeApplyMeta(_selector.Selected!, MetaIndex.Wetness, apply);
} }
private void DrawParameterApplication() private void DrawParameterApplication()
@ -370,6 +372,7 @@ public class DesignPanel(
_designDetails.Draw(); _designDetails.Draw();
DrawApplicationRules(); DrawApplicationRules();
_modAssociations.Draw(); _modAssociations.Draw();
_designLinkDrawer.Draw();
} }
private void DrawButtonRow() private void DrawButtonRow()

View file

@ -172,7 +172,7 @@ public class NpcPanel(
_equipDrawer.DrawWeapons(mainhandData, offhandData, false); _equipDrawer.DrawWeapons(mainhandData, offhandData, false);
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled));
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
} }

View file

@ -22,17 +22,17 @@ public ref struct ToggleDrawData
public ToggleDrawData() public ToggleDrawData()
{ } { }
public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design) public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design)
{ {
var (label, value, apply, setValue, setApply) = index switch var (label, value, apply, setValue, setApply) = index switch
{ {
ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(), MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(),
(Action<bool>)(b => manager.ChangeMeta(design, index, b)), (Action<bool>)(b => manager.ChangeApplyMeta(design, index, b))), (Action<bool>)(b => manager.ChangeMeta(design, index, b)), (Action<bool>)(b => manager.ChangeApplyMeta(design, index, b))),
ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(), MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(),
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(), MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(),
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(), MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(),
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)), b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
_ => throw new Exception("Unsupported meta index."), _ => throw new Exception("Unsupported meta index."),
}; };
@ -73,19 +73,19 @@ public ref struct ToggleDrawData
SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual), SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual),
}; };
public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state) public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state)
{ {
var (label, tooltip, value, setValue) = index switch var (label, tooltip, value, setValue) = index switch
{ {
ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(), MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(),
(Action<bool>)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))), (Action<bool>)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))),
ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.", MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.",
state.ModelData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)), b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)),
ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.", MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.",
state.ModelData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)), b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)),
ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(), MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(),
b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)), b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)),
_ => throw new Exception("Unsupported meta index."), _ => throw new Exception("Unsupported meta index."),
}; };
@ -100,14 +100,14 @@ public ref struct ToggleDrawData
}; };
} }
public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value) public static ToggleDrawData FromValue(MetaIndex index, bool value)
{ {
var (label, tooltip) = index switch var (label, tooltip) = index switch
{ {
ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."), MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."),
ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."), MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."),
ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."), MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."),
ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."), MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."),
_ => throw new Exception("Unsupported meta index."), _ => throw new Exception("Unsupported meta index."),
}; };
return new ToggleDrawData return new ToggleDrawData

View file

@ -14,24 +14,17 @@ public sealed class CharaFile
public CustomizeFlag ApplyCustomize; public CustomizeFlag ApplyCustomize;
public EquipFlag ApplyEquip; public EquipFlag ApplyEquip;
public static CharaFile? ParseData(ItemManager items, string data, string? name = null) public static CharaFile ParseData(ItemManager items, string data, string? name = null)
{ {
try var jObj = JObject.Parse(data);
{ SanityCheck(jObj);
var jObj = JObject.Parse(data); var ret = new CharaFile();
SanityCheck(jObj); ret.Data.SetDefaultEquipment(items);
var ret = new CharaFile(); ret.Data.ModelId = ParseModelId(jObj);
ret.Data.SetDefaultEquipment(items); ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
ret.Data.ModelId = ParseModelId(jObj); ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design"; ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize); return ret;
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
return ret;
}
catch
{
return null;
}
} }
private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data) private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data)
@ -282,9 +275,6 @@ public sealed class CharaFile
private static void SanityCheck(JObject jObj) private static void SanityCheck(JObject jObj)
{ {
if (jObj["TypeName"]?.ToObject<string>() is not "Anamnesis Character File")
throw new Exception("Wrong TypeName property set.");
var type = jObj["ObjectKind"]?.ToObject<string>(); var type = jObj["ObjectKind"]?.ToObject<string>();
if (type is not "Player") if (type is not "Player")
throw new Exception($"ObjectKind {type} != Player is not supported."); throw new Exception($"ObjectKind {type} != Player is not supported.");

View file

@ -63,8 +63,6 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d
{ {
var text = File.ReadAllText(path); var text = File.ReadAllText(path);
var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path)); var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
if (file == null)
throw new Exception();
name = file.Name; name = file.Name;
design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize); design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize);

View file

@ -41,9 +41,9 @@ public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager
design.ApplyCustomize = 0; design.ApplyCustomize = 0;
design.ApplyEquip = 0; design.ApplyEquip = 0;
design.ApplyCrest = 0; design.ApplyCrest = 0;
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false); designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false);
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false); designManager.ChangeApplyMeta(design, MetaIndex.HatState, false);
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, 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);

View file

@ -255,26 +255,26 @@ public class CommandService : IDisposable
} }
--designIdx; --designIdx;
AutoDesign.Type applicationFlags = 0; ApplicationType applicationFlags = 0;
if (split2.Length == 2) if (split2.Length == 2)
foreach (var character in split2[1]) foreach (var character in split2[1])
{ {
switch (char.ToLowerInvariant(character)) switch (char.ToLowerInvariant(character))
{ {
case 'c': case 'c':
applicationFlags |= AutoDesign.Type.Customizations; applicationFlags |= ApplicationType.Customizations;
break; break;
case 'e': case 'e':
applicationFlags |= AutoDesign.Type.Armor; applicationFlags |= ApplicationType.Armor;
break; break;
case 'a': case 'a':
applicationFlags |= AutoDesign.Type.Accessories; applicationFlags |= ApplicationType.Accessories;
break; break;
case 'd': case 'd':
applicationFlags |= AutoDesign.Type.GearCustomization; applicationFlags |= ApplicationType.GearCustomization;
break; break;
case 'w': case 'w':
applicationFlags |= AutoDesign.Type.Weapons; applicationFlags |= ApplicationType.Weapons;
break; break;
default: default:
_chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true) _chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true)

View file

@ -11,15 +11,6 @@ namespace Glamourer.State;
public class ActorState public class ActorState
{ {
public enum MetaIndex
{
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
HatState,
VisorState,
WeaponState,
ModelId,
}
public readonly ActorIdentifier Identifier; public readonly ActorIdentifier Identifier;
public bool AllowsRedraw(ICondition condition) public bool AllowsRedraw(ICondition condition)
@ -77,47 +68,14 @@ public class ActorState
=> Unlock(1337); => Unlock(1337);
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary> /// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
private readonly StateChanged.Source[] _sources = Enumerable public readonly StateSource Source = new();
.Repeat(StateChanged.Source.Game,
EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices
+ 5
+ CrestExtensions.AllRelevantSet.Count
+ CustomizeParameterExtensions.AllFlags.Count).ToArray();
internal ActorState(ActorIdentifier identifier) internal ActorState(ActorIdentifier identifier)
=> Identifier = identifier.CreatePermanent(); => Identifier = identifier.CreatePermanent();
public ref StateChanged.Source this[EquipSlot slot, bool stain]
=> ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)];
public ref StateChanged.Source this[CrestFlag slot]
=> ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()];
public ref StateChanged.Source this[CustomizeIndex type]
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
public ref StateChanged.Source this[MetaIndex index]
=> ref _sources[(int)index];
public ref StateChanged.Source this[CustomizeParameterFlag flag]
=> ref _sources[
EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices + 5
+ CrestExtensions.AllRelevantSet.Count
+ flag.ToInternalIndex()];
public void RemoveFixedDesignSources()
{
for (var i = 0; i < _sources.Length; ++i)
{
if (_sources[i] is StateChanged.Source.Fixed)
_sources[i] = StateChanged.Source.Manual;
}
}
public CustomizeParameterFlag OnlyChangedParameters() public CustomizeParameterFlag OnlyChangedParameters()
=> CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b); => CustomizeParameterExtensions.AllFlags.Where(f => Source[f] is not StateChanged.Source.Game)
.Aggregate((CustomizeParameterFlag)0, (a, b) => a | b);
public bool UpdateTerritory(ushort territory) public bool UpdateTerritory(ushort territory)
{ {
@ -127,4 +85,4 @@ public class ActorState
LastTerritory = territory; LastTerritory = territory;
return true; return true;
} }
} }

View file

@ -118,7 +118,7 @@ public class StateApplier(
// If the source is not IPC we do not want to apply restrictions. // If the source is not IPC we do not want to apply restrictions.
var data = GetData(state); var data = GetData(state);
if (apply) if (apply)
ChangeArmor(data, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
return data; return data;

View file

@ -61,21 +61,21 @@ public class StateEditor
state.ModelData.SetHatVisible(true); state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true); state.ModelData.SetWeaponVisible(true);
state.ModelData.SetVisor(false); state.ModelData.SetVisor(false);
state[ActorState.MetaIndex.ModelId] = source; state.Source[MetaIndex.ModelId] = source;
state[ActorState.MetaIndex.HatState] = source; state.Source[MetaIndex.HatState] = source;
state[ActorState.MetaIndex.WeaponState] = source; state.Source[MetaIndex.WeaponState] = source;
state[ActorState.MetaIndex.VisorState] = source; state.Source[MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
state[slot, true] = source; state.Source[slot, true] = source;
state[slot, false] = source; state.Source[slot, false] = source;
} }
state[CustomizeIndex.Clan] = source; state.Source[CustomizeIndex.Clan] = source;
state[CustomizeIndex.Gender] = source; state.Source[CustomizeIndex.Gender] = source;
var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable)) foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
state[index] = source; state.Source[index] = source;
} }
else else
{ {
@ -83,7 +83,7 @@ public class StateEditor
return false; return false;
state.ModelData.LoadNonHuman(modelId, customize, equipData); state.ModelData.LoadNonHuman(modelId, customize, equipData);
state[ActorState.MetaIndex.ModelId] = source; state.Source[MetaIndex.ModelId] = source;
} }
return true; return true;
@ -98,7 +98,7 @@ public class StateEditor
return false; return false;
state.ModelData.Customize[idx] = value; state.ModelData.Customize[idx] = value;
state[idx] = source; state.Source[idx] = source;
return true; return true;
} }
@ -120,7 +120,7 @@ public class StateEditor
foreach (var type in Enum.GetValues<CustomizeIndex>()) foreach (var type in Enum.GetValues<CustomizeIndex>())
{ {
if (applied.HasFlag(type.ToFlag())) if (applied.HasFlag(type.ToFlag()))
state[type] = source; state.Source[type] = source;
} }
return true; return true;
@ -144,12 +144,12 @@ public class StateEditor
_gPose.AddActionOnLeave(() => _gPose.AddActionOnLeave(() =>
{ {
if (old.Type == state.BaseData.Item(slot).Type) if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state[slot, false], out _, key); ChangeItem(state, slot, old, state.Source[slot, false], out _, key);
}); });
} }
state.ModelData.SetItem(slot, item); state.ModelData.SetItem(slot, item);
state[slot, false] = source; state.Source[slot, false] = source;
return true; return true;
} }
@ -174,14 +174,14 @@ public class StateEditor
_gPose.AddActionOnLeave(() => _gPose.AddActionOnLeave(() =>
{ {
if (old.Type == state.BaseData.Item(slot).Type) if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key); ChangeEquip(state, slot, old, oldS, state.Source[slot, false], out _, out _, key);
}); });
} }
state.ModelData.SetItem(slot, item); state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stain);
state[slot, false] = source; state.Source[slot, false] = source;
state[slot, true] = source; state.Source[slot, true] = source;
return true; return true;
} }
@ -193,7 +193,7 @@ public class StateEditor
return false; return false;
state.ModelData.SetStain(slot, stain); state.ModelData.SetStain(slot, stain);
state[slot, true] = source; state.Source[slot, true] = source;
return true; return true;
} }
@ -205,7 +205,7 @@ public class StateEditor
return false; return false;
state.ModelData.SetCrest(slot, crest); state.ModelData.SetCrest(slot, crest);
state[slot] = source; state.Source[slot] = source;
return true; return true;
} }
@ -218,20 +218,20 @@ public class StateEditor
return false; return false;
state.ModelData.Parameters.Set(flag, value); state.ModelData.Parameters.Set(flag, value);
state[flag] = source; state.Source[flag] = source;
return true; return true;
} }
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue, public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
uint key = 0) uint key = 0)
{ {
(var setter, oldValue) = index switch (var setter, oldValue) = index switch
{ {
ActorState.MetaIndex.Wetness => ((Func<bool, bool>)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()), MetaIndex.Wetness => ((Func<bool, bool>)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()),
ActorState.MetaIndex.HatState => ((Func<bool, bool>)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()), MetaIndex.HatState => ((Func<bool, bool>)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()),
ActorState.MetaIndex.VisorState => ((Func<bool, bool>)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()), MetaIndex.VisorState => ((Func<bool, bool>)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()),
ActorState.MetaIndex.WeaponState => ((Func<bool, bool>)(v => state.ModelData.SetWeaponVisible(v)), MetaIndex.WeaponState => ((Func<bool, bool>)(v => state.ModelData.SetWeaponVisible(v)),
state.ModelData.IsWeaponVisible()), state.ModelData.IsWeaponVisible()),
_ => throw new Exception("Invalid MetaIndex."), _ => throw new Exception("Invalid MetaIndex."),
}; };
@ -240,7 +240,7 @@ public class StateEditor
return false; return false;
setter(value); setter(value);
state[index] = source; state.Source[index] = source;
return true; return true;
} }
} }

View file

@ -170,7 +170,7 @@ public class StateListener : IDisposable
var set = _customizations.Manager.GetSet(model.Clan, model.Gender); var set = _customizations.Manager.GetSet(model.Clan, model.Gender);
foreach (var index in CustomizationExtensions.AllBasic) foreach (var index in CustomizationExtensions.AllBasic)
{ {
if (state[index] is not StateChanged.Source.Fixed) if (state.Source[index] is not StateChanged.Source.Fixed)
{ {
var newValue = customize[index]; var newValue = customize[index];
var oldValue = model[index]; var oldValue = model[index];
@ -213,7 +213,7 @@ public class StateListener : IDisposable
&& _manager.TryGetValue(identifier, out var state)) && _manager.TryGetValue(identifier, out var state))
{ {
HandleEquipSlot(actor, state, slot, ref armor); HandleEquipSlot(actor, state, slot, ref armor);
locked = state[slot, false] is StateChanged.Source.Ipc; locked = state.Source[slot, false] is StateChanged.Source.Ipc;
} }
_funModule.ApplyFunToSlot(actor, ref armor, slot); _funModule.ApplyFunToSlot(actor, ref armor, slot);
@ -240,7 +240,7 @@ public class StateListener : IDisposable
continue; continue;
var changed = changedItem.Weapon(stain); var changed = changedItem.Weapon(stain);
if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (current.Value == changed.Value && state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
{ {
_manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game); _manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
_manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game); _manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game);
@ -251,7 +251,7 @@ public class StateListener : IDisposable
_applier.ChangeWeapon(objects, slot, currentItem, stain); _applier.ChangeWeapon(objects, slot, currentItem, stain);
break; break;
default: default:
_applier.ChangeArmor(objects, slot, current.ToArmor(), state[slot, false] is not StateChanged.Source.Ipc, _applier.ChangeArmor(objects, slot, current.ToArmor(), state.Source[slot, false] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
break; break;
} }
@ -285,12 +285,12 @@ public class StateListener : IDisposable
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later. // Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
case UpdateState.Transformed: break; case UpdateState.Transformed: break;
case UpdateState.Change: case UpdateState.Change:
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
@ -384,12 +384,12 @@ public class StateListener : IDisposable
// Update model state if not on fixed design. // Update model state if not on fixed design.
case UpdateState.Change: case UpdateState.Change:
var apply = false; var apply = false;
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state.Source[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game); _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state.Source[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
else else
apply = true; apply = true;
@ -418,7 +418,7 @@ public class StateListener : IDisposable
switch (UpdateBaseCrest(actor, state, slot, value)) switch (UpdateBaseCrest(actor, state, slot, value))
{ {
case UpdateState.Change: case UpdateState.Change:
if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc) if (state.Source[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game); _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
else else
value = state.ModelData.Crest(slot); value = state.ModelData.Crest(slot);
@ -564,7 +564,7 @@ public class StateListener : IDisposable
{ {
// if base state changed, either overwrite the actual value if we have fixed values, // if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one. // or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value = state.ModelData.IsVisorToggled(); value = state.ModelData.IsVisorToggled();
else else
_manager.ChangeVisorState(state, value, StateChanged.Source.Game); _manager.ChangeVisorState(state, value, StateChanged.Source.Game);
@ -597,7 +597,7 @@ public class StateListener : IDisposable
{ {
// if base state changed, either overwrite the actual value if we have fixed values, // if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one. // or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value = state.ModelData.IsHatVisible(); value = state.ModelData.IsHatVisible();
else else
_manager.ChangeHatState(state, value, StateChanged.Source.Game); _manager.ChangeHatState(state, value, StateChanged.Source.Game);
@ -630,7 +630,7 @@ public class StateListener : IDisposable
{ {
// if base state changed, either overwrite the actual value if we have fixed values, // if base state changed, either overwrite the actual value if we have fixed values,
// or overwrite the stored model state with the new one. // or overwrite the stored model state with the new one.
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc) if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
value = state.ModelData.IsWeaponVisible(); value = state.ModelData.IsWeaponVisible();
else else
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game); _manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
@ -732,7 +732,7 @@ public class StateListener : IDisposable
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
{ {
var newValue = data[flag]; var newValue = data[flag];
switch (state[flag]) switch (state.Source[flag])
{ {
case StateChanged.Source.Game: case StateChanged.Source.Game:
if (state.BaseData.Parameters.Set(flag, newValue)) if (state.BaseData.Parameters.Set(flag, newValue))
@ -755,7 +755,7 @@ public class StateListener : IDisposable
break; break;
case StateChanged.Source.Pending: case StateChanged.Source.Pending:
state.BaseData.Parameters.Set(flag, newValue); state.BaseData.Parameters.Set(flag, newValue);
state[flag] = StateChanged.Source.Manual; state.Source[flag] = StateChanged.Source.Manual;
if (_config.UseAdvancedParameters) if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters); model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break; break;

View file

@ -329,49 +329,49 @@ public class StateManager(
/// <summary> Change hat visibility. </summary> /// <summary> Change hat visibility. </summary>
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, value, source, out var old, key)) if (!_editor.ChangeMetaState(state, MetaIndex.HatState, value, source, out var old, key))
return; return;
var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.HatState)); _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState));
} }
/// <summary> Change weapon visibility. </summary> /// <summary> Change weapon visibility. </summary>
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, value, source, out var old, key)) if (!_editor.ChangeMetaState(state, MetaIndex.WeaponState, value, source, out var old, key))
return; return;
var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.WeaponState)); _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.WeaponState));
} }
/// <summary> Change visor state. </summary> /// <summary> Change visor state. </summary>
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, value, source, out var old, key)) if (!_editor.ChangeMetaState(state, MetaIndex.VisorState, value, source, out var old, key))
return; return;
var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc); var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.VisorState)); _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.VisorState));
} }
/// <summary> Set GPose Wetness. </summary> /// <summary> Set GPose Wetness. </summary>
public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, value, source, out var old, key)) if (!_editor.ChangeMetaState(state, MetaIndex.Wetness, value, source, out var old, key))
return; return;
var actors = _applier.ChangeWetness(state, true); var actors = _applier.ChangeWetness(state, true);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, actors, (old, value, ActorState.MetaIndex.Wetness)); _event.Invoke(StateChanged.Type.Other, state.Source[MetaIndex.Wetness], state, actors, (old, value, MetaIndex.Wetness));
} }
#endregion #endregion
@ -385,16 +385,16 @@ public class StateManager(
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman; var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
if (design.DoApplyWetness()) if (design.DoApplyWetness())
_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key);
if (state.ModelData.IsHuman) if (state.ModelData.IsHuman)
{ {
if (design.DoApplyHatVisible()) if (design.DoApplyHatVisible())
_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key);
if (design.DoApplyWeaponVisible()) if (design.DoApplyWeaponVisible())
_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key);
if (design.DoApplyVisorToggle()) if (design.DoApplyVisorToggle())
_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key); _editor.ChangeMetaState(state, MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key);
var flags = state.AllowsRedraw(_condition) var flags = state.AllowsRedraw(_condition)
? design.ApplyCustomize ? design.ApplyCustomize
@ -419,13 +419,13 @@ public class StateManager(
if (!state.ModelData.Customize.Highlights) if (!state.ModelData.Customize.Highlights)
_editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight, _editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse], state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state[CustomizeParameterFlag.HairDiffuse], out _, key); state.Source[CustomizeParameterFlag.HairDiffuse], out _, key);
} }
var actors = ApplyAll(state, redraw, false); var actors = ApplyAll(state, redraw, false);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); $"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design); _event.Invoke(StateChanged.Type.Design, state.Source[MetaIndex.Wetness], state, actors, design);
return; return;
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain) void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
@ -455,7 +455,7 @@ public class StateManager(
_applier.ChangeCustomize(actors, state.ModelData.Customize); _applier.ChangeCustomize(actors, state.ModelData.Customize);
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)
{ {
_applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc, _applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Source[slot, false] is not StateChanged.Source.Ipc,
state.ModelData.IsHatVisible()); state.ModelData.IsHatVisible());
} }
@ -489,22 +489,22 @@ public class StateManager(
state.ModelData = state.BaseData; state.ModelData = state.BaseData;
state.ModelData.SetIsWet(false); state.ModelData.SetIsWet(false);
foreach (var index in Enum.GetValues<CustomizeIndex>()) foreach (var index in Enum.GetValues<CustomizeIndex>())
state[index] = StateChanged.Source.Game; state.Source[index] = StateChanged.Source.Game;
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
state[slot, true] = StateChanged.Source.Game; state.Source[slot, true] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game; state.Source[slot, false] = StateChanged.Source.Game;
} }
foreach (var type in Enum.GetValues<ActorState.MetaIndex>()) foreach (var type in Enum.GetValues<MetaIndex>())
state[type] = StateChanged.Source.Game; state.Source[type] = StateChanged.Source.Game;
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
state[slot] = StateChanged.Source.Game; state.Source[slot] = StateChanged.Source.Game;
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game; state.Source[flag] = StateChanged.Source.Game;
var actors = ActorData.Invalid; var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
@ -523,7 +523,7 @@ public class StateManager(
state.ModelData.Parameters = state.BaseData.Parameters; state.ModelData.Parameters = state.BaseData.Parameters;
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game; state.Source[flag] = StateChanged.Source.Game;
var actors = ActorData.Invalid; var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
@ -538,69 +538,69 @@ public class StateManager(
if (!state.Unlock(key)) if (!state.Unlock(key))
return; return;
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state[i] is StateChanged.Source.Fixed)) foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state.Source[i] is StateChanged.Source.Fixed))
{ {
state[index] = StateChanged.Source.Game; state.Source[index] = StateChanged.Source.Game;
state.ModelData.Customize[index] = state.BaseData.Customize[index]; state.ModelData.Customize[index] = state.BaseData.Customize[index];
} }
foreach (var slot in EquipSlotExtensions.FullSlots) foreach (var slot in EquipSlotExtensions.FullSlots)
{ {
if (state[slot, true] is StateChanged.Source.Fixed) if (state.Source[slot, true] is StateChanged.Source.Fixed)
{ {
state[slot, true] = StateChanged.Source.Game; state.Source[slot, true] = StateChanged.Source.Game;
state.ModelData.SetStain(slot, state.BaseData.Stain(slot)); state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
} }
if (state[slot, false] is StateChanged.Source.Fixed) if (state.Source[slot, false] is StateChanged.Source.Fixed)
{ {
state[slot, false] = StateChanged.Source.Game; state.Source[slot, false] = StateChanged.Source.Game;
state.ModelData.SetItem(slot, state.BaseData.Item(slot)); state.ModelData.SetItem(slot, state.BaseData.Item(slot));
} }
} }
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
{ {
if (state[slot] is StateChanged.Source.Fixed) if (state.Source[slot] is StateChanged.Source.Fixed)
{ {
state[slot] = StateChanged.Source.Game; state.Source[slot] = StateChanged.Source.Game;
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot)); state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
} }
} }
foreach (var flag in CustomizeParameterExtensions.AllFlags) foreach (var flag in CustomizeParameterExtensions.AllFlags)
{ {
switch (state[flag]) switch (state.Source[flag])
{ {
case StateChanged.Source.Fixed: case StateChanged.Source.Fixed:
case StateChanged.Source.Manual when !respectManualPalettes: case StateChanged.Source.Manual when !respectManualPalettes:
state[flag] = StateChanged.Source.Game; state.Source[flag] = StateChanged.Source.Game;
state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag]; state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag];
break; break;
} }
} }
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) if (state.Source[MetaIndex.HatState] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; state.Source[MetaIndex.HatState] = StateChanged.Source.Game;
state.ModelData.SetHatVisible(state.BaseData.IsHatVisible()); state.ModelData.SetHatVisible(state.BaseData.IsHatVisible());
} }
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed) if (state.Source[MetaIndex.VisorState] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.VisorState] = StateChanged.Source.Game; state.Source[MetaIndex.VisorState] = StateChanged.Source.Game;
state.ModelData.SetVisor(state.BaseData.IsVisorToggled()); state.ModelData.SetVisor(state.BaseData.IsVisorToggled());
} }
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed) if (state.Source[MetaIndex.WeaponState] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.WeaponState] = StateChanged.Source.Game; state.Source[MetaIndex.WeaponState] = StateChanged.Source.Game;
state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible()); state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible());
} }
if (state[ActorState.MetaIndex.Wetness] is StateChanged.Source.Fixed) if (state.Source[MetaIndex.Wetness] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.Wetness] = StateChanged.Source.Game; state.Source[MetaIndex.Wetness] = StateChanged.Source.Game;
state.ModelData.SetIsWet(state.BaseData.IsWet()); state.ModelData.SetIsWet(state.BaseData.IsWet());
} }
} }

View file

@ -0,0 +1,58 @@
using Glamourer.GameData;
using Penumbra.GameData.Enums;
using static Glamourer.Events.StateChanged;
namespace Glamourer.State;
public enum MetaIndex
{
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
HatState,
VisorState,
WeaponState,
ModelId,
}
public readonly struct StateSource
{
public static readonly int Size = EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices
+ 5
+ CrestExtensions.AllRelevantSet.Count
+ CustomizeParameterExtensions.AllFlags.Count;
private readonly Source[] _data = Enumerable.Repeat(Source.Game, Size).ToArray();
public StateSource()
{ }
public ref Source this[EquipSlot slot, bool stain]
=> ref _data[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)];
public ref Source this[CrestFlag slot]
=> ref _data[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()];
public ref Source this[CustomizeIndex type]
=> ref _data[EquipFlagExtensions.NumEquipFlags + (int)type];
public ref Source this[MetaIndex index]
=> ref _data[(int)index];
public ref Source this[CustomizeParameterFlag flag]
=> ref _data[
EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices
+ 5
+ CrestExtensions.AllRelevantSet.Count
+ flag.ToInternalIndex()];
public void RemoveFixedDesignSources()
{
for (var i = 0; i < _data.Length; ++i)
{
if (_data[i] is Source.Fixed)
_data[i] = Source.Manual;
}
}
}

@ -1 +1 @@
Subproject commit d734d5d2b0686db0f5f4270dc379360d31f72e59 Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3