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,21 +12,9 @@ public class AutoDesign
{
public const string RevertName = "Revert";
[Flags]
public enum Type : byte
{
Armor = 0x01,
Customizations = 0x02,
Weapons = 0x04,
GearCustomization = 0x08,
Accessories = 0x10,
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
}
public Design? Design;
public JobGroup Jobs;
public Type ApplicationType;
public ApplicationType Type;
public short GearsetIndex = -1;
public string Name(bool incognito)
@ -42,7 +30,7 @@ public class AutoDesign
=> new()
{
Design = Design,
ApplicationType = ApplicationType,
Type = Type,
Jobs = Jobs,
GearsetIndex = GearsetIndex,
};
@ -65,7 +53,7 @@ public class AutoDesign
=> new()
{
["Design"] = Design?.Identifier.ToString(),
["ApplicationType"] = (uint)ApplicationType,
["Type"] = (uint)Type,
["Conditions"] = CreateConditionObject(),
};
@ -82,42 +70,5 @@ public class AutoDesign
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool
ApplyWeapon, bool ApplyWet) ApplyWhat()
{
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;
=> Type.ApplyWhat(Design);
}

View file

@ -1,6 +1,7 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
@ -15,7 +16,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Automation;
public class AutoDesignApplier : IDisposable
public sealed class AutoDesignApplier : IDisposable
{
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
@ -30,6 +31,7 @@ public class AutoDesignApplier : IDisposable
private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
private ActorState? _jobChangeState;
@ -182,7 +184,7 @@ public class AutoDesignApplier : IDisposable
}
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)
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))
state.RemoveFixedDesignSources();
state.Source.RemoveFixedDesignSources();
}
}
}
@ -276,7 +278,7 @@ public class AutoDesignApplier : IDisposable
if (set.BaseState == AutoDesignSet.Base.Game)
_state.ResetStateFixed(state, respectManual);
else if (!respectManual)
state.RemoveFixedDesignSources();
state.Source.RemoveFixedDesignSources();
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
return;
@ -286,7 +288,7 @@ public class AutoDesignApplier : IDisposable
if (!design.IsActive(actor))
continue;
if (design.ApplicationType is 0)
if (design.Type is 0)
continue;
ref readonly var data = ref design.GetDesignData(state);
@ -342,7 +344,7 @@ public class AutoDesignApplier : IDisposable
if (!crestFlags.HasFlag(slot))
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);
totalCrestFlags |= slot;
}
@ -360,7 +362,7 @@ public class AutoDesignApplier : IDisposable
if (!parameterFlags.HasFlag(flag))
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);
totalParameterFlags |= flag;
}
@ -381,7 +383,7 @@ public class AutoDesignApplier : IDisposable
var item = design.Item(slot);
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);
totalEquipFlags |= flag;
}
@ -390,7 +392,7 @@ public class AutoDesignApplier : IDisposable
var stainFlag = slot.ToStainFlag();
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);
totalEquipFlags |= stainFlag;
}
@ -400,7 +402,7 @@ public class AutoDesignApplier : IDisposable
{
var item = design.Item(EquipSlot.MainHand);
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 (fromJobChange)
@ -420,7 +422,7 @@ public class AutoDesignApplier : IDisposable
{
var item = design.Item(EquipSlot.OffHand);
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 (fromJobChange)
@ -438,14 +440,14 @@ public class AutoDesignApplier : IDisposable
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);
totalEquipFlags |= EquipFlag.MainhandStain;
}
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);
totalEquipFlags |= EquipFlag.OffhandStain;
}
@ -467,7 +469,7 @@ public class AutoDesignApplier : IDisposable
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);
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race;
@ -475,7 +477,7 @@ public class AutoDesignApplier : IDisposable
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);
customizeFlags &= ~CustomizeFlag.Gender;
totalCustomizeFlags |= CustomizeFlag.Gender;
@ -486,7 +488,7 @@ public class AutoDesignApplier : IDisposable
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);
customizeFlags &= ~CustomizeFlag.Face;
totalCustomizeFlags |= CustomizeFlag.Face;
@ -506,7 +508,7 @@ public class AutoDesignApplier : IDisposable
if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _))
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);
totalCustomizeFlags |= flag;
}
@ -518,28 +520,28 @@ public class AutoDesignApplier : IDisposable
{
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);
totalMetaFlags |= 0x01;
}
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);
totalMetaFlags |= 0x02;
}
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);
totalMetaFlags |= 0x04;
}
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);
totalMetaFlags |= 0x08;
}

View file

@ -232,7 +232,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var newDesign = new AutoDesign()
{
Design = design,
ApplicationType = AutoDesign.Type.All,
Type = ApplicationType.All,
Jobs = _jobs.JobGroups[1],
};
set.Designs.Add(newDesign);
@ -328,21 +328,21 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
_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)
return;
type &= AutoDesign.Type.All;
applicationType &= ApplicationType.All;
var design = set.Designs[which];
if (design.ApplicationType == type)
if (design.Type == applicationType)
return;
var old = design.ApplicationType;
design.ApplicationType = type;
var old = design.Type;
design.Type = applicationType;
Save();
Glamourer.Log.Debug($"Changed application type from {old} to {type} for associated design {which + 1} in design set.");
_event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, type));
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, applicationType));
}
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,
ApplicationType = applicationType & AutoDesign.Type.All,
Type = applicationType & ApplicationType.All,
};
return ParseConditions(setName, jObj, ret) ? ret : null;
}
@ -550,7 +551,24 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private ActorIdentifier[] GetGroup(ActorIdentifier identifier)
{
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)
{
@ -566,23 +584,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
identifier.Kind,
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)

View file

@ -3,12 +3,12 @@ using Penumbra.GameData.Actors;
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 ActorIdentifier[] Identifiers;
public string Name = name;
public ActorIdentifier[] Identifiers = identifiers;
public bool Enabled;
public Base BaseState = Base.Current;
@ -32,13 +32,6 @@ public class AutoDesignSet
: 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
{
Current,

View file

@ -56,7 +56,7 @@ public class FixedDesignMigrator(JobService jobs)
autoManager.AddDesign(set, leaf.Value);
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 Glamourer.Automation;
using Glamourer.Designs.Links;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Newtonsoft.Json;
@ -35,10 +37,11 @@ public sealed class Design : DesignBase, ISavable
public DateTimeOffset LastEdit { get; internal set; }
public LowerString Name { get; internal set; } = LowerString.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 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
=> Identifier.ToString()[..8];
@ -64,6 +67,7 @@ public sealed class Design : DesignBase, ISavable
["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(),
};
return ret;
}
@ -95,24 +99,18 @@ public sealed class Design : DesignBase, ISavable
#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;
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."),
};
}
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 design = new Design(customizations, items)
@ -131,8 +129,15 @@ public sealed class Design : DesignBase, ISavable
LoadEquip(items, json["Equipment"], design, design.Name, true);
LoadMods(json["Mods"], design);
LoadParameters(json["Parameters"], design, design.Name);
LoadLinks(linkLoader, json["Links"], design);
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
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)
@ -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
#region ISavable

View file

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

View file

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

@ -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>
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>
Customize,
@ -92,6 +95,9 @@ public sealed class DesignChanged()
public enum Priority
{
/// <seealso cref="Designs.Links.DesignLinkManager.OnDesignChange"/>
DesignLinkManager = 1,
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
AutoDesignManager = 1,

View file

@ -141,7 +141,7 @@ public class ActorPanel(
if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw))
_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));
}
@ -187,21 +187,21 @@ public class ActorPanel(
{
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!));
}
ImGui.SameLine();
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!));
}
ImGui.SameLine();
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!));
}
}

View file

@ -369,13 +369,13 @@ public class SetPanel(
private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
var newType = design.ApplicationType;
var newType = design.Type;
var newTypeInt = (uint)newType;
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
{
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
newType = (AutoDesign.Type)newTypeInt;
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All))
newType = (ApplicationType)newTypeInt;
}
style.Pop();
@ -385,7 +385,7 @@ public class SetPanel(
void Box(int 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))
newType = value ? newType | type : newType & ~type;
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."),
(AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
(AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
(AutoDesign.Type.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.Armor, "Apply all armor piece 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."),
(ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
(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>

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})";
}
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();
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();
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();
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
state[ActorState.MetaIndex.VisorState]);
state.Source[MetaIndex.VisorState]);
ImGui.TableNextRow();
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
state[ActorState.MetaIndex.WeaponState]);
state.Source[MetaIndex.WeaponState]);
ImGui.TableNextRow();
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.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>())
{
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();
}
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();
}
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();
}
}

View file

@ -42,7 +42,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr
foreach (var (design, designIdx) in set.Designs.WithIndex())
{
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,
ImportService _importService,
MultiDesignPanel _multiDesignPanel,
CustomizeParameterDrawer _parameterDrawer)
CustomizeParameterDrawer _parameterDrawer,
DesignLinkDrawer _designLinkDrawer)
{
private readonly FileDialogManager _fileDialog = new();
@ -119,21 +120,21 @@ public class DesignPanel(
{
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!));
}
ImGui.SameLine();
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!));
}
ImGui.SameLine();
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!));
}
}
@ -158,7 +159,7 @@ public class DesignPanel(
_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));
}
@ -166,6 +167,7 @@ public class DesignPanel(
{
if (!_config.UseAdvancedParameters)
return;
using var h = ImRaii.CollapsingHeader("Advanced Customizations");
if (!h)
return;
@ -259,20 +261,20 @@ public class DesignPanel(
}
}
ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[]
ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[]
{
EquipSlot.MainHand,
EquipSlot.OffHand,
});
ImGui.NewLine();
ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
ImGui.NewLine();
ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
ImGui.NewLine();
ApplyEquip("Dyes", AutoDesign.StainFlags, true,
ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true,
EquipSlotExtensions.FullSlots);
ImGui.NewLine();
@ -294,19 +296,19 @@ public class DesignPanel(
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible();
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();
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();
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();
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()
@ -370,6 +372,7 @@ public class DesignPanel(
_designDetails.Draw();
DrawApplicationRules();
_modAssociations.Draw();
_designLinkDrawer.Draw();
}
private void DrawButtonRow()

View file

@ -172,7 +172,7 @@ public class NpcPanel(
_equipDrawer.DrawWeapons(mainhandData, offhandData, false);
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));
}

View file

@ -22,17 +22,17 @@ public ref struct 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
{
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))),
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)),
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)),
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)),
_ => throw new Exception("Unsupported meta index."),
};
@ -73,19 +73,19 @@ public ref struct ToggleDrawData
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
{
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))),
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(),
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(),
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)),
_ => 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
{
ActorState.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."),
ActorState.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.HatState => ("Hat Visible", "Hide or show the characters head gear."),
MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."),
MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."),
MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."),
_ => throw new Exception("Unsupported meta index."),
};
return new ToggleDrawData

View file

@ -14,9 +14,7 @@ public sealed class CharaFile
public CustomizeFlag ApplyCustomize;
public EquipFlag ApplyEquip;
public static CharaFile? ParseData(ItemManager items, string data, string? name = null)
{
try
public static CharaFile ParseData(ItemManager items, string data, string? name = null)
{
var jObj = JObject.Parse(data);
SanityCheck(jObj);
@ -28,11 +26,6 @@ public sealed class CharaFile
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
return ret;
}
catch
{
return null;
}
}
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)
{
if (jObj["TypeName"]?.ToObject<string>() is not "Anamnesis Character File")
throw new Exception("Wrong TypeName property set.");
var type = jObj["ObjectKind"]?.ToObject<string>();
if (type is not "Player")
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 file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
if (file == null)
throw new Exception();
name = file.Name;
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.ApplyEquip = 0;
design.ApplyCrest = 0;
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false);
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false);
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, false);
designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false);
designManager.ChangeApplyMeta(design, MetaIndex.HatState, false);
designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false);
foreach (var flag in flags.Iterate())
{
designManager.ChangeApplyParameter(design, flag, true);

View file

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

View file

@ -11,15 +11,6 @@ namespace Glamourer.State;
public class ActorState
{
public enum MetaIndex
{
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
HatState,
VisorState,
WeaponState,
ModelId,
}
public readonly ActorIdentifier Identifier;
public bool AllowsRedraw(ICondition condition)
@ -77,47 +68,14 @@ public class ActorState
=> 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>
private readonly StateChanged.Source[] _sources = Enumerable
.Repeat(StateChanged.Source.Game,
EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices
+ 5
+ CrestExtensions.AllRelevantSet.Count
+ CustomizeParameterExtensions.AllFlags.Count).ToArray();
public readonly StateSource Source = new();
internal ActorState(ActorIdentifier identifier)
=> 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()
=> 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)
{

View file

@ -118,7 +118,7 @@ public class StateApplier(
// If the source is not IPC we do not want to apply restrictions.
var data = GetData(state);
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());
return data;

View file

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

View file

@ -170,7 +170,7 @@ public class StateListener : IDisposable
var set = _customizations.Manager.GetSet(model.Clan, model.Gender);
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 oldValue = model[index];
@ -213,7 +213,7 @@ public class StateListener : IDisposable
&& _manager.TryGetValue(identifier, out var state))
{
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);
@ -240,7 +240,7 @@ public class StateListener : IDisposable
continue;
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.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game);
@ -251,7 +251,7 @@ public class StateListener : IDisposable
_applier.ChangeWeapon(objects, slot, currentItem, stain);
break;
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());
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.
case UpdateState.Transformed: break;
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);
else
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);
else
apply = true;
@ -384,12 +384,12 @@ public class StateListener : IDisposable
// Update model state if not on fixed design.
case UpdateState.Change:
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);
else
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);
else
apply = true;
@ -418,7 +418,7 @@ public class StateListener : IDisposable
switch (UpdateBaseCrest(actor, state, slot, value))
{
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);
else
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,
// 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();
else
_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,
// 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();
else
_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,
// 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();
else
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
@ -732,7 +732,7 @@ public class StateListener : IDisposable
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
var newValue = data[flag];
switch (state[flag])
switch (state.Source[flag])
{
case StateChanged.Source.Game:
if (state.BaseData.Parameters.Set(flag, newValue))
@ -755,7 +755,7 @@ public class StateListener : IDisposable
break;
case StateChanged.Source.Pending:
state.BaseData.Parameters.Set(flag, newValue);
state[flag] = StateChanged.Source.Manual;
state.Source[flag] = StateChanged.Source.Manual;
if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;

View file

@ -329,49 +329,49 @@ public class StateManager(
/// <summary> Change hat visibility. </summary>
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;
var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"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>
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;
var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"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>
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;
var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"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>
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;
var actors = _applier.ChangeWetness(state, true);
Glamourer.Log.Verbose(
$"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
@ -385,16 +385,16 @@ public class StateManager(
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
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 (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())
_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())
_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)
? design.ApplyCustomize
@ -419,13 +419,13 @@ public class StateManager(
if (!state.ModelData.Customize.Highlights)
_editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state[CustomizeParameterFlag.HairDiffuse], out _, key);
state.Source[CustomizeParameterFlag.HairDiffuse], out _, key);
}
var actors = ApplyAll(state, redraw, false);
Glamourer.Log.Verbose(
$"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;
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
@ -455,7 +455,7 @@ public class StateManager(
_applier.ChangeCustomize(actors, state.ModelData.Customize);
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());
}
@ -489,22 +489,22 @@ public class StateManager(
state.ModelData = state.BaseData;
state.ModelData.SetIsWet(false);
foreach (var index in Enum.GetValues<CustomizeIndex>())
state[index] = StateChanged.Source.Game;
state.Source[index] = StateChanged.Source.Game;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state[slot, true] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game;
state.Source[slot, true] = StateChanged.Source.Game;
state.Source[slot, false] = StateChanged.Source.Game;
}
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
state[type] = StateChanged.Source.Game;
foreach (var type in Enum.GetValues<MetaIndex>())
state.Source[type] = StateChanged.Source.Game;
foreach (var slot in CrestExtensions.AllRelevantSet)
state[slot] = StateChanged.Source.Game;
state.Source[slot] = StateChanged.Source.Game;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game;
state.Source[flag] = StateChanged.Source.Game;
var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
@ -523,7 +523,7 @@ public class StateManager(
state.ModelData.Parameters = state.BaseData.Parameters;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game;
state.Source[flag] = StateChanged.Source.Game;
var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
@ -538,69 +538,69 @@ public class StateManager(
if (!state.Unlock(key))
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];
}
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));
}
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));
}
}
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));
}
}
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
switch (state[flag])
switch (state.Source[flag])
{
case StateChanged.Source.Fixed:
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];
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());
}
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());
}
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());
}
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());
}
}

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