Merge branch 'designlinks'

This commit is contained in:
Ottermandias 2024-01-26 16:45:51 +01:00
commit eba27e10fb
69 changed files with 3395 additions and 1997 deletions

View file

@ -1,8 +1,8 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
@ -130,12 +130,12 @@ public partial class GlamourerIpc
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
{
_stateManager.ApplyDesign(design, state, StateChanged.Source.Ipc, lockCode);
_stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode));
state.Lock(lockCode);
}
}
}
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode)
=> ApplyDesign(_designManager.Designs.FirstOrDefault(x => x.Identifier == identifier), actors, DesignConverter.Version, lockCode);
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode);
}

View file

@ -15,7 +15,7 @@ public partial class GlamourerIpc
private readonly EventProvider<StateChanged.Type, nint, Lazy<string>> _stateChangedProvider;
private readonly EventProvider<bool> _gPoseChangedProvider;
private void OnStateChanged(StateChanged.Type type, StateChanged.Source source, ActorState state, ActorData actors, object? data = null)
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
{
foreach (var actor in actors.Objects)
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state)));

View file

@ -1,6 +1,7 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Events;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;
@ -82,7 +83,7 @@ public partial class GlamourerIpc
foreach (var id in actors)
{
if (_stateManager.TryGetValue(id, out var state))
_stateManager.ResetState(state, StateChanged.Source.Ipc, lockCode);
_stateManager.ResetState(state, StateSource.Ipc, lockCode);
}
}

View file

@ -1,7 +1,9 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -55,7 +57,7 @@ public partial class GlamourerIpc
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key));
return GlamourerErrorCode.Success;
}
@ -82,7 +84,7 @@ public partial class GlamourerIpc
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key));
found = true;
}

View file

@ -0,0 +1,68 @@
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 readonly IReadOnlyList<(ApplicationType, string)> Types = new[]
{
(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."),
(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."),
};
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) 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;
var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
if (design == null)
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta);
}
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet;
public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger;
public const EquipFlag StainFlags = EquipFlag.MainhandStain
| EquipFlag.OffhandStain
| EquipFlag.HeadStain
| EquipFlag.BodyStain
| EquipFlag.HandsStain
| EquipFlag.LegsStain
| EquipFlag.FeetStain
| EquipFlag.EarsStain
| EquipFlag.NeckStain
| EquipFlag.WristStain
| EquipFlag.RFingerStain
| EquipFlag.LFingerStain;
}

View file

@ -12,22 +12,10 @@ public class AutoDesign
{
public const string RevertName = "Revert";
[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 short GearsetIndex = -1;
public Design? Design;
public JobGroup Jobs;
public ApplicationType Type;
public short GearsetIndex = -1;
public string Name(bool incognito)
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
@ -41,10 +29,10 @@ public class AutoDesign
public AutoDesign Clone()
=> new()
{
Design = Design,
ApplicationType = ApplicationType,
Jobs = Jobs,
GearsetIndex = GearsetIndex,
Design = Design,
Type = Type,
Jobs = Jobs,
GearsetIndex = GearsetIndex,
};
public unsafe bool IsActive(Actor actor)
@ -64,9 +52,9 @@ public class AutoDesign
public JObject Serialize()
=> new()
{
["Design"] = Design?.Identifier.ToString(),
["ApplicationType"] = (uint)ApplicationType,
["Conditions"] = CreateConditionObject(),
["Design"] = Design?.Identifier.ToString(),
["Type"] = (uint)Type,
["Conditions"] = CreateConditionObject(),
};
private JObject CreateConditionObject()
@ -80,44 +68,6 @@ public class AutoDesign
return ret;
}
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;
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat()
=> Type.ApplyWhat(Design);
}

View file

@ -1,13 +1,11 @@
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;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Unlocks;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -15,54 +13,41 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Automation;
public class AutoDesignApplier : IDisposable
public sealed class AutoDesignApplier : IDisposable
{
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly CustomizeService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly ItemUnlockManager _itemUnlocks;
private readonly AutomationChanged _event;
private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly IClientState _clientState;
private readonly Configuration _config;
private readonly AutoDesignManager _manager;
private readonly StateManager _state;
private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset;
private readonly ActorManager _actors;
private readonly AutomationChanged _event;
private readonly ObjectManager _objects;
private readonly WeaponLoading _weapons;
private readonly HumanModelList _humans;
private readonly DesignMerger _designMerger;
private readonly IClientState _clientState;
private ActorState? _jobChangeState;
private readonly Dictionary<FullEquipType, (EquipItem, StateChanged.Source)> _jobChangeMainhand = [];
private readonly Dictionary<FullEquipType, (EquipItem, StateChanged.Source)> _jobChangeOffhand = [];
private readonly JobChangeState _jobChangeState;
private void ResetJobChange()
{
_jobChangeState = null;
_jobChangeMainhand.Clear();
_jobChangeOffhand.Clear();
}
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs,
CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks,
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors,
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
EquippedGearset equippedGearset)
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
{
_config = config;
_manager = manager;
_state = state;
_jobs = jobs;
_customizations = customizations;
_actors = actors;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_event = @event;
_objects = objects;
_weapons = weapons;
_humans = humans;
_clientState = clientState;
_equippedGearset = equippedGearset;
_jobs.JobChanged += OnJobChange;
_config = config;
_manager = manager;
_state = state;
_jobs = jobs;
_actors = actors;
_event = @event;
_objects = objects;
_weapons = weapons;
_humans = humans;
_clientState = clientState;
_equippedGearset = equippedGearset;
_designMerger = designMerger;
_jobChangeState = jobChangeState;
_jobs.JobChanged += OnJobChange;
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
_weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier);
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
@ -78,45 +63,46 @@ public class AutoDesignApplier : IDisposable
private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
{
if (_jobChangeState == null || !_config.EnableAutoDesigns)
if (!_jobChangeState.HasState || !_config.EnableAutoDesigns)
return;
var id = actor.GetIdentifier(_actors);
if (id == _jobChangeState.Identifier)
{
var current = _jobChangeState.BaseData.Item(slot);
var state = _jobChangeState.State!;
var current = state.BaseData.Item(slot);
switch (slot)
{
case EquipSlot.MainHand:
{
if (_jobChangeMainhand.TryGetValue(current.Type, out var data))
if (_jobChangeState.TryGetValue(current.Type, out var data))
{
Glamourer.Log.Verbose(
$"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2);
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand);
$"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2));
weapon = state.ModelData.Weapon(EquipSlot.MainHand);
}
break;
}
case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand():
case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand():
{
if (_jobChangeOffhand.TryGetValue(current.Type, out var data))
if (_jobChangeState.TryGetValue(current.Type, out var data))
{
Glamourer.Log.Verbose(
$"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2);
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand);
$"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
_state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2));
weapon = state.ModelData.Weapon(EquipSlot.OffHand);
}
ResetJobChange();
_jobChangeState.Reset();
break;
}
}
}
else
{
ResetJobChange();
_jobChangeState.Reset();
}
}
@ -134,7 +120,7 @@ public class AutoDesignApplier : IDisposable
break;
case AutomationChanged.Type.ChangeIdentifier when set.Enabled:
// Remove fixed state from the old identifiers assigned and the old enabled set, if any.
var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
var (oldIds, _, _) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
RemoveOld(oldIds);
ApplyNew(set); // Does not need to disable oldSet because same identifiers.
break;
@ -182,7 +168,7 @@ public class AutoDesignApplier : IDisposable
}
else if (_state.TryGetValue(id, out var state))
{
state.RemoveFixedDesignSources();
state.Sources.RemoveFixedDesignSources();
}
}
}
@ -196,9 +182,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.Sources.RemoveFixedDesignSources();
else if (_state.TryGetValue(id, out var state))
state.RemoveFixedDesignSources();
state.Sources.RemoveFixedDesignSources();
}
}
}
@ -255,56 +241,31 @@ public class AutoDesignApplier : IDisposable
else if (!GetPlayerSet(identifier, out set!))
{
if (state.UpdateTerritory(_clientState.TerritoryType) && _config.RevertManualChangesOnZoneChange)
_state.ResetState(state, StateChanged.Source.Game);
_state.ResetState(state, StateSource.Game);
return true;
}
var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange;
if (!respectManual)
_state.ResetState(state, StateChanged.Source.Game);
_state.ResetState(state, StateSource.Game);
Reduce(actor, state, set, respectManual, false);
return true;
}
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange)
{
EquipFlag totalEquipFlags = 0;
CustomizeFlag totalCustomizeFlags = 0;
CrestFlag totalCrestFlags = 0;
CustomizeParameterFlag totalParameterFlags = 0;
byte totalMetaFlags = 0;
if (set.BaseState == AutoDesignSet.Base.Game)
_state.ResetStateFixed(state, respectManual);
else if (!respectManual)
state.RemoveFixedDesignSources();
state.Sources.RemoveFixedDesignSources();
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
return;
foreach (var design in set.Designs)
{
if (!design.IsActive(actor))
continue;
if (design.ApplicationType is 0)
continue;
ref readonly var data = ref design.GetDesignData(state);
var source = design.Revert ? StateChanged.Source.Game : StateChanged.Source.Fixed;
if (!data.IsHuman)
continue;
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source);
ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source);
ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange);
ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source);
ReduceParameters(state, data, parameterFlags, ref totalParameterFlags, respectManual, source);
}
if (totalCustomizeFlags != 0)
state.ModelData.ModelId = 0;
var mergedDesign = _designMerger.Merge(
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks ?? [(d.Design, d.Type)]),
state.ModelData, true, false);
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false));
}
/// <summary> Get world-specific first and all-world afterward. </summary>
@ -330,221 +291,6 @@ public class AutoDesignApplier : IDisposable
}
}
private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual,
StateChanged.Source source)
{
crestFlags &= ~totalCrestFlags;
if (crestFlags == 0)
return;
foreach (var slot in CrestExtensions.AllRelevantSet)
{
if (!crestFlags.HasFlag(slot))
continue;
if (!respectManual || state[slot] is not StateChanged.Source.Manual)
_state.ChangeCrest(state, slot, design.Crest(slot), source);
totalCrestFlags |= slot;
}
}
private void ReduceParameters(ActorState state, in DesignData design, CustomizeParameterFlag parameterFlags,
ref CustomizeParameterFlag totalParameterFlags, bool respectManual, StateChanged.Source source)
{
parameterFlags &= ~totalParameterFlags;
if (parameterFlags == 0)
return;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
if (!parameterFlags.HasFlag(flag))
continue;
if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending)
_state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source);
totalParameterFlags |= flag;
}
}
private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual,
StateChanged.Source source, bool fromJobChange)
{
equipFlags &= ~totalEquipFlags;
if (equipFlags == 0)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var flag = slot.ToFlag();
if (equipFlags.HasFlag(flag))
{
var item = design.Item(slot);
if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _))
{
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual)
_state.ChangeItem(state, slot, item, source);
totalEquipFlags |= flag;
}
}
var stainFlag = slot.ToStainFlag();
if (equipFlags.HasFlag(stainFlag))
{
if (!respectManual || state[slot, true] is not StateChanged.Source.Manual)
_state.ChangeStain(state, slot, design.Stain(slot), source);
totalEquipFlags |= stainFlag;
}
}
if (equipFlags.HasFlag(EquipFlag.Mainhand))
{
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;
if (checkUnlock && checkState)
{
if (fromJobChange)
{
_jobChangeMainhand.TryAdd(item.Type, (item, source));
_jobChangeState = state;
}
else if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type)
{
_state.ChangeItem(state, EquipSlot.MainHand, item, source);
totalEquipFlags |= EquipFlag.Mainhand;
}
}
}
if (equipFlags.HasFlag(EquipFlag.Offhand))
{
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;
if (checkUnlock && checkState)
{
if (fromJobChange)
{
_jobChangeOffhand.TryAdd(item.Type, (item, source));
_jobChangeState = state;
}
else if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type)
{
_state.ChangeItem(state, EquipSlot.OffHand, item, source);
totalEquipFlags |= EquipFlag.Offhand;
}
}
}
if (equipFlags.HasFlag(EquipFlag.MainhandStain))
{
if (!respectManual || state[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)
_state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source);
totalEquipFlags |= EquipFlag.OffhandStain;
}
}
private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags,
bool respectManual, StateChanged.Source source)
{
customizeFlags &= ~totalCustomizeFlags;
if (customizeFlags == 0)
return;
var customize = state.ModelData.Customize;
CustomizeFlag fixFlags = 0;
// Skip anything not human.
if (!state.ModelData.IsHuman || !design.IsHuman)
return;
if (customizeFlags.HasFlag(CustomizeFlag.Clan))
{
if (!respectManual || state[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;
}
if (customizeFlags.HasFlag(CustomizeFlag.Gender))
{
if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual)
fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender);
customizeFlags &= ~CustomizeFlag.Gender;
totalCustomizeFlags |= CustomizeFlag.Gender;
}
if (fixFlags != 0)
_state.ChangeCustomize(state, customize, fixFlags, source);
if (customizeFlags.HasFlag(CustomizeFlag.Face))
{
if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source);
customizeFlags &= ~CustomizeFlag.Face;
totalCustomizeFlags |= CustomizeFlag.Face;
}
var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
var face = state.ModelData.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))
{
if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _))
continue;
if (!respectManual || state[index] is not StateChanged.Source.Manual)
_state.ChangeCustomize(state, index, value, source);
totalCustomizeFlags |= flag;
}
}
}
private void ReduceMeta(ActorState state, in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet,
ref byte totalMetaFlags, bool respectManual, StateChanged.Source source)
{
if (applyHat && (totalMetaFlags & 0x01) == 0)
{
if (!respectManual || state[ActorState.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)
_state.ChangeVisorState(state, design.IsVisorToggled(), source);
totalMetaFlags |= 0x02;
}
if (applyWeapon && (totalMetaFlags & 0x04) == 0)
{
if (!respectManual || state[ActorState.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)
_state.ChangeWetness(state, design.IsWet(), source);
totalMetaFlags |= 0x08;
}
}
internal static int NewGearsetId = -1;
private void OnEquippedGearset(string name, int id, int prior, byte _, byte job)

View file

@ -231,9 +231,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
{
var newDesign = new AutoDesign()
{
Design = design,
ApplicationType = AutoDesign.Type.All,
Jobs = _jobs.JobGroups[1],
Design = design,
Type = ApplicationType.All,
Jobs = _jobs.JobGroups[1],
};
set.Designs.Add(newDesign);
Save();
@ -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)
@ -480,8 +480,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
return null;
}
design = _designs.Designs.FirstOrDefault(d => d.Identifier == guid);
if (design == null)
if (!_designs.Designs.TryGetValue(guid, out design))
{
Glamourer.Messager.NotificationMessage(
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.",
@ -490,12 +489,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,
Design = design,
Type = applicationType & ApplicationType.All,
};
return ParseConditions(setName, jObj, ret) ? ret : null;
}
@ -550,7 +550,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 +583,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

@ -41,6 +41,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool UseFloatForColors { get; set; } = true;
public bool UseRgbForColors { get; set; } = true;
public bool ShowColorConfig { get; set; } = true;
public bool ChangeEntireItem { get; set; } = false;
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;

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,14 +37,18 @@ 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];
public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks
=> LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type));
#endregion
#region Serialization
@ -64,6 +70,7 @@ public sealed class Design : DesignBase, ISavable
["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(),
["Links"] = Links.Serialize(),
};
return ret;
}
@ -95,24 +102,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 +132,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 +169,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,6 +1,7 @@
using Dalamud.Interface.Internal.Notifications;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.State;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
@ -34,7 +35,7 @@ public class DesignBase
_designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
ApplyEquip = equipFlags & EquipFlagExtensions.All;
_designFlags = 0;
ApplyMeta = 0;
CustomizeSet = SetCustomizationSet(customize);
}
@ -45,7 +46,7 @@ public class DesignBase
ApplyCustomize = clone.ApplyCustomizeRaw;
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All;
_designFlags = clone._designFlags & (DesignFlags)0x0F;
ApplyMeta = clone.ApplyMeta & MetaExtensions.All;
}
/// <summary> Ensure that the customization set is updated when the design data changes. </summary>
@ -57,16 +58,6 @@ public class DesignBase
#region Application Data
[Flags]
private enum DesignFlags : byte
{
ApplyHatVisible = 0x01,
ApplyVisorState = 0x02,
ApplyWeaponVisible = 0x04,
ApplyWetness = 0x08,
WriteProtected = 0x10,
}
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
public CustomizeSet CustomizeSet { get; private set; }
@ -84,9 +75,10 @@ public class DesignBase
internal CustomizeFlag ApplyCustomizeRaw
=> _applyCustomize;
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
private bool _writeProtected;
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
{
@ -98,69 +90,30 @@ public class DesignBase
return true;
}
public bool DoApplyHatVisible()
=> _designFlags.HasFlag(DesignFlags.ApplyHatVisible);
public bool DoApplyVisorToggle()
=> _designFlags.HasFlag(DesignFlags.ApplyVisorState);
public bool DoApplyWeaponVisible()
=> _designFlags.HasFlag(DesignFlags.ApplyWeaponVisible);
public bool DoApplyWetness()
=> _designFlags.HasFlag(DesignFlags.ApplyWetness);
public bool DoApplyMeta(MetaIndex index)
=> ApplyMeta.HasFlag(index.ToFlag());
public bool WriteProtected()
=> _designFlags.HasFlag(DesignFlags.WriteProtected);
=> _writeProtected;
public bool SetApplyHatVisible(bool value)
public bool SetApplyMeta(MetaIndex index, bool value)
{
var newFlag = value ? _designFlags | DesignFlags.ApplyHatVisible : _designFlags & ~DesignFlags.ApplyHatVisible;
if (newFlag == _designFlags)
var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag();
if (newFlag == ApplyMeta)
return false;
_designFlags = newFlag;
return true;
}
public bool SetApplyVisorToggle(bool value)
{
var newFlag = value ? _designFlags | DesignFlags.ApplyVisorState : _designFlags & ~DesignFlags.ApplyVisorState;
if (newFlag == _designFlags)
return false;
_designFlags = newFlag;
return true;
}
public bool SetApplyWeaponVisible(bool value)
{
var newFlag = value ? _designFlags | DesignFlags.ApplyWeaponVisible : _designFlags & ~DesignFlags.ApplyWeaponVisible;
if (newFlag == _designFlags)
return false;
_designFlags = newFlag;
return true;
}
public bool SetApplyWetness(bool value)
{
var newFlag = value ? _designFlags | DesignFlags.ApplyWetness : _designFlags & ~DesignFlags.ApplyWetness;
if (newFlag == _designFlags)
return false;
_designFlags = newFlag;
ApplyMeta = newFlag;
return true;
}
public bool SetWriteProtected(bool value)
{
var newFlag = value ? _designFlags | DesignFlags.WriteProtected : _designFlags & ~DesignFlags.WriteProtected;
if (newFlag == _designFlags)
if (value == _writeProtected)
return false;
_designFlags = newFlag;
_writeProtected = value;
return true;
}
public bool DoApplyEquip(EquipSlot slot)
@ -298,9 +251,9 @@ public class DesignBase
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
}
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply");
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply");
}
else
{
@ -344,7 +297,7 @@ public class DesignBase
ret["Wetness"] = new JObject()
{
["Value"] = _designData.IsWet(),
["Apply"] = DoApplyWetness(),
["Apply"] = DoApplyMeta(MetaIndex.Wetness),
};
return ret;
@ -478,7 +431,7 @@ public class DesignBase
// Load the token and set application.
bool TryGetToken(CustomizeParameterFlag flag, [NotNullWhen(true)] out JToken? token)
{
token = parameters![flag.ToString()];
token = parameters[flag.ToString()];
if (token != null)
{
var apply = token["Apply"]?.ToObject<bool>() ?? false;
@ -493,8 +446,8 @@ public class DesignBase
void MigrateLipOpacity()
{
var token = parameters!["LipOpacity"]?["Percentage"]?.ToObject<float>();
var actualToken = parameters![CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"];
var token = parameters["LipOpacity"]?["Percentage"]?.ToObject<float>();
var actualToken = parameters[CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"];
if (token != null && actualToken == null)
design.GetDesignDataRef().Parameters.LipDiffuse.W = token.Value;
}
@ -575,15 +528,15 @@ public class DesignBase
design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff);
}
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
design.SetApplyHatVisible(metaValue.Enabled);
design.SetApplyMeta(MetaIndex.HatState, metaValue.Enabled);
design._designData.SetHatVisible(metaValue.ForcedValue);
metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
design.SetApplyWeaponVisible(metaValue.Enabled);
design.SetApplyMeta(MetaIndex.WeaponState, metaValue.Enabled);
design._designData.SetWeaponVisible(metaValue.ForcedValue);
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
design.SetApplyVisorToggle(metaValue.Enabled);
design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled);
design._designData.SetVisor(metaValue.ForcedValue);
}
@ -610,7 +563,7 @@ public class DesignBase
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
design._designData.SetIsWet(wetness.ForcedValue);
design.SetApplyWetness(wetness.Enabled);
design.SetApplyMeta(MetaIndex.Wetness, wetness.Enabled);
design._designData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId,
@ -664,17 +617,13 @@ public class DesignBase
try
{
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
out var writeProtected,
out var applyHat, out var applyVisor, out var applyWeapon);
out var writeProtected, out var applyMeta);
ApplyEquip = equipFlags;
ApplyCustomize = customizeFlags;
ApplyParameters = 0;
ApplyCrest = 0;
ApplyMeta = applyMeta;
SetWriteProtected(writeProtected);
SetApplyHatVisible(applyHat);
SetApplyVisorToggle(applyVisor);
SetApplyWeaponVisible(applyWeapon);
SetApplyWetness(true);
CustomizeSet = SetCustomizationSet(customize);
}
catch (Exception ex)

View file

@ -1,4 +1,5 @@
using Glamourer.Services;
using Glamourer.State;
using OtterGui;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -13,7 +14,7 @@ public class DesignBase64Migration
public const int Base64SizeV4 = 95;
public static unsafe DesignData MigrateBase64(ItemManager items, HumanModelList humans, string base64, out EquipFlag equipFlags,
out CustomizeFlag customizeFlags, out bool writeProtected, out bool applyHat, out bool applyVisor, out bool applyWeapon)
out CustomizeFlag customizeFlags, out bool writeProtected, out MetaFlag metaFlags)
{
static void CheckSize(int length, int requiredLength)
{
@ -25,9 +26,7 @@ public class DesignBase64Migration
byte applicationFlags;
ushort equipFlagsS;
var bytes = Convert.FromBase64String(base64);
applyHat = false;
applyVisor = false;
applyWeapon = false;
metaFlags = MetaFlag.Wetness;
var data = new DesignData();
switch (bytes[0])
{
@ -77,9 +76,12 @@ public class DesignBase64Migration
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
data.SetIsWet((applicationFlags & 0x02) != 0);
applyHat = (applicationFlags & 0x04) != 0;
applyWeapon = (applicationFlags & 0x08) != 0;
applyVisor = (applicationFlags & 0x10) != 0;
if ((applicationFlags & 0x04) != 0)
metaFlags |= MetaFlag.HatState;
if ((applicationFlags & 0x08) != 0)
metaFlags |= MetaFlag.WeaponState;
if ((applicationFlags & 0x10) != 0)
metaFlags |= MetaFlag.VisorState;
writeProtected = (applicationFlags & 0x20) != 0;
equipFlags = 0;
@ -161,16 +163,15 @@ public class DesignBase64Migration
}
}
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags,
bool setHat, bool setVisor, bool setWeapon, bool writeProtected, float alpha = 1.0f)
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, bool writeProtected, float alpha = 1.0f)
{
var data = stackalloc byte[Base64SizeV4];
data[0] = 5;
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
| (save.IsWet() ? 0x02 : 0)
| (setHat ? 0x04 : 0)
| (setWeapon ? 0x08 : 0)
| (setVisor ? 0x10 : 0)
| (meta.HasFlag(MetaFlag.HatState) ? 0x04 : 0)
| (meta.HasFlag(MetaFlag.WeaponState) ? 0x08 : 0)
| (meta.HasFlag(MetaFlag.VisorState) ? 0x10 : 0)
| (writeProtected ? 0x20 : 0));
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)

View file

@ -11,21 +11,10 @@ using OtterGui.Classes;
namespace Glamourer.Designs;
public class DesignColorUi
public class DesignColorUi(DesignColors colors, Configuration config)
{
private readonly DesignColors _colors;
private readonly DesignManager _designs;
private readonly Configuration _config;
private string _newName = string.Empty;
public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config)
{
_colors = colors;
_designs = designs;
_config = config;
}
public void Draw()
{
using var table = ImRaii.Table("designColors", 3, ImGuiTableFlags.RowBg);
@ -44,7 +33,7 @@ public class DesignColorUi
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), buttonSize,
"Revert the color used for missing design colors to its default.", _colors.MissingColor == DesignColors.MissingColorDefault,
"Revert the color used for missing design colors to its default.", colors.MissingColor == DesignColors.MissingColorDefault,
true))
{
changeString = DesignColors.MissingColorName;
@ -52,7 +41,7 @@ public class DesignColorUi
}
ImGui.TableNextColumn();
if (DrawColorButton(DesignColors.MissingColorName, _colors.MissingColor, out var newColor))
if (DrawColorButton(DesignColors.MissingColorName, colors.MissingColor, out var newColor))
{
changeString = DesignColors.MissingColorName;
changeValue = newColor;
@ -64,12 +53,12 @@ public class DesignColorUi
ImGuiUtil.HoverTooltip("This color is used when the color specified in a design is not available.");
var disabled = !_config.DeleteDesignModifier.IsActive();
var disabled = !config.DeleteDesignModifier.IsActive();
var tt = "Delete this color. This does not remove it from designs using it.";
if (disabled)
tt += $"\nHold {_config.DeleteDesignModifier} to delete.";
tt += $"\nHold {config.DeleteDesignModifier} to delete.";
foreach (var ((name, color), idx) in _colors.WithIndex())
foreach (var ((name, color), idx) in colors.WithIndex())
{
using var id = ImRaii.PushId(idx);
ImGui.TableNextColumn();
@ -97,7 +86,7 @@ public class DesignColorUi
? ("Specify a name for a new color first.", true)
: _newName is DesignColors.MissingColorName or DesignColors.AutomaticName
? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true)
: _colors.ContainsKey(_newName)
: colors.ContainsKey(_newName)
? ($"The color {_newName} already exists, please choose a different name.", true)
: ($"Add a new color {_newName} to your list.", false);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, disabled, true))
@ -119,9 +108,9 @@ public class DesignColorUi
if (changeString.Length > 0)
{
if (!changeValue.HasValue)
_colors.DeleteColor(changeString);
colors.DeleteColor(changeString);
else
_colors.SetColor(changeString, changeValue.Value);
colors.SetColor(changeString, changeValue.Value);
}
}

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;
@ -54,10 +55,10 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
design.ApplyCrest = crestFlags & CrestExtensions.All;
design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All;
design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head));
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
design.SetApplyWetness(true);
design.SetApplyMeta(MetaIndex.HatState, design.DoApplyEquip(EquipSlot.Head));
design.SetApplyMeta(MetaIndex.VisorState, design.DoApplyEquip(EquipSlot.Head));
design.SetApplyMeta(MetaIndex.WeaponState, design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
design.SetApplyMeta(MetaIndex.Wetness, true);
design.SetDesignData(_customize, data);
return design;
}
@ -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;
}
@ -125,16 +126,14 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
return null;
}
ret.SetApplyWetness(customize);
ret.SetApplyMeta(MetaIndex.Wetness, customize);
ret.ApplyCustomize = customize ? CustomizeFlagExtensions.AllRelevant : 0;
if (!equip)
{
ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.SetApplyHatVisible(false);
ret.SetApplyWeaponVisible(false);
ret.SetApplyVisorToggle(false);
ret.ApplyEquip = 0;
ret.ApplyCrest = 0;
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
}
return ret;

View file

@ -62,10 +62,10 @@ public unsafe struct DesignData
=> CrestVisibility.HasFlag(slot);
public FullEquipType MainhandType
public readonly FullEquipType MainhandType
=> _typeMainhand;
public FullEquipType OffhandType
public readonly FullEquipType OffhandType
=> _typeOffhand;
public readonly EquipItem Item(EquipSlot slot)
@ -186,6 +186,26 @@ public unsafe struct DesignData
return true;
}
public readonly bool GetMeta(MetaIndex index)
=> index switch
{
MetaIndex.Wetness => IsWet(),
MetaIndex.HatState => IsHatVisible(),
MetaIndex.VisorState => IsVisorToggled(),
MetaIndex.WeaponState => IsWeaponVisible(),
_ => false,
};
public bool SetMeta(MetaIndex index, bool value)
=> index switch
{
MetaIndex.Wetness => SetIsWet(value),
MetaIndex.HatState => SetHatVisible(value),
MetaIndex.VisorState => SetVisor(value),
MetaIndex.WeaponState => SetWeaponVisible(value),
_ => false,
};
public readonly bool IsWet()
=> (_states & 0x01) == 0x01;

View file

@ -0,0 +1,297 @@
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public class DesignEditor(
SaveService saveService,
DesignChanged designChanged,
CustomizeService customizations,
ItemManager items,
Configuration config)
: IDesignEditor
{
protected readonly DesignChanged DesignChanged = designChanged;
protected readonly SaveService SaveService = saveService;
protected readonly ItemManager Items = items;
protected readonly CustomizeService Customizations = customizations;
protected readonly Configuration Config = config;
protected readonly Dictionary<Guid, DesignData> UndoStore = [];
private bool _forceFullItemOff;
/// <summary> Whether an Undo for the given design is possible. </summary>
public bool CanUndo(Design? design)
=> design != null && UndoStore.ContainsKey(design.Identifier);
/// <inheritdoc/>
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default)
{
var design = (Design)data;
var oldValue = design.DesignData.Customize[idx];
switch (idx)
{
case CustomizeIndex.Race:
case CustomizeIndex.BodyType:
Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen.");
return;
case CustomizeIndex.Clan:
{
var customize = design.DesignData.Customize;
if (Customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
return;
if (!design.SetCustomize(Customizations, customize))
return;
break;
}
case CustomizeIndex.Gender:
{
var customize = design.DesignData.Customize;
if (Customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
return;
if (!design.SetCustomize(Customizations, customize))
return;
break;
}
default:
if (!Customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender,
design.DesignData.Customize.Face, idx, value)
|| !design.GetDesignDataRef().Customize.Set(idx, value))
return;
break;
}
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
}
/// <inheritdoc/>
public void ChangeEntireCustomize(object data, in CustomizeArray customize, CustomizeFlag apply, ApplySettings _ = default)
{
var design = (Design)data;
var (newCustomize, applied, changed) = Customizations.Combine(design.DesignData.Customize, customize, apply, true);
if (changed == 0)
return;
var oldCustomize = design.DesignData.Customize;
design.SetCustomize(Customizations, newCustomize);
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}.");
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed));
}
/// <inheritdoc/>
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default)
{
var design = (Design)data;
var old = design.DesignData.Parameters[flag];
if (!design.GetDesignDataRef().Parameters.Set(flag, value))
return;
var @new = design.DesignData.Parameters[flag];
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag));
}
/// <inheritdoc/>
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings _ = default)
{
var design = (Design)data;
switch (slot)
{
case EquipSlot.MainHand:
{
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
if (!Items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
return;
if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug(
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets));
return;
}
case EquipSlot.OffHand:
{
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
if (!Items.IsOffhandValid(currentOff.Type, item.ItemId, out item))
return;
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug(
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
return;
}
default:
{
if (!Items.IsItemValid(slot, item.Id, out item))
return;
var old = design.DesignData.Item(slot);
if (!design.GetDesignDataRef().SetItem(slot, item))
return;
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug(
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
return;
}
}
}
/// <inheritdoc/>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default)
{
var design = (Design)data;
if (Items.ValidateStain(stain, out var _, false).Length > 0)
return;
var oldStain = design.DesignData.Stain(slot);
if (!design.GetDesignDataRef().SetStain(slot, stain))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
}
/// <inheritdoc/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default)
{
if (item.HasValue)
ChangeItem(data, slot, item.Value, _);
if (stain.HasValue)
ChangeStain(data, slot, stain.Value, _);
}
/// <inheritdoc/>
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings _ = default)
{
var design = (Design)data;
var oldCrest = design.DesignData.Crest(slot);
if (!design.GetDesignDataRef().SetCrest(slot, crest))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
}
/// <inheritdoc/>
public void ChangeMetaState(object data, MetaIndex metaIndex, bool value, ApplySettings _ = default)
{
var design = (Design)data;
if (!design.GetDesignDataRef().SetMeta(metaIndex, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
}
/// <inheritdoc/>
public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default)
=> ApplyDesign(data, other.Design);
/// <inheritdoc/>
public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default)
{
var design = (Design)data;
UndoStore[design.Identifier] = design.DesignData;
foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta))
design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index));
if (!design.DesignData.IsHuman)
return;
ChangeEntireCustomize(design, other.DesignData.Customize, other.ApplyCustomize);
_forceFullItemOff = true;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
ChangeEquip(design, slot,
other.DoApplyEquip(slot) ? other.DesignData.Item(slot) : null,
other.DoApplyStain(slot) ? other.DesignData.Stain(slot) : null);
}
_forceFullItemOff = false;
foreach (var slot in Enum.GetValues<CrestFlag>().Where(other.DoApplyCrest))
ChangeCrest(design, slot, other.DesignData.Crest(slot));
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
}
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>
private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff,
out EquipItem? newGauntlets)
{
newOff = null;
newGauntlets = null;
if (newMain.Type != currentMain.Type)
{
var defaultOffhand = Items.GetDefaultOffhand(newMain);
if (!Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
return false;
newOff = o;
}
else if (!_forceFullItemOff && Config.ChangeEntireItem)
{
var defaultOffhand = Items.GetDefaultOffhand(newMain);
if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
newOff = o;
if (newMain.Type is FullEquipType.Fists && Items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g))
newGauntlets = g;
}
if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain))
return false;
if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value))
{
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
return false;
}
if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value))
{
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff);
return false;
}
return true;
}
}

View file

@ -105,7 +105,8 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.", NotificationType.Error);
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.",
NotificationType.Error);
}
CreateDuplicateLeaf(parent, design.Name.Text, design);

View file

@ -1,65 +1,58 @@
using Dalamud.Utility;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public class DesignManager
public sealed class DesignManager : DesignEditor
{
private readonly CustomizeService _customizations;
private readonly ItemManager _items;
private readonly HumanModelList _humans;
private readonly SaveService _saveService;
private readonly DesignChanged _event;
private readonly List<Design> _designs = [];
private readonly Dictionary<Guid, DesignData> _undoStore = [];
public IReadOnlyList<Design> Designs
=> _designs;
public readonly DesignStorage Designs;
private readonly HumanModelList _humans;
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
DesignChanged @event, HumanModelList humans)
DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config)
: base(saveService, @event, customizations, items, config)
{
_saveService = saveService;
_items = items;
_customizations = customizations;
_event = @event;
_humans = humans;
Designs = storage;
_humans = humans;
LoadDesigns(designLinkLoader);
CreateDesignFolder(saveService);
LoadDesigns();
MigrateOldDesigns();
designLinkLoader.SetAllObjects();
}
#region Design Management
/// <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()
private void LoadDesigns(DesignLinkLoader linkLoader)
{
_humans.Awaiter.Wait();
_customizations.Awaiter.Wait();
_items.ItemData.Awaiter.Wait();
Customizations.Awaiter.Wait();
Items.ItemData.Awaiter.Wait();
var stopwatch = Stopwatch.StartNew();
_designs.Clear();
Designs.Clear();
var skipped = 0;
ThreadLocal<List<(Design, string)>> designs = new(() => [], true);
Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) =>
Parallel.ForEach(SaveService.FileNames.Designs(), (f, _) =>
{
try
{
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)
@ -74,15 +67,15 @@ public class DesignManager
{
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path))
invalidNames.Add((design, path));
if (_designs.Any(d => d.Identifier == design.Identifier))
if (Designs.Contains(design.Identifier))
{
Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique.");
++skipped;
continue;
}
design.Index = _designs.Count;
_designs.Add(design);
design.Index = Designs.Count;
Designs.Add(design);
}
var failed = MoveInvalidNames(invalidNames);
@ -91,34 +84,30 @@ public class DesignManager
$"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}");
Glamourer.Log.Information(
$"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
_event.Invoke(DesignChanged.Type.ReloadedAll, null!, null);
$"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null);
}
/// <summary> Whether an Undo for the given design is possible. </summary>
public bool CanUndo(Design? design)
=> design != null && _undoStore.ContainsKey(design.Identifier);
/// <summary> Create a new temporary design without adding it to the manager. </summary>
public DesignBase CreateTemporary()
=> new(_customizations, _items);
=> new(Customizations, Items);
/// <summary> Create a new design of a given name. </summary>
public Design CreateEmpty(string name, bool handlePath)
{
var (actualName, path) = ParseName(name, handlePath);
var design = new Design(_customizations, _items)
var design = new Design(Customizations, Items)
{
CreationDate = DateTimeOffset.UtcNow,
LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Name = actualName,
Index = _designs.Count,
Index = Designs.Count,
};
_designs.Add(design);
Designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
_saveService.ImmediateSave(design);
_event.Invoke(DesignChanged.Type.Created, design, path);
SaveService.ImmediateSave(design);
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
return design;
}
@ -132,13 +121,13 @@ public class DesignManager
LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Name = actualName,
Index = _designs.Count,
Index = Designs.Count,
};
_designs.Add(design);
Designs.Add(design);
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
_saveService.ImmediateSave(design);
_event.Invoke(DesignChanged.Type.Created, design, path);
SaveService.ImmediateSave(design);
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
return design;
}
@ -152,26 +141,30 @@ public class DesignManager
LastEdit = DateTimeOffset.UtcNow,
Identifier = CreateNewGuid(),
Name = actualName,
Index = _designs.Count,
Index = Designs.Count,
};
_designs.Add(design);
Designs.Add(design);
Glamourer.Log.Debug(
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
_saveService.ImmediateSave(design);
_event.Invoke(DesignChanged.Type.Created, design, path);
SaveService.ImmediateSave(design);
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
return design;
}
/// <summary> Delete a design. </summary>
public void Delete(Design design)
{
foreach (var d in _designs.Skip(design.Index + 1))
foreach (var d in Designs.Skip(design.Index + 1))
--d.Index;
_designs.RemoveAt(design.Index);
_saveService.ImmediateDelete(design);
_event.Invoke(DesignChanged.Type.Deleted, design, null);
Designs.RemoveAt(design.Index);
SaveService.ImmediateDelete(design);
DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null);
}
#endregion
#region Edit Information
/// <summary> Rename a design. </summary>
public void Rename(Design design, string newName)
{
@ -181,9 +174,9 @@ public class DesignManager
design.Name = newName;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.Renamed, design, oldName);
DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName);
}
/// <summary> Change the description of a design. </summary>
@ -195,9 +188,9 @@ public class DesignManager
design.Description = description;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
}
public void ChangeColor(Design design, string newColor)
@ -208,9 +201,9 @@ public class DesignManager
design.Color = newColor;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor);
DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor);
}
/// <summary> Add a new tag to a design. The tags remain sorted. </summary>
@ -222,15 +215,11 @@ public class DesignManager
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
design.LastEdit = DateTimeOffset.UtcNow;
var idx = design.Tags.IndexOf(tag);
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx));
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx));
}
/// <summary> Remove a tag from a design if it exists. </summary>
public void RemoveTag(Design design, string tag)
=> RemoveTag(design, design.Tags.IndexOf(tag));
/// <summary> Remove a tag from a design by its index. </summary>
public void RemoveTag(Design design, int tagIdx)
{
@ -240,9 +229,9 @@ public class DesignManager
var oldTag = design.Tags[tagIdx];
design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray();
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx));
DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx));
}
/// <summary> Rename a tag from a design by its index. The tags stay sorted.</summary>
@ -255,9 +244,9 @@ public class DesignManager
design.Tags[tagIdx] = newTag;
Array.Sort(design.Tags);
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
_event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
}
/// <summary> Add an associated mod to a design. </summary>
@ -267,9 +256,9 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
}
/// <summary> Remove an associated mod from a design. </summary>
@ -279,9 +268,9 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}.");
_event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
}
/// <summary> Set the write protection status of a design. </summary>
@ -290,56 +279,14 @@ public class DesignManager
if (!design.SetWriteProtected(value))
return;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected.");
_event.Invoke(DesignChanged.Type.WriteProtection, design, value);
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value);
}
/// <summary> Change a customization value. </summary>
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
{
var oldValue = design.DesignData.Customize[idx];
#endregion
switch (idx)
{
case CustomizeIndex.Race:
case CustomizeIndex.BodyType:
Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen.");
return;
case CustomizeIndex.Clan:
{
var customize = design.DesignData.Customize;
if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
return;
if (!design.SetCustomize(_customizations, customize))
return;
break;
}
case CustomizeIndex.Gender:
{
var customize = design.DesignData.Customize;
if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
return;
if (!design.SetCustomize(_customizations, customize))
return;
break;
}
default:
if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender,
design.DesignData.Customize.Face, idx, value)
|| !design.GetDesignDataRef().Customize.Set(idx, value))
return;
break;
}
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
_saveService.QueueSave(design);
_event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
}
#region Edit Application Rules
/// <summary> Change whether to apply a specific customize value. </summary>
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
@ -348,115 +295,21 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
_event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
}
/// <summary> Change a non-weapon equipment piece. </summary>
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
{
if (!_items.IsItemValid(slot, item.Id, out item))
return;
var old = design.DesignData.Item(slot);
if (!design.GetDesignDataRef().SetItem(slot, item))
return;
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug(
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
_saveService.QueueSave(design);
_event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
}
/// <summary> Change a weapon. </summary>
public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item)
{
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
switch (slot)
{
case EquipSlot.MainHand:
var newOff = currentOff;
if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
return;
if (item.Type != currentMain.Type)
{
var defaultOffhand = _items.GetDefaultOffhand(item);
if (!_items.IsOffhandValid(item, defaultOffhand.ItemId, out newOff))
return;
}
if (!(design.GetDesignDataRef().SetItem(EquipSlot.MainHand, item)
| design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff)))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug(
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
return;
case EquipSlot.OffHand:
if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item))
return;
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug(
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
return;
default: return;
}
}
/// <summary> Change a customize parameter. </summary>
public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value)
{
var old = design.DesignData.Parameters[flag];
if (!design.GetDesignDataRef().Parameters.Set(flag, value))
return;
var @new = design.DesignData.Parameters[flag];
design.LastEdit = DateTimeOffset.UtcNow;
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
_saveService.QueueSave(design);
_event.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag));
DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
}
/// <summary> Change whether to apply a specific equipment piece. </summary>
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
public void ChangeApplyItem(Design design, EquipSlot slot, bool value)
{
if (!design.SetApplyEquip(slot, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
_event.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
}
/// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStain(Design design, EquipSlot slot, StainId stain)
{
if (_items.ValidateStain(stain, out _, false).Length > 0)
return;
var oldStain = design.DesignData.Stain(slot);
if (!design.GetDesignDataRef().SetStain(slot, stain))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
_event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
}
/// <summary> Change whether to apply a specific stain. </summary>
@ -466,22 +319,9 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
}
/// <summary> Change the crest visibility for any equipment piece. </summary>
public void ChangeCrest(Design design, CrestFlag slot, bool crest)
{
var oldCrest = design.DesignData.Crest(slot);
if (!design.GetDesignDataRef().SetCrest(slot, crest))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
_event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot);
}
/// <summary> Change whether to apply a specific crest visibility. </summary>
@ -491,49 +331,21 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
_event.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
}
/// <summary> Change the bool value of one of the meta flags. </summary>
public void ChangeMeta(Design design, ActorState.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),
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
};
if (!change)
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
}
/// <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),
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
};
if (!change)
if (!design.SetApplyMeta(metaIndex, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}.");
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
}
/// <summary> Change the application value of a customize parameter. </summary>
@ -543,83 +355,35 @@ public class DesignManager
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
SaveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}.");
_event.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
}
/// <summary> Apply an entire design based on its appliance rules piece by piece. </summary>
public void ApplyDesign(Design design, DesignBase other)
{
_undoStore[design.Identifier] = design.DesignData;
if (other.DoApplyWetness())
design.GetDesignDataRef().SetIsWet(other.DesignData.IsWet());
if (other.DoApplyHatVisible())
design.GetDesignDataRef().SetHatVisible(other.DesignData.IsHatVisible());
if (other.DoApplyVisorToggle())
design.GetDesignDataRef().SetVisor(other.DesignData.IsVisorToggled());
if (other.DoApplyWeaponVisible())
design.GetDesignDataRef().SetWeaponVisible(other.DesignData.IsWeaponVisible());
if (design.DesignData.IsHuman)
{
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
if (other.DoApplyCustomize(index))
ChangeCustomize(design, index, other.DesignData.Customize[index]);
}
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
if (other.DoApplyEquip(slot))
ChangeEquip(design, slot, other.DesignData.Item(slot));
if (other.DoApplyStain(slot))
ChangeStain(design, slot, other.DesignData.Stain(slot));
}
foreach (var slot in Enum.GetValues<CrestFlag>())
{
if (other.DoApplyCrest(slot))
ChangeCrest(design, slot, other.DesignData.Crest(slot));
}
}
if (other.DoApplyEquip(EquipSlot.MainHand))
ChangeWeapon(design, EquipSlot.MainHand, other.DesignData.Item(EquipSlot.MainHand));
if (other.DoApplyEquip(EquipSlot.OffHand))
ChangeWeapon(design, EquipSlot.OffHand, other.DesignData.Item(EquipSlot.OffHand));
if (other.DoApplyStain(EquipSlot.MainHand))
ChangeStain(design, EquipSlot.MainHand, other.DesignData.Stain(EquipSlot.MainHand));
if (other.DoApplyStain(EquipSlot.OffHand))
ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand));
}
#endregion
public void UndoDesignChange(Design design)
{
if (!_undoStore.Remove(design.Identifier, out var otherData))
if (!UndoStore.Remove(design.Identifier, out var otherData))
return;
var other = CreateTemporary();
other.SetDesignData(_customizations, otherData);
other.SetDesignData(Customizations, otherData);
ApplyDesign(design, other);
}
private void MigrateOldDesigns()
{
if (!File.Exists(_saveService.FileNames.MigrationDesignFile))
if (!File.Exists(SaveService.FileNames.MigrationDesignFile))
return;
var errors = 0;
var skips = 0;
var successes = 0;
var oldDesigns = _designs.ToList();
var oldDesigns = Designs.ToList();
try
{
var text = File.ReadAllText(_saveService.FileNames.MigrationDesignFile);
var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile);
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(text) ?? new Dictionary<string, string>();
var migratedFileSystemPaths = new Dictionary<string, string>(dict.Count);
foreach (var (name, base64) in dict)
@ -627,14 +391,14 @@ public class DesignManager
try
{
var actualName = Path.GetFileName(name);
var design = new Design(_customizations, _items)
var design = new Design(Customizations, Items)
{
CreationDate = File.GetCreationTimeUtc(_saveService.FileNames.MigrationDesignFile),
LastEdit = File.GetLastWriteTimeUtc(_saveService.FileNames.MigrationDesignFile),
CreationDate = File.GetCreationTimeUtc(SaveService.FileNames.MigrationDesignFile),
LastEdit = File.GetLastWriteTimeUtc(SaveService.FileNames.MigrationDesignFile),
Identifier = CreateNewGuid(),
Name = actualName,
};
design.MigrateBase64(_customizations, _items, _humans, base64);
design.MigrateBase64(Customizations, Items, _humans, base64);
if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate))
{
Add(design, $"Migrated old design to {design.Identifier}.");
@ -655,24 +419,24 @@ public class DesignManager
}
}
DesignFileSystem.MigrateOldPaths(_saveService, migratedFileSystemPaths);
DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths);
Glamourer.Log.Information(
$"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs.");
}
catch (Exception e)
{
Glamourer.Log.Error($"Could not migrate old design file {_saveService.FileNames.MigrationDesignFile}:\n{e}");
Glamourer.Log.Error($"Could not migrate old design file {SaveService.FileNames.MigrationDesignFile}:\n{e}");
}
try
{
File.Move(_saveService.FileNames.MigrationDesignFile,
Path.ChangeExtension(_saveService.FileNames.MigrationDesignFile, ".json.bak"));
Glamourer.Log.Information($"Moved migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file.");
File.Move(SaveService.FileNames.MigrationDesignFile,
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"));
Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not move migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file:\n{ex}");
Glamourer.Log.Error($"Could not move migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file:\n{ex}");
}
}
@ -702,7 +466,7 @@ public class DesignManager
{
try
{
var correctName = _saveService.FileNames.DesignFile(design);
var correctName = SaveService.FileNames.DesignFile(design);
File.Move(name, correctName, false);
Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
}
@ -722,7 +486,7 @@ public class DesignManager
while (true)
{
var guid = Guid.NewGuid();
if (_designs.All(d => d.Identifier != guid))
if (!Designs.Contains(guid))
return guid;
}
}
@ -732,18 +496,17 @@ public class DesignManager
/// Returns false if the design is already contained or if the identifier is already in use.
/// The design is treated as newly created and invokes an event.
/// </summary>
private bool Add(Design design, string? message)
private void Add(Design design, string? message)
{
if (_designs.Any(d => d == design || d.Identifier == design.Identifier))
return false;
if (Designs.Any(d => d == design || d.Identifier == design.Identifier))
return;
design.Index = _designs.Count;
_designs.Add(design);
design.Index = Designs.Count;
Designs.Add(design);
if (!message.IsNullOrEmpty())
Glamourer.Log.Debug(message);
_saveService.ImmediateSave(design);
_event.Invoke(DesignChanged.Type.Created, design, null);
return true;
SaveService.ImmediateSave(design);
DesignChanged.Invoke(DesignChanged.Type.Created, design, null);
}
/// <summary> Split a given string into its folder path and its name, if <paramref name="handlePath"/> is true. </summary>

View file

@ -0,0 +1,18 @@
using OtterGui.Services;
namespace Glamourer.Designs;
public class DesignStorage : List<Design>, IService
{
public bool TryGetValue(Guid identifier, [NotNullWhen(true)] out Design? design)
{
design = ByIdentifier(identifier);
return design != null;
}
public Design? ByIdentifier(Guid identifier)
=> this.FirstOrDefault(d => d.Identifier == identifier);
public bool Contains(Guid identifier)
=> ByIdentifier(identifier) != null;
}

View file

@ -0,0 +1,71 @@
using Glamourer.Designs.Links;
using Glamourer.GameData;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs;
public readonly record struct ApplySettings(
uint Key = 0,
StateSource Source = StateSource.Manual,
bool RespectManual = false,
bool FromJobChange = false,
bool UseSingleSource = false,
bool MergeLinks = false)
{
public static readonly ApplySettings Manual = new()
{
Key = 0,
Source = StateSource.Manual,
FromJobChange = false,
RespectManual = false,
UseSingleSource = false,
MergeLinks = false,
};
public static readonly ApplySettings Game = new()
{
Key = 0,
Source = StateSource.Game,
FromJobChange = false,
RespectManual = false,
UseSingleSource = false,
MergeLinks = false,
};
}
public interface IDesignEditor
{
/// <summary> Change a customization value. </summary>
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings = default);
/// <summary> Change an entire customize array according to the given flags. </summary>
public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings = default);
/// <summary> Change a customize parameter. </summary>
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue v, ApplySettings settings = default);
/// <summary> Change an equipment piece. </summary>
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
=> ChangeEquip(data, slot, item, null, settings);
/// <summary> Change the stain for any equipment piece. </summary>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default)
=> ChangeEquip(data, slot, null, stain, settings);
/// <summary> Change an equipment piece and its stain at the same time. </summary>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default);
/// <summary> Change the crest visibility for any equipment piece. </summary>
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default);
/// <summary> Change the bool value of one of the meta flags. </summary>
public void ChangeMetaState(object data, MetaIndex slot, bool value, ApplySettings settings = default);
/// <summary> Change all values applies from the given design. </summary>
public void ApplyDesign(object data, MergedDesign design, ApplySettings settings = default);
/// <summary> Change all values applies from the given design. </summary>
public void ApplyDesign(object data, DesignBase design, ApplySettings settings = default);
}

View file

@ -0,0 +1,19 @@
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,
None,
};

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,85 @@
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);
}
public void ChangeApplicationType(Design parent, int idx, LinkOrder order, ApplicationType applicationType)
{
applicationType &= ApplicationType.All;
if (!parent.Links.ChangeApplicationRules(idx, order, applicationType, out var old))
return;
_saveService.QueueSave(parent);
Glamourer.Log.Debug($"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {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))
continue;
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,272 @@
using Glamourer.Automation;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Unlocks;
using OtterGui.Services;
using Penumbra.GameData.Enums;
namespace Glamourer.Designs.Links;
public class DesignMerger(
DesignManager designManager,
CustomizeService _customize,
Configuration _config,
ItemUnlockManager _itemUnlocks,
CustomizeUnlockManager _customizeUnlocks) : IService
{
public MergedDesign Merge(LinkContainer designs, in DesignData baseRef, bool respectOwnership, bool modAssociations)
=> Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), baseRef, respectOwnership, modAssociations);
public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership,
bool modAssociations)
{
var ret = new MergedDesign(designManager);
CustomizeFlag fixFlags = 0;
respectOwnership &= _config.UnlockedItemMode;
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 ? StateSource.Game : StateSource.Manual;
if (!data.IsHuman)
continue;
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design);
ReduceMeta(data, applyMeta, ret, source);
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership);
ReduceEquip(data, equipFlags, ret, source, respectOwnership);
ReduceMainhands(data, equipFlags, ret, source, respectOwnership);
ReduceOffhands(data, equipFlags, ret, source, respectOwnership);
ReduceCrests(data, crestFlags, ret, source);
ReduceParameters(data, parameterFlags, ret, source);
ReduceMods(design as Design, ret, modAssociations);
}
ApplyFixFlags(ret, fixFlags);
return ret;
}
private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations)
{
if (design == null || !modAssociations)
return;
foreach (var (mod, settings) in design.AssociatedMods)
ret.AssociatedMods.TryAdd(mod, settings);
}
private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source)
{
applyMeta &= ~ret.Design.ApplyMeta;
foreach (var index in MetaExtensions.AllRelevant)
{
if (!applyMeta.HasFlag(index.ToFlag()))
continue;
ret.Design.SetApplyMeta(index, true);
ret.Design.GetDesignDataRef().SetMeta(index, design.GetMeta(index));
ret.Sources[index] = source;
}
}
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource 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.Sources[slot] = source;
}
}
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
StateSource 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.Sources[flag] = source;
}
}
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
bool respectOwnership)
{
equipFlags &= ~ret.Design.ApplyEquip;
if (equipFlags == 0)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var flag = slot.ToFlag();
if (equipFlags.HasFlag(flag))
{
var item = design.Item(slot);
if (!respectOwnership || _itemUnlocks.IsUnlocked(item.Id, out _))
ret.Design.GetDesignDataRef().SetItem(slot, item);
ret.Design.SetApplyEquip(slot, true);
ret.Sources[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.Sources[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.Sources[slot, true] = source;
}
}
}
private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
bool respectOwnership)
{
if (!equipFlags.HasFlag(EquipFlag.Mainhand))
return;
var weapon = design.Item(EquipSlot.MainHand);
if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _))
return;
if (!ret.Design.DoApplyEquip(EquipSlot.MainHand))
{
ret.Design.SetApplyEquip(EquipSlot.MainHand, true);
ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon);
}
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
}
private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership)
{
if (!equipFlags.HasFlag(EquipFlag.Offhand))
return;
var weapon = design.Item(EquipSlot.OffHand);
if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _))
return;
if (!ret.Design.DoApplyEquip(EquipSlot.OffHand))
{
ret.Design.SetApplyEquip(EquipSlot.OffHand, true);
ret.Design.GetDesignDataRef().SetItem(EquipSlot.OffHand, weapon);
}
if (weapon.Valid)
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
}
private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret,
StateSource source, bool respectOwnership)
{
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.Sources[CustomizeIndex.Clan] = source;
ret.Sources[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.Sources[CustomizeIndex.Gender] = source;
}
if (customizeFlags.HasFlag(CustomizeFlag.Face))
{
customize[CustomizeIndex.Face] = design.Customize.Face;
ret.Design.SetApplyCustomize(CustomizeIndex.Face, true);
customizeFlags &= ~CustomizeFlag.Face;
ret.Sources[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;
if (data.HasValue && respectOwnership && !_customizeUnlocks.IsUnlocked(data.Value, out _))
continue;
customize[index] = data?.Value ?? value;
ret.Design.SetApplyCustomize(index, true);
ret.Sources[index] = source;
fixFlags &= ~flag;
}
ret.Design.SetCustomize(_customize, customize);
}
private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags)
{
if (fixFlags == 0)
return;
var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan)
? ret.Sources[CustomizeIndex.Clan]
: ret.Sources[CustomizeIndex.Gender];
foreach (var index in Enum.GetValues<CustomizeIndex>())
{
var flag = index.ToFlag();
if (!fixFlags.HasFlag(flag))
continue;
ret.Sources[index] = source;
ret.Design.SetApplyCustomize(index, true);
}
}
}

View file

@ -0,0 +1,193 @@
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 bool ChangeApplicationRules(int idx, LinkOrder order, ApplicationType type, out ApplicationType old)
{
var list = order switch
{
LinkOrder.Before => Before,
LinkOrder.After => After,
_ => throw new ArgumentException("Invalid link order."),
};
old = list[idx].Type;
if (idx < 0 || idx >= list.Count || old == type)
return false;
list[idx] = list[idx] with { Type = type };
return true;
}
public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error)
{
if (parent == child)
{
error = $"Can not link {parent.Incognito} with itself.";
return false;
}
if (parent.Links.Contains(child))
{
error = $"Design {parent.Incognito} already contains a direct link to {child.Incognito}.";
return false;
}
if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order))
{
error = $"Adding {child.Incognito} to {parent.Incognito}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.Incognito} to {parent.Incognito}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)
{
after.Add(new JObject
{
["Design"] = link.Link.Identifier,
["Type"] = (uint)link.Type,
});
}
return new JObject
{
[nameof(Before)] = before,
[nameof(After)] = after,
};
}
}

View file

@ -0,0 +1,51 @@
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Designs.Links;
public sealed class MergedDesign
{
public MergedDesign(DesignManager designManager)
{
Design = designManager.CreateTemporary();
Design.ApplyEquip = 0;
Design.ApplyCustomize = 0;
Design.ApplyCrest = 0;
Design.ApplyParameters = 0;
Design.ApplyMeta = 0;
}
public MergedDesign(DesignBase design)
{
Design = design;
if (design.DoApplyEquip(EquipSlot.MainHand))
{
var weapon = design.DesignData.Item(EquipSlot.MainHand);
if (weapon.Valid)
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
}
if (design.DoApplyEquip(EquipSlot.OffHand))
{
var weapon = design.DesignData.Item(EquipSlot.OffHand);
if (weapon.Valid)
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
}
}
public MergedDesign(Design design)
: this((DesignBase)design)
{
foreach (var (mod, settings) in design.AssociatedMods)
AssociatedMods[mod] = settings;
}
public readonly DesignBase Design;
public readonly Dictionary<FullEquipType, (EquipItem, StateSource)> Weapons = new(4);
public readonly SortedList<Mod, ModSettings> AssociatedMods = [];
public StateSources Sources = new();
}

View file

@ -0,0 +1,69 @@
using Glamourer.State;
namespace Glamourer.Designs;
public enum MetaIndex
{
Wetness = StateIndex.MetaWetness,
HatState = StateIndex.MetaHatState,
VisorState = StateIndex.MetaVisorState,
WeaponState = StateIndex.MetaWeaponState,
ModelId = StateIndex.MetaModelId,
}
[Flags]
public enum MetaFlag : byte
{
Wetness = 0x01,
HatState = 0x02,
VisorState = 0x04,
WeaponState = 0x08,
}
public static class MetaExtensions
{
public static readonly IReadOnlyList<MetaIndex> AllRelevant =
[MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState];
public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
public static MetaFlag ToFlag(this MetaIndex index)
=> index switch
{
MetaIndex.Wetness => MetaFlag.Wetness,
MetaIndex.HatState => MetaFlag.HatState,
MetaIndex.VisorState => MetaFlag.VisorState,
MetaIndex.WeaponState => MetaFlag.WeaponState,
_ => (MetaFlag)byte.MaxValue,
};
public static MetaIndex ToIndex(this MetaFlag index)
=> index switch
{
MetaFlag.Wetness => MetaIndex.Wetness,
MetaFlag.HatState => MetaIndex.HatState,
MetaFlag.VisorState => MetaIndex.VisorState,
MetaFlag.WeaponState => MetaIndex.WeaponState,
_ => (MetaIndex)byte.MaxValue,
};
public static string ToName(this MetaIndex index)
=> index switch
{
MetaIndex.HatState => "Hat Visible",
MetaIndex.VisorState => "Visor Toggled",
MetaIndex.WeaponState => "Weapon Visible",
MetaIndex.Wetness => "Force Wetness",
_ => "Unknown Meta",
};
public static string ToTooltip(this MetaIndex index)
=> index switch
{
MetaIndex.HatState => "Hide or show the characters head gear.",
MetaIndex.VisorState => "Toggle the visor state of the characters head gear.",
MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.",
MetaIndex.Wetness => "Force the character to be wet or not.",
_ => string.Empty,
};
}

View file

@ -12,7 +12,7 @@ namespace Glamourer.Events;
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class DesignChanged()
public sealed class DesignChanged()
: EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged))
{
public enum Type
@ -50,13 +50,19 @@ 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,
/// <summary> An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. </summary>
EntireCustomize,
/// <summary> An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
Equip,
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. </summary>
Weapon,
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
@ -92,6 +98,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

@ -2,68 +2,59 @@ using Glamourer.Interop.Structs;
using Glamourer.State;
using OtterGui.Classes;
namespace Glamourer.Events;
/// <summary>
/// Triggered when a Design is edited in any way.
/// <list type="number">
/// <item>Parameter is the type of the change </item>
/// <item>Parameter is the changed saved state. </item>
/// <item>Parameter is the existing actors using this saved state. </item>
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class StateChanged()
: EventWrapper<StateChanged.Type, StateChanged.Source, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
namespace Glamourer.Events
{
public enum Type
/// <summary>
/// Triggered when a Design is edited in any way.
/// <list type="number">
/// <item>Parameter is the type of the change </item>
/// <item>Parameter is the changed saved state. </item>
/// <item>Parameter is the existing actors using this saved state. </item>
/// <item>Parameter is any additional data depending on the type of change. </item>
/// </list>
/// </summary>
public sealed class StateChanged()
: EventWrapper<StateChanged.Type, StateSource, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
{
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
Model,
public enum Type
{
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
Model,
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
EntireCustomize,
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
EntireCustomize,
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize,
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
Customize,
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
Equip,
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
Equip,
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
Weapon,
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
Weapon,
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Stain,
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Stain,
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
Crest,
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
Crest,
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
Parameter,
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
Parameter,
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
Design,
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
Design,
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
Reset,
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
Reset,
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Other,
}
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
Other,
}
public enum Source : byte
{
Game,
Manual,
Fixed,
Ipc,
// Only used for CustomizeParameters.
Pending,
}
public enum Priority
{
GlamourerIpc = int.MinValue,
public enum Priority
{
GlamourerIpc = int.MinValue,
}
}
}

View file

@ -1,19 +1,28 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.State;
namespace Glamourer.Gui.Customization;
public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
{
private IDesignEditor _editor;
private object _object;
public readonly CustomizeParameterFlag Flag = flag;
public bool Locked;
public bool DisplayApplication;
public bool AllowRevert;
public Action<CustomizeParameterValue> ValueSetter = null!;
public Action<bool> ApplySetter = null!;
public readonly void ChangeParameter(CustomizeParameterValue value)
=> _editor.ChangeCustomizeParameter(_object, Flag, value, ApplySettings.Manual);
public readonly void ChangeApplyParameter(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyParameter(design, Flag, value);
}
public CustomizeParameterValue CurrentValue = data.Parameters[flag];
public CustomizeParameterValue GameValue;
public bool CurrentApply;
@ -21,19 +30,20 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des
public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag)
=> new(flag, design.DesignData)
{
_editor = manager,
_object = design,
Locked = design.WriteProtected(),
DisplayApplication = true,
CurrentApply = design.DoApplyParameter(flag),
ValueSetter = v => manager.ChangeCustomizeParameter(design, flag, v),
ApplySetter = v => manager.ChangeApplyParameter(design, flag, v),
};
public static CustomizeParameterDrawData FromState(StateManager manager, ActorState state, CustomizeParameterFlag flag)
=> new(flag, state.ModelData)
{
_editor = manager,
_object = state,
Locked = state.IsLocked,
DisplayApplication = false,
ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual),
GameValue = state.BaseData.Parameters[flag],
AllowRevert = true,
};

View file

@ -195,7 +195,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
using (_ = ImRaii.Disabled(data.Locked || noHighlights))
{
if (ImGui.ColorEdit3("##value", ref value, GetFlags()))
data.ValueSetter(new CustomizeParameterValue(value));
data.ChangeParameter(new CustomizeParameterValue(value));
}
if (noHighlights)
@ -215,7 +215,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
using (_ = ImRaii.Disabled(data.Locked))
{
if (ImGui.ColorEdit4("##value", ref value, GetFlags() | ImGuiColorEditFlags.AlphaPreviewHalf))
data.ValueSetter(new CustomizeParameterValue(value));
data.ChangeParameter(new CustomizeParameterValue(value));
}
DrawRevert(data);
@ -231,7 +231,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
using (_ = ImRaii.Disabled(data.Locked))
{
if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f))
data.ValueSetter(new CustomizeParameterValue(value));
data.ChangeParameter(new CustomizeParameterValue(value));
}
DrawRevert(data);
@ -247,7 +247,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
using (_ = ImRaii.Disabled(data.Locked))
{
if (ImGui.SliderFloat("##value", ref value, -100f, 300, "%.2f"))
data.ValueSetter(new CustomizeParameterValue(value / 100f));
data.ChangeParameter(new CustomizeParameterValue(value / 100f));
ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging.");
}
@ -262,7 +262,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
return;
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl)
data.ValueSetter(data.GameValue);
data.ChangeParameter(data.GameValue);
ImGuiUtil.HoverTooltip("Hold Control and Right-click to revert to game values.");
}
@ -271,7 +271,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
{
if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled,
data.Locked))
data.ApplySetter(enabled);
data.ChangeApplyParameter(enabled);
}
private void DrawApplyAndLabel(in CustomizeParameterDrawData data)
@ -310,6 +310,6 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
_copy.HasValue ? "Paste the currently copied value." : "No value copied yet.", locked || !_copy.HasValue, true))
data.ValueSetter(_copy!.Value);
data.ChangeParameter(_copy!.Value);
}
}

View file

@ -5,7 +5,7 @@ using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Events;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.State;
@ -32,7 +32,7 @@ public sealed class DesignQuickBar : Window, IDisposable
private readonly ImRaii.Style _windowPadding = new();
private readonly ImRaii.Color _windowColor = new();
private DateTime _keyboardToggle = DateTime.UnixEpoch;
private int _numButtons = 0;
private int _numButtons;
public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState,
ObjectManager objects, AutoDesignApplier autoDesignApplier)
@ -163,7 +163,7 @@ public sealed class DesignQuickBar : Window, IDisposable
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
_stateManager.ApplyDesign(state, design, ApplySettings.Manual);
}
public void DrawRevertButton(Vector2 buttonSize)
@ -189,7 +189,7 @@ public sealed class DesignQuickBar : Window, IDisposable
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available);
if (clicked)
_stateManager.ResetState(state!, StateChanged.Source.Manual);
_stateManager.ResetState(state!, StateSource.Manual);
}
public void DrawRevertAutomationButton(Vector2 buttonSize)
@ -257,7 +257,7 @@ public sealed class DesignQuickBar : Window, IDisposable
ImGui.SameLine();
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available);
if (clicked)
_stateManager.ResetAdvancedState(state!, StateChanged.Source.Manual);
_stateManager.ResetAdvancedState(state!, StateSource.Manual);
}
private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip,

View file

@ -1,29 +1,45 @@
using Dalamud.Game.Inventory;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Designs;
using Glamourer.State;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
public struct EquipDrawData(EquipSlot slot, in DesignData designData)
{
public readonly EquipSlot Slot = slot;
public bool Locked;
public bool DisplayApplication;
public bool AllowRevert;
private IDesignEditor _editor;
private object _object;
public readonly EquipSlot Slot = slot;
public bool Locked;
public bool DisplayApplication;
public bool AllowRevert;
public Action<EquipItem> ItemSetter = null!;
public Action<StainId> StainSetter = null!;
public Action<bool> ApplySetter = null!;
public Action<bool> ApplyStainSetter = null!;
public EquipItem CurrentItem = designData.Item(slot);
public StainId CurrentStain = designData.Stain(slot);
public EquipItem GameItem = default;
public StainId GameStain = default;
public bool CurrentApply;
public bool CurrentApplyStain;
public readonly void SetItem(EquipItem item)
=> _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual);
public readonly void SetStain(StainId stain)
=> _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual);
public readonly void SetApplyItem(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyItem(design, Slot, value);
}
public readonly void SetApplyStain(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_object;
manager.ChangeApplyStain(design, Slot, value);
}
public EquipItem CurrentItem = designData.Item(slot);
public StainId CurrentStain = designData.Stain(slot);
public EquipItem GameItem = default;
public StainId GameStain = default;
public bool CurrentApply;
public bool CurrentApplyStain;
public readonly Gender CurrentGender = designData.Customize.Gender;
public readonly Race CurrentRace = designData.Customize.Race;
@ -31,12 +47,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot)
=> new(slot, design.DesignData)
{
ItemSetter = slot.IsEquipment() || slot.IsAccessory()
? i => manager.ChangeEquip(design, slot, i)
: i => manager.ChangeWeapon(design, slot, i),
StainSetter = i => manager.ChangeStain(design, slot, i),
ApplySetter = b => manager.ChangeApplyEquip(design, slot, b),
ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b),
_editor = manager,
_object = design,
CurrentApply = design.DoApplyEquip(slot),
CurrentApplyStain = design.DoApplyStain(slot),
Locked = design.WriteProtected(),
@ -46,8 +58,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot)
=> new(slot, state.ModelData)
{
ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual),
StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual),
_editor = manager,
_object = state,
Locked = state.IsLocked,
DisplayApplication = false,
GameItem = state.BaseData.Item(slot),

View file

@ -205,7 +205,7 @@ public class EquipmentDrawer
{
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Id != current.CurrentItem.PrimaryId.Id)
current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
current.SetItem(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
}
ImGui.SameLine();
@ -214,7 +214,7 @@ public class EquipmentDrawer
{
var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue);
if (newType.Id != current.CurrentItem.SecondaryId.Id)
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
}
ImGui.SameLine();
@ -223,7 +223,7 @@ public class EquipmentDrawer
{
var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant.Id != current.CurrentItem.Variant.Id)
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
newVariant));
}
}
@ -239,7 +239,7 @@ public class EquipmentDrawer
var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
if (newStainId != data.CurrentStain.Id)
data.StainSetter(newStainId);
data.SetStain(newStainId);
}
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
@ -252,7 +252,7 @@ public class EquipmentDrawer
{
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Id != data.CurrentItem.PrimaryId.Id)
data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
data.SetItem(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
}
ImGui.SameLine();
@ -261,7 +261,7 @@ public class EquipmentDrawer
{
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant != data.CurrentItem.Variant)
data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
data.SetItem(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
}
}
@ -365,7 +365,7 @@ public class EquipmentDrawer
mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand);
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
ImGui.SameLine();
using (var group = ImRaii.Group())
using (ImRaii.Group())
{
DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left);
if (mainhand.DisplayApplication)
@ -391,7 +391,7 @@ public class EquipmentDrawer
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
ImGui.SameLine();
using (var group = ImRaii.Group())
using (ImRaii.Group())
{
DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left);
if (offhand.DisplayApplication)
@ -420,12 +420,12 @@ public class EquipmentDrawer
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength);
if (change)
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
data.StainSetter(stain.RowIndex);
data.SetStain(stain.RowIndex);
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
data.StainSetter(Stain.None.RowIndex);
data.SetStain(Stain.None.RowIndex);
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var id))
data.StainSetter(Stain.None.RowIndex);
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _))
data.SetStain(Stain.None.RowIndex);
}
private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)
@ -441,13 +441,13 @@ public class EquipmentDrawer
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_requiredComboWidth);
if (change)
data.ItemSetter(combo.CurrentSelection);
data.SetItem(combo.CurrentSelection);
else if (combo.CustomVariant.Id > 0)
data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot),
out var item))
data.ItemSetter(item);
data.SetItem(item);
}
private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear,
@ -467,9 +467,9 @@ public class EquipmentDrawer
(true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.", revertItem, true),
(true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.", clearItem, true),
(true, false, true) => ("Control and Right-Click to revert to game.", revertItem, true),
(true, false, false) => ("Control and Right-Click to revert to game.", (T?)default, false),
(true, false, false) => ("Control and Right-Click to revert to game.", default, false),
(false, true, _) => ("Right-click to clear.", clearItem, true),
(false, false, _) => (string.Empty, (T?)default, false),
(false, false, _) => (string.Empty, default, false),
};
ImGuiUtil.HoverTooltip(tt);
@ -502,11 +502,11 @@ public class EquipmentDrawer
if (changedItem != null)
{
mainhand.ItemSetter(changedItem.Value);
mainhand.SetItem(changedItem.Value);
if (changedItem.Value.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand())
{
offhand.CurrentItem = _items.GetDefaultOffhand(changedItem.Value);
offhand.ItemSetter(offhand.CurrentItem);
offhand.SetItem(offhand.CurrentItem);
}
mainhand.CurrentItem = changedItem.Value;
@ -533,18 +533,18 @@ public class EquipmentDrawer
UiHelpers.OpenCombo($"##{combo.Label}");
if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
_requiredComboWidth))
offhand.ItemSetter(combo.CurrentSelection);
offhand.SetItem(combo.CurrentSelection);
var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem);
if (ResetOrClear(locked, open, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
offhand.ItemSetter(item);
if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
offhand.SetItem(item);
}
private static void DrawApply(in EquipDrawData data)
{
if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this item when applying the Design.", data.CurrentApply, out var enabled,
data.Locked))
data.ApplySetter(enabled);
data.SetApplyItem(enabled);
}
private static void DrawApplyStain(in EquipDrawData data)
@ -552,7 +552,7 @@ public class EquipmentDrawer
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain,
out var enabled,
data.Locked))
data.ApplyStainSetter(enabled);
data.SetApplyStain(enabled);
}
#endregion

View file

@ -1,4 +1,4 @@
using Glamourer.Events;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
@ -11,7 +11,7 @@ using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
public class PenumbraChangedItemTooltip : IDisposable
public sealed class PenumbraChangedItemTooltip : IDisposable
{
private readonly PenumbraService _penumbra;
private readonly StateManager _stateManager;
@ -111,24 +111,24 @@ public class PenumbraChangedItemTooltip : IDisposable
switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift)
{
case (false, false):
Glamourer.Log.Information($"Applying {item.Name} to Right Finger.");
Glamourer.Log.Debug($"Applying {item.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, item, state);
_stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, EquipSlot.RFinger, item, ApplySettings.Manual);
break;
case (false, true):
Glamourer.Log.Information($"Applying {item.Name} to Left Finger.");
Glamourer.Log.Debug($"Applying {item.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, item, state);
_stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, EquipSlot.LFinger, item, ApplySettings.Manual);
break;
case (true, false) when last.Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger.");
Glamourer.Log.Debug($"Re-Applying {last.Name} to Right Finger.");
SetLastItem(EquipSlot.RFinger, default, state);
_stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, EquipSlot.RFinger, last, ApplySettings.Manual);
break;
case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid:
Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger.");
Glamourer.Log.Debug($"Re-Applying {last.Name} to Left Finger.");
SetLastItem(EquipSlot.LFinger, default, state);
_stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, EquipSlot.LFinger, last, ApplySettings.Manual);
break;
}
@ -136,15 +136,15 @@ public class PenumbraChangedItemTooltip : IDisposable
default:
if (ImGui.GetIO().KeyCtrl && last.Valid)
{
Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}.");
Glamourer.Log.Debug($"Re-Applying {last.Name} to {slot.ToName()}.");
SetLastItem(slot, default, state);
_stateManager.ChangeItem(state, slot, last, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, slot, last, ApplySettings.Manual);
}
else
{
Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}.");
Glamourer.Log.Debug($"Applying {item.Name} to {slot.ToName()}.");
SetLastItem(slot, item, state);
_stateManager.ChangeItem(state, slot, item, StateChanged.Source.Manual);
_stateManager.ChangeItem(state, slot, item, ApplySettings.Manual);
}
return;

View file

@ -5,7 +5,6 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
@ -61,13 +60,13 @@ public class ActorPanel(
if (_importService.CreateDatTarget(out var dat))
{
_stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual);
_stateManager.ChangeEntireCustomize(_state!, dat.Customize, CustomizeApplicationFlags, ApplySettings.Manual);
Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.",
NotificationType.Success, false);
}
else if (_importService.CreateCharaTarget(out var designBase, out var name))
{
_stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual);
_stateManager.ApplyDesign(_state!, designBase, ApplySettings.Manual);
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success,
false);
}
@ -139,9 +138,9 @@ public class ActorPanel(
return;
if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw))
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
_stateManager.ChangeEntireCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, ApplySettings.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));
}
@ -159,7 +158,7 @@ public class ActorPanel(
var data = EquipDrawData.FromState(_stateManager, _state!, slot);
_equipmentDrawer.DrawEquip(data);
if (usedAllStain)
_stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual);
_stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual);
}
var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand);
@ -187,21 +186,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!));
}
}
@ -264,7 +263,7 @@ public class ActorPanel(
}
if (turnHuman)
_stateManager.TurnHuman(_state, StateChanged.Source.Manual);
_stateManager.TurnHuman(_state, StateSource.Manual);
}
private HeaderDrawer.Button SetFromClipboardButton()
@ -316,9 +315,9 @@ public class ActorPanel(
private void SaveDesignOpen()
{
ImGui.OpenPopup("Save as Design");
_newName = _state!.Identifier.ToName();
_newName = _state!.Identifier.ToName();
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
}
private void SaveDesignDrawPopup()
@ -340,7 +339,7 @@ public class ActorPanel(
var text = ImGui.GetClipboardText();
var design = _converter.FromBase64(text, applyCustomize, applyGear, out _)
?? throw new Exception("The clipboard did not contain valid data.");
_stateManager.ApplyDesign(design, _state!, StateChanged.Source.Manual);
_stateManager.ApplyDesign(_state!, design, ApplySettings.Manual with { MergeLinks = true });
}
catch (Exception ex)
{
@ -368,7 +367,7 @@ public class ActorPanel(
{
if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.",
_state!.IsLocked))
_stateManager.ResetState(_state!, StateChanged.Source.Manual);
_stateManager.ResetState(_state!, StateSource.Manual);
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton("Reapply State", Vector2.Zero, "Try to reapply the configured state if something went wrong.",
@ -395,8 +394,8 @@ public class ActorPanel(
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
StateChanged.Source.Manual);
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
ApplySettings.Manual);
}
private void DrawApplyToTarget()
@ -413,7 +412,7 @@ public class ActorPanel(
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
StateChanged.Source.Manual);
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
ApplySettings.Manual);
}
}

View file

@ -264,7 +264,7 @@ public class SetPanel(
var size = new Vector2(ImGui.GetFrameHeight());
size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _, _, _, _) = design.ApplyWhat();
var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat();
var sb = new StringBuilder();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{
@ -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();
@ -384,8 +384,8 @@ public class SetPanel(
{
void Box(int idx)
{
var (type, description) = Types[idx];
var value = design.ApplicationType.HasFlag(type);
var (type, description) = ApplicationTypeExtensions.Types[idx];
var value = design.Type.HasFlag(type);
if (ImGui.Checkbox($"##{(byte)type}", ref value))
newType = value ? newType | type : newType & ~type;
ImGuiUtil.HoverTooltip(description);
@ -428,40 +428,20 @@ public class SetPanel(
_manager.ChangeIdentifier(setIndex, _identifierDrawer.MannequinIdentifier);
}
private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[]
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), log)
{
(AutoDesign.Type.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."),
};
private sealed class JobGroupCombo : FilterComboCache<JobGroup>
{
private readonly AutoDesignManager _manager;
private readonly JobService _jobs;
public JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
: base(() => jobs.JobGroups.Values.ToList(), log)
{
_manager = manager;
_jobs = jobs;
}
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
{
CurrentSelection = design.Jobs;
CurrentSelectionIdx = _jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
CurrentSelectionIdx = jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
if (Draw("##JobGroups", design.Jobs.Name,
"Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes.",
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeightWithSpacing())
&& CurrentSelectionIdx >= 0)
_manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
else if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right))
_manager.ChangeJobCondition(set, autoDesignIndex, _jobs.JobGroups[1]);
manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]);
}
protected override string ToString(JobGroup obj)

View file

@ -52,11 +52,11 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
ImGuiUtil.DrawTableColumn(state.Identifier.ToString());
ImGui.TableNextColumn();
if (ImGui.Button("Reset"))
stateManager.ResetState(state, StateChanged.Source.Manual);
stateManager.ResetState(state, StateSource.Manual);
ImGui.TableNextRow();
static void PrintRow<T>(string label, T actor, T model, StateChanged.Source source) where T : notnull
static void PrintRow<T>(string label, T actor, T model, StateSource source) where T : notnull
{
ImGuiUtil.DrawTableColumn(label);
ImGuiUtil.DrawTableColumn(actor.ToString()!);
@ -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.Sources[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.Sources[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.Sources[MetaIndex.HatState]);
ImGui.TableNextRow();
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
state[ActorState.MetaIndex.VisorState]);
state.Sources[MetaIndex.VisorState]);
ImGui.TableNextRow();
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
state[ActorState.MetaIndex.WeaponState]);
state.Sources[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.Sources[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.Sources[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.Sources[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.Sources[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.Sources[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

@ -25,9 +25,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
continue;
DrawDesign(design, _designFileSystem);
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw,
design.DoApplyHatVisible(),
design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected());
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.ApplyMeta,
design.WriteProtected());
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGuiUtil.TextWrapped(base64);
if (ImGui.IsItemClicked())
@ -85,18 +84,12 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
ImGuiUtil.DrawTableColumn(applyCrest ? "Apply" : "Keep");
}
ImGuiUtil.DrawTableColumn("Hat Visible");
ImGuiUtil.DrawTableColumn(design.DesignData.IsHatVisible().ToString());
ImGuiUtil.DrawTableColumn(design.DoApplyHatVisible() ? "Apply" : "Keep");
ImGui.TableNextRow();
ImGuiUtil.DrawTableColumn("Visor Toggled");
ImGuiUtil.DrawTableColumn(design.DesignData.IsVisorToggled().ToString());
ImGuiUtil.DrawTableColumn(design.DoApplyVisorToggle() ? "Apply" : "Keep");
ImGui.TableNextRow();
ImGuiUtil.DrawTableColumn("Weapon Visible");
ImGuiUtil.DrawTableColumn(design.DesignData.IsWeaponVisible().ToString());
ImGuiUtil.DrawTableColumn(design.DoApplyWeaponVisible() ? "Apply" : "Keep");
ImGui.TableNextRow();
foreach (var index in MetaExtensions.AllRelevant)
{
ImGuiUtil.DrawTableColumn(index.ToName());
ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString());
ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep");
}
ImGuiUtil.DrawTableColumn("Model ID");
ImGuiUtil.DrawTableColumn(design.DesignData.ModelId.ToString());
@ -111,9 +104,5 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep");
ImGui.TableNextRow();
}
ImGuiUtil.DrawTableColumn("Is Wet");
ImGuiUtil.DrawTableColumn(design.DesignData.IsWet().ToString());
ImGui.TableNextRow();
}
}

View file

@ -55,10 +55,8 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGa
try
{
_parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah,
out var av,
out var aw);
_restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp);
_parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var meta);
_restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, meta, wp);
_restoreBytes = Convert.FromBase64String(_restore);
}
catch (Exception ex)

View file

@ -2,7 +2,6 @@
using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.State;
@ -67,9 +66,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled))
{
foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true))
_state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual);
_state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual);
_state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual);
_state.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual);
_state.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual);
_state.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual);
}
ImGui.TableNextColumn();

View file

@ -128,7 +128,11 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
{
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false,
true))
{
_cloneDesign = null;
_clipboardText = null;
ImGui.OpenPopup("##NewDesign");
}
}
private void ImportDesignButton(Vector2 size)
@ -139,6 +143,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
try
{
_cloneDesign = null;
_clipboardText = ImGui.GetClipboardText();
ImGui.OpenPopup("##NewDesign");
}
@ -156,7 +161,8 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
return;
_cloneDesign = Selected!;
_clipboardText = null;
_cloneDesign = Selected!;
ImGui.OpenPopup("##NewDesign");
}

View file

@ -0,0 +1,217 @@
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Automation;
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.None;
private int _dragDropTargetIndex = -1;
private LinkOrder _dragDropTargetOrder = LinkOrder.None;
public void Draw()
{
using var header = ImRaii.CollapsingHeader("Design Links");
if (!header)
return;
DrawList();
}
private void MoveLink()
{
if (_dragDropTargetIndex < 0 || _dragDropIndex < 0)
return;
if (_dragDropOrder is LinkOrder.Self)
switch (_dragDropTargetOrder)
{
case LinkOrder.Before:
for (var i = _selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i)
_linkManager.MoveDesignLink(_selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After);
break;
case LinkOrder.After:
for (var i = 0; i <= _dragDropTargetIndex; ++i)
{
_linkManager.MoveDesignLink(_selector.Selected!, 0, LinkOrder.After, _selector.Selected!.Links.Before.Count,
LinkOrder.Before);
}
break;
}
else if (_dragDropTargetOrder is LinkOrder.Self)
_linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _selector.Selected!.Links.Before.Count,
LinkOrder.Before);
else
_linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
_dragDropIndex = -1;
_dragDropTargetIndex = -1;
_dragDropOrder = LinkOrder.None;
_dragDropTargetOrder = LinkOrder.None;
}
private void DrawList()
{
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter);
if (!table)
return;
ImGui.TableSetupColumn("Del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("Detail", ImGuiTableColumnFlags.WidthFixed,
6 * ImGui.GetFrameHeight() + 5 * ImGui.GetStyle().ItemInnerSpacing.X);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
DrawSubList(_selector.Selected!.Links.Before, LinkOrder.Before);
DrawSelf();
DrawSubList(_selector.Selected!.Links.After, LinkOrder.After);
DrawNew();
MoveLink();
}
private void DrawSelf()
{
using var id = ImRaii.PushId((int)LinkOrder.Self);
ImGui.TableNextColumn();
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.Selectable(_selector.IncognitoMode ? _selector.Selected!.Incognito : _selector.Selected!.Name.Text);
DrawDragDrop(_selector.Selected!, LinkOrder.Self, 0);
ImGui.TableNextColumn();
}
private void DrawSubList(IReadOnlyList<DesignLink> list, LinkOrder order)
{
using var id = ImRaii.PushId((int)order);
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);
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();
DrawApplicationBoxes(i, order, flags);
if (delete)
_linkManager.RemoveDesignLink(_selector.Selected!, i--, order);
}
}
private void DrawNew()
{
var buttonSize = new Vector2(ImGui.GetFrameHeight());
ImGui.TableNextColumn();
ImGui.TableNextColumn();
_combo.Draw(ImGui.GetContentRegionAvail().X);
ImGui.TableNextColumn();
string ttBefore, ttAfter;
bool canAddBefore, canAddAfter;
if (_combo.Design == null)
{
ttAfter = ttBefore = "Select a design first.";
canAddBefore = canAddAfter = false;
}
else
{
canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.Before, out var error);
ttBefore = canAddBefore
? $"Add a link at the top of the list to {_combo.Design.Name}."
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.After, out error);
ttAfter = canAddAfter
? $"Add a link at the bottom of the list to {_combo.Design.Name}."
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
}
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleUp.ToIconString(), buttonSize, ttBefore, !canAddBefore, true))
{
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.Before);
_linkManager.MoveDesignLink(_selector.Selected!, _selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
}
ImGui.SameLine();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleDown.ToIconString(), buttonSize, ttAfter, !canAddAfter, true))
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.After);
}
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;
}
private void DrawApplicationBoxes(int idx, LinkOrder order, ApplicationType current)
{
var newType = current;
var newTypeInt = (uint)newType;
using (ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale))
{
using var _ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All))
newType = (ApplicationType)newTypeInt;
}
ImGuiUtil.HoverTooltip("Toggle all application modes at once.");
ImGui.SameLine();
Box(0);
ImGui.SameLine();
Box(1);
ImGui.SameLine();
Box(2);
ImGui.SameLine();
Box(3);
ImGui.SameLine();
Box(4);
if (newType != current)
_linkManager.ChangeApplicationType(_selector.Selected!, idx, order, current);
return;
void Box(int i)
{
var (applicationType, description) = ApplicationTypeExtensions.Types[i];
var value = applicationType.HasFlag(applicationType);
if (ImGui.Checkbox($"##{(byte)applicationType}", ref value))
newType = value ? newType | applicationType : newType & ~applicationType;
ImGuiUtil.HoverTooltip(description);
}
}
}

View file

@ -4,7 +4,6 @@ using Dalamud.Interface.Internal.Notifications;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Automation;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
@ -31,7 +30,8 @@ public class DesignPanel(
DesignConverter _converter,
ImportService _importService,
MultiDesignPanel _multiDesignPanel,
CustomizeParameterDrawer _parameterDrawer)
CustomizeParameterDrawer _parameterDrawer,
DesignLinkDrawer _designLinkDrawer)
{
private readonly FileDialogManager _fileDialog = new();
@ -119,21 +119,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 +158,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 +166,7 @@ public class DesignPanel(
{
if (!_config.UseAdvancedParameters)
return;
using var h = ImRaii.CollapsingHeader("Advanced Customizations");
if (!h)
return;
@ -255,24 +256,24 @@ public class DesignPanel(
{
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot);
if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
_manager.ChangeApplyEquip(_selector.Selected!, slot, apply);
_manager.ChangeApplyItem(_selector.Selected!, slot, apply);
}
}
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();
@ -285,28 +286,25 @@ public class DesignPanel(
private void DrawMetaApplication()
{
using var id = ImRaii.PushId("Meta");
const uint all = 0x0Fu;
var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00)
| (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00)
| (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00)
| (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00);
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);
using var id = ImRaii.PushId("Meta");
const uint all = (uint)MetaExtensions.All;
var flags = (uint)_selector.Selected!.ApplyMeta;
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
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);
var labels = new[]
{
"Apply Hat Visibility",
"Apply Visor State",
"Apply Weapon Visibility",
"Apply Wetness",
};
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);
apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness();
if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply);
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels))
{
var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index);
if (ImGui.Checkbox(label, ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, index, apply);
}
}
private void DrawParameterApplication()
@ -370,6 +368,7 @@ public class DesignPanel(
_designDetails.Draw();
DrawApplicationRules();
_modAssociations.Draw();
_designLinkDrawer.Draw();
}
private void DrawButtonRow()
@ -439,7 +438,7 @@ public class DesignPanel(
{
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with { MergeLinks = true });
}
}
@ -458,7 +457,7 @@ public class DesignPanel(
{
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with {MergeLinks = true});
}
}

View file

@ -2,14 +2,12 @@
using Dalamud.Interface.Internal.Notifications;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using Lumina.Data.Parsing.Scd;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
@ -172,7 +170,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));
}
@ -202,7 +200,7 @@ public class NpcPanel(
{
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
_state.ApplyDesign(state, design, ApplySettings.Manual);
}
}
@ -219,9 +217,9 @@ public class NpcPanel(
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
_state.ApplyDesign(state, design, ApplySettings.Manual);
}
}

View file

@ -68,6 +68,9 @@ public class SettingsTab(
if (!ImGui.CollapsingHeader("Glamourer Behavior"))
return;
Checkbox("Always Apply Entire Weapon for Mainhand",
"When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.",
config.ChangeEntireItem, v => config.ChangeEntireItem = v);
Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender",
"Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.",
config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v);

View file

@ -1,121 +1,115 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.State;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;
public ref struct ToggleDrawData
public struct ToggleDrawData
{
private IDesignEditor _editor = null!;
private object _data = null!;
private StateIndex _index;
public bool Locked;
public bool DisplayApplication;
public bool CurrentValue;
public bool CurrentApply;
public Action<bool> SetValue = null!;
public Action<bool> SetApply = null!;
public string Label = string.Empty;
public string Tooltip = string.Empty;
public ToggleDrawData()
{ }
public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design)
public readonly void SetValue(bool value)
{
var (label, value, apply, setValue, setApply) = index switch
switch (_index.GetFlag())
{
ActorState.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(),
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
ActorState.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(),
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
_ => throw new Exception("Unsupported meta index."),
};
case MetaIndex index:
_editor.ChangeMetaState(_data, index, value, ApplySettings.Manual);
break;
case CrestFlag flag:
_editor.ChangeCrest(_data, flag, value, ApplySettings.Manual);
break;
}
}
return new ToggleDrawData
public readonly void SetApply(bool value)
{
var manager = (DesignManager)_editor;
var design = (Design)_data;
switch (_index.GetFlag())
{
Label = label,
case MetaIndex index:
manager.ChangeApplyMeta(design, index, value);
break;
case CrestFlag flag:
manager.ChangeApplyCrest(design, flag, value);
break;
}
}
public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design)
=> new()
{
_index = index,
_editor = manager,
_data = design,
Label = index.ToName(),
Tooltip = string.Empty,
Locked = design.WriteProtected(),
DisplayApplication = true,
CurrentValue = value,
CurrentApply = apply,
SetValue = setValue,
SetApply = setApply,
CurrentValue = design.DesignData.GetMeta(index),
CurrentApply = design.DoApplyMeta(index),
};
public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state)
=> new()
{
_index = index,
_editor = manager,
_data = state,
Label = index.ToName(),
Tooltip = index.ToTooltip(),
Locked = state.IsLocked,
CurrentValue = state.ModelData.GetMeta(index),
};
}
public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design)
=> new()
{
_index = slot,
_editor = manager,
_data = design,
Label = $"{slot.ToLabel()} Crest",
Tooltip = string.Empty,
Locked = design.WriteProtected(),
DisplayApplication = true,
CurrentValue = design.DesignData.Crest(slot),
CurrentApply = design.DoApplyCrest(slot),
SetValue = v => manager.ChangeCrest(design, slot, v),
SetApply = v => manager.ChangeApplyCrest(design, slot, v),
};
public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state)
=> new()
{
_index = slot,
_editor = manager,
_data = state,
Label = $"{slot.ToLabel()} Crest",
Tooltip = "Hide or show your free company crest on this piece of gear.",
Locked = state.IsLocked,
CurrentValue = state.ModelData.Crest(slot),
SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual),
};
public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state)
{
var (label, tooltip, value, setValue) = index switch
public static ToggleDrawData FromValue(MetaIndex index, bool value)
=> new()
{
ActorState.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.",
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.",
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(),
b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)),
_ => throw new Exception("Unsupported meta index."),
};
return new ToggleDrawData
{
Label = label,
Tooltip = tooltip,
Locked = state.IsLocked,
CurrentValue = value,
SetValue = setValue,
};
}
public static ToggleDrawData FromValue(ActorState.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."),
_ => throw new Exception("Unsupported meta index."),
};
return new ToggleDrawData
{
Label = label,
Tooltip = tooltip,
_index = index,
Label = index.ToName(),
Tooltip = index.ToTooltip(),
Locked = true,
CurrentValue = value,
};
}
}

View file

@ -14,24 +14,17 @@ public sealed class CharaFile
public CustomizeFlag ApplyCustomize;
public EquipFlag ApplyEquip;
public static CharaFile? ParseData(ItemManager items, string data, string? name = null)
public static CharaFile ParseData(ItemManager items, string data, string? name = null)
{
try
{
var jObj = JObject.Parse(data);
SanityCheck(jObj);
var ret = new CharaFile();
ret.Data.SetDefaultEquipment(items);
ret.Data.ModelId = ParseModelId(jObj);
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
return ret;
}
catch
{
return null;
}
var jObj = JObject.Parse(data);
SanityCheck(jObj);
var ret = new CharaFile();
ret.Data.SetDefaultEquipment(items);
ret.Data.ModelId = ParseModelId(jObj);
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
return ret;
}
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

@ -3,7 +3,7 @@ using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin;
using Dalamud.Plugin.Services;
using Glamourer.Events;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.State;
using Penumbra.GameData.Enums;
@ -118,14 +118,14 @@ public class ContextMenuService : IDisposable
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.PrimaryId.Id is > 1600 and < 1651
&& _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual);
if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual);
}
};
}
@ -142,14 +142,14 @@ public class ContextMenuService : IDisposable
return;
var slot = item.Type.ToSlot();
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual);
if (item.Type.ValidOffhand().IsOffhandType())
{
if (item.PrimaryId.Id is > 1600 and < 1651
&& _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual);
if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual);
}
};
}

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

@ -1,7 +1,6 @@
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.State;
using Newtonsoft.Json.Linq;
using OtterGui.Services;
@ -41,9 +40,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)
@ -333,7 +333,7 @@ public class CommandService : IDisposable
foreach (var identifier in identifiers)
{
if (_stateManager.TryGetValue(identifier, out var state))
_stateManager.ResetState(state, StateChanged.Source.Manual);
_stateManager.ResetState(state, StateSource.Manual);
}
@ -419,7 +419,7 @@ public class CommandService : IDisposable
if (!_objects.TryGetValue(identifier, out var actors))
{
if (_stateManager.TryGetValue(identifier, out var state))
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
_stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true });
}
else
{
@ -428,7 +428,7 @@ public class CommandService : IDisposable
if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state))
{
ApplyModSettings(design, actor, applyMods);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
_stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true });
}
}
}
@ -587,7 +587,7 @@ public class CommandService : IDisposable
if (Guid.TryParse(argument, out var guid))
{
design = _designManager.Designs.FirstOrDefault(d => d.Identifier == guid);
design = _designManager.Designs.ByIdentifier(guid);
}
else
{

View file

@ -1,6 +0,0 @@
namespace Glamourer.Services
{
internal interface IGamePathParser
{
}
}

View file

@ -121,7 +121,7 @@ public static class ServiceManagerA
private static ServiceManager AddState(this ServiceManager services)
=> services.AddSingleton<StateManager>()
.AddSingleton<StateApplier>()
.AddSingleton<StateEditor>()
.AddSingleton<InternalStateEditor>()
.AddSingleton<StateListener>()
.AddSingleton<FunModule>();

View file

@ -1,5 +1,4 @@
using Glamourer.Designs;
using Glamourer.Events;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Dalamud.Game.ClientState.Conditions;
@ -11,15 +10,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 +67,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 StateSources Sources = 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 => Sources[f] is not StateSource.Game)
.Aggregate((CustomizeParameterFlag)0, (a, b) => a | b);
public bool UpdateTerritory(ushort territory)
{
@ -127,4 +84,4 @@ public class ActorState
LastTerritory = territory;
return true;
}
}
}

View file

@ -116,8 +116,6 @@ public unsafe class FunModule : IDisposable
SetRandomItem(slot, ref armor);
break;
case CodeService.CodeFlag.Elephants:
SetElephant(slot, ref armor);
break;
case CodeService.CodeFlag.World when actor.Index != 0:
KeepOldArmor(actor, slot, ref armor);
break;
@ -166,8 +164,9 @@ public unsafe class FunModule : IDisposable
SetRandomItem(slot, ref armor[idx]);
break;
case CodeService.CodeFlag.Elephants:
SetElephant(EquipSlot.Body, ref armor[1]);
SetElephant(EquipSlot.Head, ref armor[0]);
var stainId = ElephantStains[_rng.Next(0, ElephantStains.Length)];
SetElephant(EquipSlot.Body, ref armor[1], stainId);
SetElephant(EquipSlot.Head, ref armor[0], stainId);
break;
case CodeService.CodeFlag.World when actor.Index != 0:
_worldSets.Apply(actor, _rng, armor);
@ -216,12 +215,24 @@ public unsafe class FunModule : IDisposable
armor.Variant = item.Variant;
}
private static void SetElephant(EquipSlot slot, ref CharacterArmor armor)
private static ReadOnlySpan<byte> ElephantStains
=>
[
87, 87, 87, 87, 87, // Cherry Pink
83, 83, 83, // Colibri Pink
80, // Iris Purple
85, // Regal Purple
103, // Pastel Pink
82, 82, 82, // Lotus Pink
7, // Rose Pink
];
private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId)
{
armor = slot switch
{
EquipSlot.Body => new CharacterArmor(6133, 1, 87),
EquipSlot.Head => new CharacterArmor(6133, 1, 87),
EquipSlot.Body => new CharacterArmor(6133, 1, stainId),
EquipSlot.Head => new CharacterArmor(6133, 1, stainId),
_ => armor,
};
}

View file

@ -0,0 +1,234 @@
using Dalamud.Plugin.Services;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class InternalStateEditor(
CustomizeService customizations,
HumanModelList humans,
ItemManager items,
GPoseService gPose,
ICondition condition)
{
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source,
out uint oldModelId, uint key = 0)
{
oldModelId = state.ModelData.ModelId;
// TODO think about this.
if (modelId != 0)
return false;
if (!state.CanUnlock(key))
return false;
var oldIsHuman = state.ModelData.IsHuman;
state.ModelData.IsHuman = humans.IsHuman(modelId);
if (state.ModelData.IsHuman)
{
if (oldModelId == modelId)
return true;
state.ModelData.ModelId = modelId;
if (oldIsHuman)
return true;
if (!state.AllowsRedraw(condition))
return false;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = CustomizeArray.Default;
state.ModelData.SetDefaultEquipment(items);
state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true);
state.ModelData.SetVisor(false);
state.Sources[MetaIndex.ModelId] = source;
state.Sources[MetaIndex.HatState] = source;
state.Sources[MetaIndex.WeaponState] = source;
state.Sources[MetaIndex.VisorState] = source;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state.Sources[slot, true] = source;
state.Sources[slot, false] = source;
}
state.Sources[CustomizeIndex.Clan] = source;
state.Sources[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.Sources[index] = source;
}
else
{
if (!state.AllowsRedraw(condition))
return false;
state.ModelData.LoadNonHuman(modelId, customize, equipData);
state.Sources[MetaIndex.ModelId] = source;
}
return true;
}
/// <summary> Change a customization value. </summary>
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source,
out CustomizeValue old, uint key = 0)
{
old = state.ModelData.Customize[idx];
if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state.Sources[idx] = source;
return true;
}
/// <summary> Change an entire customization array according to functions. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich,
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
old = state.ModelData.Customize;
changed = 0;
if (!state.CanUnlock(key))
return false;
(var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
if (changed == 0)
return false;
state.ModelData.Customize = customize;
applied |= changed;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
if (applied.HasFlag(type.ToFlag()))
state.Sources[type] = source(type);
}
return true;
}
/// <summary> Change an entire customization array according to functions. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, Func<CustomizeIndex, bool> applyWhich,
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
var apply = Enum.GetValues<CustomizeIndex>().Where(applyWhich).Aggregate((CustomizeFlag)0, (current, type) => current | type.ToFlag());
return ChangeHumanCustomize(state, customizeInput, apply, source, out old, out changed, key);
}
/// <summary> Change a single piece of equipment without stain. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state.Sources[slot, false], out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.Sources[slot, false] = source;
return true;
}
/// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem,
out StainId oldStain, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!gPose.InGPose)
return false;
var old = oldItem;
var oldS = oldStain;
gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state.Sources[slot, false] = source;
state.Sources[slot, true] = source;
return true;
}
/// <summary> Change only the stain of an equipment piece. </summary>
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0)
{
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state.Sources[slot, true] = source;
return true;
}
/// <summary> Change the crest of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0)
{
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetCrest(slot, crest);
state.Sources[slot] = source;
return true;
}
/// <summary> Change the customize flags of a character. </summary>
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source,
out CustomizeParameterValue oldValue, uint key = 0)
{
oldValue = state.ModelData.Parameters[flag];
if (!state.CanUnlock(key))
return false;
state.ModelData.Parameters.Set(flag, value);
state.Sources[flag] = source;
return true;
}
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
uint key = 0)
{
oldValue = state.ModelData.GetMeta(index);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetMeta(index, value);
state.Sources[index] = source;
return true;
}
}

View file

@ -0,0 +1,30 @@
using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
public sealed class JobChangeState : Dictionary<FullEquipType, (EquipItem, StateSource)>, IService
{
public ActorState? State { get; private set; }
public void Reset()
{
State = null;
Clear();
}
public bool HasState
=> State != null;
public ActorIdentifier Identifier
=> State?.Identifier ?? ActorIdentifier.Invalid;
public void Set(ActorState state, IEnumerable<(EquipItem, StateSource)> items)
{
foreach (var (item, source) in items.Where(p => p.Item1.Valid))
TryAdd(item.Type, (item, source));
State = state;
}
}

View file

@ -1,4 +1,4 @@
using Glamourer.Events;
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
@ -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.Sources[slot, false] is not StateSource.Ipc,
state.ModelData.IsHatVisible());
return data;
@ -200,67 +200,44 @@ public class StateApplier(
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
}
/// <summary> Change the visor state of actors only on the draw object. </summary>
public void ChangeVisor(ActorData data, bool value)
/// <summary> Change a meta state. </summary>
public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value)
{
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_visor.SetVisorState(actor.Model, value);
switch (index)
{
case MetaIndex.Wetness:
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value;
return;
}
case MetaIndex.HatState:
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetHatState(actor, value);
return;
}
case MetaIndex.WeaponState:
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetWeaponState(actor, value);
return;
}
case MetaIndex.VisorState:
{
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
_visor.SetVisorState(actor.Model, value);
return;
}
}
}
/// <inheritdoc cref="ChangeVisor(ActorData, bool)"/>
public ActorData ChangeVisor(ActorState state, bool apply)
/// <inheritdoc cref="ChangeMetaState(ActorData, MetaIndex, bool)"/>
public ActorData ChangeMetaState(ActorState state, MetaIndex index, bool apply)
{
var data = GetData(state);
if (apply)
ChangeVisor(data, state.ModelData.IsVisorToggled());
return data;
}
/// <summary> Change the forced wetness state on actors. </summary>
public unsafe void ChangeWetness(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
actor.AsCharacter->IsGPoseWet = value;
}
/// <inheritdoc cref="ChangeWetness(ActorData, bool)"/>
public ActorData ChangeWetness(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeWetness(data, state.ModelData.IsWet());
return data;
}
/// <summary> Change the hat-visibility state on actors. </summary>
public void ChangeHatState(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetHatState(actor, value);
}
/// <inheritdoc cref="ChangeHatState(ActorData, bool)"/>
public ActorData ChangeHatState(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeHatState(data, state.ModelData.IsHatVisible());
return data;
}
/// <summary> Change the weapon-visibility state on actors. </summary>
public void ChangeWeaponState(ActorData data, bool value)
{
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
_metaService.SetWeaponState(actor, value);
}
/// <inheritdoc cref="ChangeWeaponState(ActorData, bool)"/>
public ActorData ChangeWeaponState(ActorState state, bool apply)
{
var data = GetData(state);
if (apply)
ChangeWeaponState(data, state.ModelData.IsWeaponVisible());
ChangeMetaState(data, index, state.ModelData.GetMeta(index));
return data;
}
@ -299,6 +276,47 @@ public class StateApplier(
return data;
}
/// <summary> Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. </summary>
/// <param name="state"> The state to apply. </param>
/// <param name="redraw"> Whether a redraw should be forced. </param>
/// <param name="withLock"> Whether a temporary lock should be applied for the redraw. </param>
/// <returns> The actor data for the actors who got changed. </returns>
public ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
{
var actors = ChangeMetaState(state, MetaIndex.Wetness, true);
if (redraw)
{
if (withLock)
state.TempLock();
ForceRedraw(actors);
}
else
{
ChangeCustomize(actors, state.ModelData.Customize);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
state.ModelData.IsHatVisible());
}
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
}
if (state.ModelData.IsHuman)
{
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
ChangeCrests(actors, state.ModelData.CrestVisibility);
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
}
return actors;
}
private ActorData GetData(ActorState state)
{
_objects.Update();

View file

@ -1,246 +1,326 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Common.Math;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Vector3 = FFXIVClientStructs.FFXIV.Common.Math.Vector3;
namespace Glamourer.State;
public class StateEditor
public class StateEditor(
InternalStateEditor editor,
StateApplier applier,
StateChanged stateChanged,
JobChangeState jobChange,
Configuration config,
ItemManager items,
DesignMerger merger) : IDesignEditor
{
private readonly ItemManager _items;
private readonly CustomizeService _customizations;
private readonly HumanModelList _humans;
private readonly GPoseService _gPose;
private readonly ICondition _condition;
protected readonly InternalStateEditor Editor = editor;
protected readonly StateApplier Applier = applier;
protected readonly StateChanged StateChanged = stateChanged;
protected readonly Configuration Config = config;
protected readonly ItemManager Items = items;
public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition)
{
_customizations = customizations;
_humans = humans;
_items = items;
_gPose = gPose;
_condition = condition;
}
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source,
out uint oldModelId, uint key = 0)
{
oldModelId = state.ModelData.ModelId;
// TODO think about this.
if (modelId != 0)
return false;
if (!state.CanUnlock(key))
return false;
var oldIsHuman = state.ModelData.IsHuman;
state.ModelData.IsHuman = _humans.IsHuman(modelId);
if (state.ModelData.IsHuman)
{
if (oldModelId == modelId)
return true;
state.ModelData.ModelId = modelId;
if (oldIsHuman)
return true;
if (!state.AllowsRedraw(_condition))
return false;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = CustomizeArray.Default;
state.ModelData.SetDefaultEquipment(_items);
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;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state[slot, true] = source;
state[slot, false] = source;
}
state[CustomizeIndex.Clan] = source;
state[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;
}
else
{
if (!state.AllowsRedraw(_condition))
return false;
state.ModelData.LoadNonHuman(modelId, customize, equipData);
state[ActorState.MetaIndex.ModelId] = source;
}
return true;
}
/// <summary> Change a customization value. </summary>
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
out CustomizeValue old, uint key = 0)
{
old = state.ModelData.Customize[idx];
if (!state.CanUnlock(key))
return false;
state.ModelData.Customize[idx] = value;
state[idx] = source;
return true;
}
/// <summary> Change an entire customization array according to flags. </summary>
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
old = state.ModelData.Customize;
changed = 0;
if (!state.CanUnlock(key))
return false;
(var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
if (changed == 0)
return false;
state.ModelData.Customize = customize;
applied |= changed;
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
if (applied.HasFlag(type.ToFlag()))
state[type] = source;
}
return true;
}
/// <summary> Change a single piece of equipment without stain. </summary>
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!_gPose.InGPose)
return false;
var old = oldItem;
_gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeItem(state, slot, old, state[slot, false], out _, key);
});
}
state.ModelData.SetItem(slot, item);
state[slot, false] = source;
return true;
}
/// <summary> Change a single piece of equipment including stain. </summary>
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, out EquipItem oldItem,
out StainId oldStain, uint key = 0)
{
oldItem = state.ModelData.Item(slot);
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
// Can not change weapon type from expected type in state.
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
{
if (!_gPose.InGPose)
return false;
var old = oldItem;
var oldS = oldStain;
_gPose.AddActionOnLeave(() =>
{
if (old.Type == state.BaseData.Item(slot).Type)
ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key);
});
}
state.ModelData.SetItem(slot, item);
state.ModelData.SetStain(slot, stain);
state[slot, false] = source;
state[slot, true] = source;
return true;
}
/// <summary> Change only the stain of an equipment piece. </summary>
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, out StainId oldStain, uint key = 0)
{
oldStain = state.ModelData.Stain(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetStain(slot, stain);
state[slot, true] = source;
return true;
}
/// <summary> Change the crest of an equipment piece. </summary>
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0)
{
oldCrest = state.ModelData.Crest(slot);
if (!state.CanUnlock(key))
return false;
state.ModelData.SetCrest(slot, crest);
state[slot] = source;
return true;
}
/// <summary> Change the customize flags of a character. </summary>
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source,
out CustomizeParameterValue oldValue, uint key = 0)
{
oldValue = state.ModelData.Parameters[flag];
if (!state.CanUnlock(key))
return false;
state.ModelData.Parameters.Set(flag, value);
state[flag] = source;
return true;
}
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source,
uint key = 0)
{
(var setter, oldValue) = index switch
if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
return;
var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
}
/// <inheritdoc/>
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx));
}
/// <inheritdoc/>
public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key))
return;
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied));
}
/// <inheritdoc/>
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
{
var state = (ActorState)data;
if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, settings);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot));
}
/// <inheritdoc/>
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings)
{
switch (item.HasValue, stain.HasValue)
{
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)),
state.ModelData.IsWeaponVisible()),
_ => throw new Exception("Invalid MetaIndex."),
};
case (false, false): return;
case (true, false):
ChangeItem(data, slot, item!.Value, settings);
return;
case (false, true):
ChangeStain(data, slot, stain!.Value, settings);
return;
}
if (!state.CanUnlock(key))
return false;
var state = (ActorState)data;
if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source,
out var old, out var oldStain, settings.Key))
return;
setter(value);
state[index] = source;
return true;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
if (slot is EquipSlot.MainHand)
ApplyMainhandPeriphery(state, item, settings);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot));
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot));
}
/// <inheritdoc/>
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot));
}
/// <inheritdoc/>
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot));
}
/// <inheritdoc/>
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings)
{
if (data is not ActorState state)
return;
// Also apply main color to highlights when highlights is off.
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings);
if (!Editor.ChangeParameter(state, flag, value, settings.Source, out var old, settings.Key))
return;
var @new = state.ModelData.Parameters[flag];
var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
}
/// <inheritdoc/>
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key))
return;
var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc);
Glamourer.Log.Verbose(
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState));
}
/// <inheritdoc/>
public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings)
{
var state = (ActorState)data;
if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize,
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
return;
var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman;
if (state.ModelData.IsHuman)
{
foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest))
{
if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual)
Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot),
out _, settings.Key);
}
var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw;
if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan))
customizeFlags |= CustomizeFlag.Race;
Func<CustomizeIndex, bool> applyWhich = settings.RespectManual
? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual
: i => customizeFlags.HasFlag(i.ToFlag());
if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed,
settings.Key))
requiresRedraw |= changed.RequiresRedraw();
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate())
{
if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending)
continue;
var source = Source(parameter);
if (source is StateSource.Manual)
source = StateSource.Pending;
Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key);
}
// Do not apply highlights from a design if highlights is unchecked.
if (!state.ModelData.Customize.Highlights)
Editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state.Sources[CustomizeParameterFlag.HairDiffuse], out _, settings.Key);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
if (mergedDesign.Design.DoApplyEquip(slot))
if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual)
Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot),
Source(slot.ToState()), out _, settings.Key);
if (mergedDesign.Design.DoApplyStain(slot))
if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual)
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
Source(slot.ToState(true)), out _, settings.Key);
}
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
{
if (mergedDesign.Design.DoApplyStain(weaponSlot))
if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual)
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
Source(weaponSlot.ToState(true)), out _, settings.Key);
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
continue;
if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual)
continue;
var currentType = state.ModelData.Item(weaponSlot).Type;
if (!settings.FromJobChange && mergedDesign.Weapons.TryGetValue(currentType, out var weapon))
{
var source = settings.UseSingleSource ? settings.Source :
weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2;
Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _,
settings.Key);
}
}
if (settings.FromJobChange)
jobChange.Set(state, mergedDesign.Weapons.Values.Select(m =>
(m.Item1, settings.UseSingleSource ? settings.Source :
m.Item2 is StateSource.Game ? StateSource.Game : m.Item2)));
foreach (var meta in MetaExtensions.AllRelevant)
{
if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual)
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
}
}
var actors = settings.Source is StateSource.Manual or StateSource.Ipc
? Applier.ApplyAll(state, requiresRedraw, false)
: ActorData.Invalid;
Glamourer.Log.Verbose(
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design);
return;
StateSource Source(StateIndex index)
{
if (settings.UseSingleSource)
return settings.Source;
var source = mergedDesign.Sources[index];
return source is StateSource.Game ? StateSource.Game : settings.Source;
}
}
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
{
var merged = settings.MergeLinks && design is Design d
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, false)
: new MergedDesign(design);
ApplyDesign(data, merged, settings with
{
FromJobChange = false,
RespectManual = false,
UseSingleSource = true,
});
}
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings)
{
if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual)
return;
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
if (offhand.Valid)
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), settings);
if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets))
ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
state.ModelData.Stain(EquipSlot.Hands), settings);
}
}

View file

@ -0,0 +1,456 @@
using Glamourer.Designs;
using Glamourer.GameData;
using Penumbra.GameData.Enums;
namespace Glamourer.State;
public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIndex, StateIndex, bool>
{
public static readonly StateIndex Invalid = new(-1);
public static implicit operator StateIndex(EquipFlag flag)
=> flag switch
{
EquipFlag.Head => new StateIndex(EquipHead),
EquipFlag.Body => new StateIndex(EquipBody),
EquipFlag.Hands => new StateIndex(EquipHands),
EquipFlag.Legs => new StateIndex(EquipLegs),
EquipFlag.Feet => new StateIndex(EquipFeet),
EquipFlag.Ears => new StateIndex(EquipEars),
EquipFlag.Neck => new StateIndex(EquipNeck),
EquipFlag.Wrist => new StateIndex(EquipWrist),
EquipFlag.RFinger => new StateIndex(EquipRFinger),
EquipFlag.LFinger => new StateIndex(EquipLFinger),
EquipFlag.Mainhand => new StateIndex(EquipMainhand),
EquipFlag.Offhand => new StateIndex(EquipOffhand),
EquipFlag.HeadStain => new StateIndex(StainHead),
EquipFlag.BodyStain => new StateIndex(StainBody),
EquipFlag.HandsStain => new StateIndex(StainHands),
EquipFlag.LegsStain => new StateIndex(StainLegs),
EquipFlag.FeetStain => new StateIndex(StainFeet),
EquipFlag.EarsStain => new StateIndex(StainEars),
EquipFlag.NeckStain => new StateIndex(StainNeck),
EquipFlag.WristStain => new StateIndex(StainWrist),
EquipFlag.RFingerStain => new StateIndex(StainRFinger),
EquipFlag.LFingerStain => new StateIndex(StainLFinger),
EquipFlag.MainhandStain => new StateIndex(StainMainhand),
EquipFlag.OffhandStain => new StateIndex(StainOffhand),
_ => Invalid,
};
public static implicit operator StateIndex(CustomizeIndex index)
=> index switch
{
CustomizeIndex.Race => new StateIndex(CustomizeRace),
CustomizeIndex.Gender => new StateIndex(CustomizeGender),
CustomizeIndex.BodyType => new StateIndex(CustomizeBodyType),
CustomizeIndex.Height => new StateIndex(CustomizeHeight),
CustomizeIndex.Clan => new StateIndex(CustomizeClan),
CustomizeIndex.Face => new StateIndex(CustomizeFace),
CustomizeIndex.Hairstyle => new StateIndex(CustomizeHairstyle),
CustomizeIndex.Highlights => new StateIndex(CustomizeHighlights),
CustomizeIndex.SkinColor => new StateIndex(CustomizeSkinColor),
CustomizeIndex.EyeColorRight => new StateIndex(CustomizeEyeColorRight),
CustomizeIndex.HairColor => new StateIndex(CustomizeHairColor),
CustomizeIndex.HighlightsColor => new StateIndex(CustomizeHighlightsColor),
CustomizeIndex.FacialFeature1 => new StateIndex(CustomizeFacialFeature1),
CustomizeIndex.FacialFeature2 => new StateIndex(CustomizeFacialFeature2),
CustomizeIndex.FacialFeature3 => new StateIndex(CustomizeFacialFeature3),
CustomizeIndex.FacialFeature4 => new StateIndex(CustomizeFacialFeature4),
CustomizeIndex.FacialFeature5 => new StateIndex(CustomizeFacialFeature5),
CustomizeIndex.FacialFeature6 => new StateIndex(CustomizeFacialFeature6),
CustomizeIndex.FacialFeature7 => new StateIndex(CustomizeFacialFeature7),
CustomizeIndex.LegacyTattoo => new StateIndex(CustomizeLegacyTattoo),
CustomizeIndex.TattooColor => new StateIndex(CustomizeTattooColor),
CustomizeIndex.Eyebrows => new StateIndex(CustomizeEyebrows),
CustomizeIndex.EyeColorLeft => new StateIndex(CustomizeEyeColorLeft),
CustomizeIndex.EyeShape => new StateIndex(CustomizeEyeShape),
CustomizeIndex.SmallIris => new StateIndex(CustomizeSmallIris),
CustomizeIndex.Nose => new StateIndex(CustomizeNose),
CustomizeIndex.Jaw => new StateIndex(CustomizeJaw),
CustomizeIndex.Mouth => new StateIndex(CustomizeMouth),
CustomizeIndex.Lipstick => new StateIndex(CustomizeLipstick),
CustomizeIndex.LipColor => new StateIndex(CustomizeLipColor),
CustomizeIndex.MuscleMass => new StateIndex(CustomizeMuscleMass),
CustomizeIndex.TailShape => new StateIndex(CustomizeTailShape),
CustomizeIndex.BustSize => new StateIndex(CustomizeBustSize),
CustomizeIndex.FacePaint => new StateIndex(CustomizeFacePaint),
CustomizeIndex.FacePaintReversed => new StateIndex(CustomizeFacePaintReversed),
CustomizeIndex.FacePaintColor => new StateIndex(CustomizeFacePaintColor),
_ => Invalid,
};
public static implicit operator StateIndex(MetaIndex meta)
=> new((int)meta);
public static implicit operator StateIndex(CrestFlag crest)
=> crest switch
{
CrestFlag.OffHand => new StateIndex(CrestOffhand),
CrestFlag.Head => new StateIndex(CrestHead),
CrestFlag.Body => new StateIndex(CrestBody),
_ => Invalid,
};
public static implicit operator StateIndex(CustomizeParameterFlag param)
=> param switch
{
CustomizeParameterFlag.SkinDiffuse => new StateIndex(ParamSkinDiffuse),
CustomizeParameterFlag.MuscleTone => new StateIndex(ParamMuscleTone),
CustomizeParameterFlag.SkinSpecular => new StateIndex(ParamSkinSpecular),
CustomizeParameterFlag.LipDiffuse => new StateIndex(ParamLipDiffuse),
CustomizeParameterFlag.HairDiffuse => new StateIndex(ParamHairDiffuse),
CustomizeParameterFlag.HairSpecular => new StateIndex(ParamHairSpecular),
CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight),
CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye),
CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye),
CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor),
CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier),
CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset),
CustomizeParameterFlag.DecalColor => new StateIndex(ParamDecalColor),
_ => Invalid,
};
public const int EquipHead = 0;
public const int EquipBody = EquipHead + 1;
public const int EquipHands = EquipBody + 1;
public const int EquipLegs = EquipHands + 1;
public const int EquipFeet = EquipLegs + 1;
public const int EquipEars = EquipFeet + 1;
public const int EquipNeck = EquipEars + 1;
public const int EquipWrist = EquipNeck + 1;
public const int EquipRFinger = EquipWrist + 1;
public const int EquipLFinger = EquipRFinger + 1;
public const int EquipMainhand = EquipLFinger + 1;
public const int EquipOffhand = EquipMainhand + 1;
public const int StainHead = EquipOffhand + 1;
public const int StainBody = StainHead + 1;
public const int StainHands = StainBody + 1;
public const int StainLegs = StainHands + 1;
public const int StainFeet = StainLegs + 1;
public const int StainEars = StainFeet + 1;
public const int StainNeck = StainEars + 1;
public const int StainWrist = StainNeck + 1;
public const int StainRFinger = StainWrist + 1;
public const int StainLFinger = StainRFinger + 1;
public const int StainMainhand = StainLFinger + 1;
public const int StainOffhand = StainMainhand + 1;
public const int CustomizeRace = StainOffhand + 1;
public const int CustomizeGender = CustomizeRace + 1;
public const int CustomizeBodyType = CustomizeGender + 1;
public const int CustomizeHeight = CustomizeBodyType + 1;
public const int CustomizeClan = CustomizeHeight + 1;
public const int CustomizeFace = CustomizeClan + 1;
public const int CustomizeHairstyle = CustomizeFace + 1;
public const int CustomizeHighlights = CustomizeHairstyle + 1;
public const int CustomizeSkinColor = CustomizeHighlights + 1;
public const int CustomizeEyeColorRight = CustomizeSkinColor + 1;
public const int CustomizeHairColor = CustomizeEyeColorRight + 1;
public const int CustomizeHighlightsColor = CustomizeHairColor + 1;
public const int CustomizeFacialFeature1 = CustomizeHighlightsColor + 1;
public const int CustomizeFacialFeature2 = CustomizeFacialFeature1 + 1;
public const int CustomizeFacialFeature3 = CustomizeFacialFeature2 + 1;
public const int CustomizeFacialFeature4 = CustomizeFacialFeature3 + 1;
public const int CustomizeFacialFeature5 = CustomizeFacialFeature4 + 1;
public const int CustomizeFacialFeature6 = CustomizeFacialFeature5 + 1;
public const int CustomizeFacialFeature7 = CustomizeFacialFeature6 + 1;
public const int CustomizeLegacyTattoo = CustomizeFacialFeature7 + 1;
public const int CustomizeTattooColor = CustomizeLegacyTattoo + 1;
public const int CustomizeEyebrows = CustomizeTattooColor + 1;
public const int CustomizeEyeColorLeft = CustomizeEyebrows + 1;
public const int CustomizeEyeShape = CustomizeEyeColorLeft + 1;
public const int CustomizeSmallIris = CustomizeEyeShape + 1;
public const int CustomizeNose = CustomizeSmallIris + 1;
public const int CustomizeJaw = CustomizeNose + 1;
public const int CustomizeMouth = CustomizeJaw + 1;
public const int CustomizeLipstick = CustomizeMouth + 1;
public const int CustomizeLipColor = CustomizeLipstick + 1;
public const int CustomizeMuscleMass = CustomizeLipColor + 1;
public const int CustomizeTailShape = CustomizeMuscleMass + 1;
public const int CustomizeBustSize = CustomizeTailShape + 1;
public const int CustomizeFacePaint = CustomizeBustSize + 1;
public const int CustomizeFacePaintReversed = CustomizeFacePaint + 1;
public const int CustomizeFacePaintColor = CustomizeFacePaintReversed + 1;
public const int MetaWetness = CustomizeFacePaintColor + 1;
public const int MetaHatState = MetaWetness + 1;
public const int MetaVisorState = MetaHatState + 1;
public const int MetaWeaponState = MetaVisorState + 1;
public const int MetaModelId = MetaWeaponState + 1;
public const int CrestHead = MetaModelId + 1;
public const int CrestBody = CrestHead + 1;
public const int CrestOffhand = CrestBody + 1;
public const int ParamSkinDiffuse = CrestOffhand + 1;
public const int ParamMuscleTone = ParamSkinDiffuse + 1;
public const int ParamSkinSpecular = ParamMuscleTone + 1;
public const int ParamLipDiffuse = ParamSkinSpecular + 1;
public const int ParamHairDiffuse = ParamLipDiffuse + 1;
public const int ParamHairSpecular = ParamHairDiffuse + 1;
public const int ParamHairHighlight = ParamHairSpecular + 1;
public const int ParamLeftEye = ParamHairHighlight + 1;
public const int ParamRightEye = ParamLeftEye + 1;
public const int ParamFeatureColor = ParamRightEye + 1;
public const int ParamFacePaintUvMultiplier = ParamFeatureColor + 1;
public const int ParamFacePaintUvOffset = ParamFacePaintUvMultiplier + 1;
public const int ParamDecalColor = ParamFacePaintUvOffset + 1;
public const int Size = ParamDecalColor + 1;
public IEnumerable<StateIndex> All
=> Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i));
public bool GetApply(DesignBase data)
=> GetFlag() switch
{
EquipFlag e => data.ApplyEquip.HasFlag(e),
CustomizeFlag c => data.ApplyCustomize.HasFlag(c),
MetaFlag m => data.ApplyMeta.HasFlag(m),
CrestFlag c => data.ApplyCrest.HasFlag(c),
CustomizeParameterFlag c => data.ApplyParameters.HasFlag(c),
bool v => v,
_ => false,
};
public string ToName()
=> GetFlag() switch
{
EquipFlag e => GetName(e),
CustomizeFlag c => c.ToIndex().ToDefaultName(),
MetaFlag m => m.ToIndex().ToName(),
CrestFlag c => c.ToLabel(),
CustomizeParameterFlag c => c.ToName(),
bool v => "Model ID",
_ => "Unknown",
};
public object GetFlag()
=> Value switch
{
EquipHead => EquipFlag.Head,
EquipBody => EquipFlag.Body,
EquipHands => EquipFlag.Hands,
EquipLegs => EquipFlag.Legs,
EquipFeet => EquipFlag.Feet,
EquipEars => EquipFlag.Ears,
EquipNeck => EquipFlag.Neck,
EquipWrist => EquipFlag.Wrist,
EquipRFinger => EquipFlag.RFinger,
EquipLFinger => EquipFlag.LFinger,
EquipMainhand => EquipFlag.Mainhand,
EquipOffhand => EquipFlag.Offhand,
StainHead => EquipFlag.HeadStain,
StainBody => EquipFlag.BodyStain,
StainHands => EquipFlag.HandsStain,
StainLegs => EquipFlag.LegsStain,
StainFeet => EquipFlag.FeetStain,
StainEars => EquipFlag.EarsStain,
StainNeck => EquipFlag.NeckStain,
StainWrist => EquipFlag.WristStain,
StainRFinger => EquipFlag.RFingerStain,
StainLFinger => EquipFlag.LFingerStain,
StainMainhand => EquipFlag.MainhandStain,
StainOffhand => EquipFlag.OffhandStain,
CustomizeRace => CustomizeFlag.Race,
CustomizeGender => CustomizeFlag.Gender,
CustomizeBodyType => CustomizeFlag.BodyType,
CustomizeHeight => CustomizeFlag.Height,
CustomizeClan => CustomizeFlag.Clan,
CustomizeFace => CustomizeFlag.Face,
CustomizeHairstyle => CustomizeFlag.Hairstyle,
CustomizeHighlights => CustomizeFlag.Highlights,
CustomizeSkinColor => CustomizeFlag.SkinColor,
CustomizeEyeColorRight => CustomizeFlag.EyeColorRight,
CustomizeHairColor => CustomizeFlag.HairColor,
CustomizeHighlightsColor => CustomizeFlag.HighlightsColor,
CustomizeFacialFeature1 => CustomizeFlag.FacialFeature1,
CustomizeFacialFeature2 => CustomizeFlag.FacialFeature2,
CustomizeFacialFeature3 => CustomizeFlag.FacialFeature3,
CustomizeFacialFeature4 => CustomizeFlag.FacialFeature4,
CustomizeFacialFeature5 => CustomizeFlag.FacialFeature5,
CustomizeFacialFeature6 => CustomizeFlag.FacialFeature6,
CustomizeFacialFeature7 => CustomizeFlag.FacialFeature7,
CustomizeLegacyTattoo => CustomizeFlag.LegacyTattoo,
CustomizeTattooColor => CustomizeFlag.TattooColor,
CustomizeEyebrows => CustomizeFlag.Eyebrows,
CustomizeEyeColorLeft => CustomizeFlag.EyeColorLeft,
CustomizeEyeShape => CustomizeFlag.EyeShape,
CustomizeSmallIris => CustomizeFlag.SmallIris,
CustomizeNose => CustomizeFlag.Nose,
CustomizeJaw => CustomizeFlag.Jaw,
CustomizeMouth => CustomizeFlag.Mouth,
CustomizeLipstick => CustomizeFlag.Lipstick,
CustomizeLipColor => CustomizeFlag.LipColor,
CustomizeMuscleMass => CustomizeFlag.MuscleMass,
CustomizeTailShape => CustomizeFlag.TailShape,
CustomizeBustSize => CustomizeFlag.BustSize,
CustomizeFacePaint => CustomizeFlag.FacePaint,
CustomizeFacePaintReversed => CustomizeFlag.FacePaintReversed,
CustomizeFacePaintColor => CustomizeFlag.FacePaintColor,
MetaWetness => MetaFlag.Wetness,
MetaHatState => MetaFlag.HatState,
MetaVisorState => MetaFlag.VisorState,
MetaWeaponState => MetaFlag.WeaponState,
MetaModelId => true,
CrestHead => CrestFlag.Head,
CrestBody => CrestFlag.Body,
CrestOffhand => CrestFlag.OffHand,
ParamSkinDiffuse => CustomizeParameterFlag.SkinDiffuse,
ParamMuscleTone => CustomizeParameterFlag.MuscleTone,
ParamSkinSpecular => CustomizeParameterFlag.SkinSpecular,
ParamLipDiffuse => CustomizeParameterFlag.LipDiffuse,
ParamHairDiffuse => CustomizeParameterFlag.HairDiffuse,
ParamHairSpecular => CustomizeParameterFlag.HairSpecular,
ParamHairHighlight => CustomizeParameterFlag.HairHighlight,
ParamLeftEye => CustomizeParameterFlag.LeftEye,
ParamRightEye => CustomizeParameterFlag.RightEye,
ParamFeatureColor => CustomizeParameterFlag.FeatureColor,
ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier,
ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset,
ParamDecalColor => CustomizeParameterFlag.DecalColor,
_ => -1,
};
public object? GetValue(in DesignData data)
{
return Value switch
{
EquipHead => data.Item(EquipSlot.Head),
EquipBody => data.Item(EquipSlot.Body),
EquipHands => data.Item(EquipSlot.Hands),
EquipLegs => data.Item(EquipSlot.Legs),
EquipFeet => data.Item(EquipSlot.Feet),
EquipEars => data.Item(EquipSlot.Ears),
EquipNeck => data.Item(EquipSlot.Neck),
EquipWrist => data.Item(EquipSlot.Wrists),
EquipRFinger => data.Item(EquipSlot.RFinger),
EquipLFinger => data.Item(EquipSlot.LFinger),
EquipMainhand => data.Item(EquipSlot.MainHand),
EquipOffhand => data.Item(EquipSlot.OffHand),
StainHead => data.Stain(EquipSlot.Head),
StainBody => data.Stain(EquipSlot.Body),
StainHands => data.Stain(EquipSlot.Hands),
StainLegs => data.Stain(EquipSlot.Legs),
StainFeet => data.Stain(EquipSlot.Feet),
StainEars => data.Stain(EquipSlot.Ears),
StainNeck => data.Stain(EquipSlot.Neck),
StainWrist => data.Stain(EquipSlot.Wrists),
StainRFinger => data.Stain(EquipSlot.RFinger),
StainLFinger => data.Stain(EquipSlot.LFinger),
StainMainhand => data.Stain(EquipSlot.MainHand),
StainOffhand => data.Stain(EquipSlot.OffHand),
CustomizeRace => data.Customize[CustomizeIndex.Race],
CustomizeGender => data.Customize[CustomizeIndex.Gender],
CustomizeBodyType => data.Customize[CustomizeIndex.BodyType],
CustomizeHeight => data.Customize[CustomizeIndex.Height],
CustomizeClan => data.Customize[CustomizeIndex.Clan],
CustomizeFace => data.Customize[CustomizeIndex.Face],
CustomizeHairstyle => data.Customize[CustomizeIndex.Hairstyle],
CustomizeHighlights => data.Customize[CustomizeIndex.Highlights],
CustomizeSkinColor => data.Customize[CustomizeIndex.SkinColor],
CustomizeEyeColorRight => data.Customize[CustomizeIndex.EyeColorRight],
CustomizeHairColor => data.Customize[CustomizeIndex.HairColor],
CustomizeHighlightsColor => data.Customize[CustomizeIndex.HighlightsColor],
CustomizeFacialFeature1 => data.Customize[CustomizeIndex.FacialFeature1],
CustomizeFacialFeature2 => data.Customize[CustomizeIndex.FacialFeature2],
CustomizeFacialFeature3 => data.Customize[CustomizeIndex.FacialFeature3],
CustomizeFacialFeature4 => data.Customize[CustomizeIndex.FacialFeature4],
CustomizeFacialFeature5 => data.Customize[CustomizeIndex.FacialFeature5],
CustomizeFacialFeature6 => data.Customize[CustomizeIndex.FacialFeature6],
CustomizeFacialFeature7 => data.Customize[CustomizeIndex.FacialFeature7],
CustomizeLegacyTattoo => data.Customize[CustomizeIndex.LegacyTattoo],
CustomizeTattooColor => data.Customize[CustomizeIndex.TattooColor],
CustomizeEyebrows => data.Customize[CustomizeIndex.Eyebrows],
CustomizeEyeColorLeft => data.Customize[CustomizeIndex.EyeColorLeft],
CustomizeEyeShape => data.Customize[CustomizeIndex.EyeShape],
CustomizeSmallIris => data.Customize[CustomizeIndex.SmallIris],
CustomizeNose => data.Customize[CustomizeIndex.Nose],
CustomizeJaw => data.Customize[CustomizeIndex.Jaw],
CustomizeMouth => data.Customize[CustomizeIndex.Mouth],
CustomizeLipstick => data.Customize[CustomizeIndex.Lipstick],
CustomizeLipColor => data.Customize[CustomizeIndex.LipColor],
CustomizeMuscleMass => data.Customize[CustomizeIndex.MuscleMass],
CustomizeTailShape => data.Customize[CustomizeIndex.TailShape],
CustomizeBustSize => data.Customize[CustomizeIndex.BustSize],
CustomizeFacePaint => data.Customize[CustomizeIndex.FacePaint],
CustomizeFacePaintReversed => data.Customize[CustomizeIndex.FacePaintReversed],
CustomizeFacePaintColor => data.Customize[CustomizeIndex.FacePaintColor],
MetaWetness => data.GetMeta(MetaIndex.Wetness),
MetaHatState => data.GetMeta(MetaIndex.HatState),
MetaVisorState => data.GetMeta(MetaIndex.VisorState),
MetaWeaponState => data.GetMeta(MetaIndex.WeaponState),
MetaModelId => data.ModelId,
CrestHead => data.Crest(CrestFlag.Head),
CrestBody => data.Crest(CrestFlag.Body),
CrestOffhand => data.Crest(CrestFlag.OffHand),
ParamSkinDiffuse => data.Parameters[CustomizeParameterFlag.SkinDiffuse],
ParamMuscleTone => data.Parameters[CustomizeParameterFlag.MuscleTone],
ParamSkinSpecular => data.Parameters[CustomizeParameterFlag.SkinSpecular],
ParamLipDiffuse => data.Parameters[CustomizeParameterFlag.LipDiffuse],
ParamHairDiffuse => data.Parameters[CustomizeParameterFlag.HairDiffuse],
ParamHairSpecular => data.Parameters[CustomizeParameterFlag.HairSpecular],
ParamHairHighlight => data.Parameters[CustomizeParameterFlag.HairHighlight],
ParamLeftEye => data.Parameters[CustomizeParameterFlag.LeftEye],
ParamRightEye => data.Parameters[CustomizeParameterFlag.RightEye],
ParamFeatureColor => data.Parameters[CustomizeParameterFlag.FeatureColor],
ParamFacePaintUvMultiplier => data.Parameters[CustomizeParameterFlag.FacePaintUvMultiplier],
ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset],
ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor],
_ => null,
};
}
private static string GetName(EquipFlag flag)
{
var slot = flag.ToSlot(out var stain);
return stain ? $"{slot.ToName()} Stain" : slot.ToName();
}
}
public static class StateExtensions
{
public static StateIndex ToState(this EquipSlot slot, bool stain = false)
=> (slot, stain) switch
{
(EquipSlot.Head, true) => new StateIndex(StateIndex.EquipHead),
(EquipSlot.Body, true) => new StateIndex(StateIndex.EquipBody),
(EquipSlot.Hands, true) => new StateIndex(StateIndex.EquipHands),
(EquipSlot.Legs, true) => new StateIndex(StateIndex.EquipLegs),
(EquipSlot.Feet, true) => new StateIndex(StateIndex.EquipFeet),
(EquipSlot.Ears, true) => new StateIndex(StateIndex.EquipEars),
(EquipSlot.Neck, true) => new StateIndex(StateIndex.EquipNeck),
(EquipSlot.Wrists, true) => new StateIndex(StateIndex.EquipWrist),
(EquipSlot.RFinger, true) => new StateIndex(StateIndex.EquipRFinger),
(EquipSlot.LFinger, true) => new StateIndex(StateIndex.EquipLFinger),
(EquipSlot.MainHand, true) => new StateIndex(StateIndex.EquipMainhand),
(EquipSlot.OffHand, true) => new StateIndex(StateIndex.EquipOffhand),
(EquipSlot.Head, false) => new StateIndex(StateIndex.StainHead),
(EquipSlot.Body, false) => new StateIndex(StateIndex.StainBody),
(EquipSlot.Hands, false) => new StateIndex(StateIndex.StainHands),
(EquipSlot.Legs, false) => new StateIndex(StateIndex.StainLegs),
(EquipSlot.Feet, false) => new StateIndex(StateIndex.StainFeet),
(EquipSlot.Ears, false) => new StateIndex(StateIndex.StainEars),
(EquipSlot.Neck, false) => new StateIndex(StateIndex.StainNeck),
(EquipSlot.Wrists, false) => new StateIndex(StateIndex.StainWrist),
(EquipSlot.RFinger, false) => new StateIndex(StateIndex.StainRFinger),
(EquipSlot.LFinger, false) => new StateIndex(StateIndex.StainLFinger),
(EquipSlot.MainHand, false) => new StateIndex(StateIndex.StainMainhand),
(EquipSlot.OffHand, false) => new StateIndex(StateIndex.StainOffhand),
_ => StateIndex.Invalid,
};
}

View file

@ -11,6 +11,7 @@ using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Glamourer.GameData;
using Penumbra.GameData.DataContainers;
using Glamourer.Designs;
namespace Glamourer.State;
@ -138,7 +139,7 @@ public class StateListener : IDisposable
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
}
private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize)
private void OnCustomizeChange(Model model, ref CustomizeArray customize)
{
if (!model.IsHuman)
return;
@ -163,21 +164,21 @@ public class StateListener : IDisposable
var model = state.ModelData.Customize;
if (customize.Gender != model.Gender || customize.Clan != model.Clan)
{
_manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateChanged.Source.Game);
_manager.ChangeEntireCustomize(state, in customize, CustomizeFlagExtensions.All, ApplySettings.Game);
return;
}
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.Sources[index] is not StateSource.Fixed)
{
var newValue = customize[index];
var oldValue = model[index];
if (newValue != oldValue)
{
if (set.Validate(index, newValue, out _, model.Face))
_manager.ChangeCustomize(state, index, newValue, StateChanged.Source.Game);
_manager.ChangeCustomize(state, index, newValue, ApplySettings.Game);
else
customize[index] = oldValue;
}
@ -213,7 +214,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.Sources[slot, false] is StateSource.Ipc;
}
_funModule.ApplyFunToSlot(actor, ref armor, slot);
@ -240,10 +241,10 @@ 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.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
{
_manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
_manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game);
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
switch (slot)
{
case EquipSlot.MainHand:
@ -251,7 +252,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.Sources[slot, false] is not StateSource.Ipc,
state.ModelData.IsHatVisible());
break;
}
@ -285,13 +286,13 @@ 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)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
else
apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
break;
@ -384,13 +385,13 @@ 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)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
else
apply = true;
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
else
apply = true;
@ -418,8 +419,8 @@ 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)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc)
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game);
else
value = state.ModelData.Crest(slot);
break;
@ -564,10 +565,10 @@ 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.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsVisorToggled();
else
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
_manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game);
}
else
{
@ -597,10 +598,10 @@ 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.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsHatVisible();
else
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
_manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game);
}
else
{
@ -630,10 +631,10 @@ 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.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc)
value = state.ModelData.IsWeaponVisible();
else
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
_manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game);
}
else
{
@ -699,9 +700,9 @@ public class StateListener : IDisposable
return;
var data = new ActorData(gameObject, _creatingIdentifier.ToName());
_applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible());
_applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible());
_applier.ChangeWetness(data, _creatingState.ModelData.IsWet());
_applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible());
_applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet());
_applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible());
ApplyParameters(_creatingState, drawObject);
}
@ -723,7 +724,7 @@ public class StateListener : IDisposable
_customizeState = null;
}
private unsafe void ApplyParameters(ActorState state, Model model)
private void ApplyParameters(ActorState state, Model model)
{
if (!model.IsHuman)
return;
@ -732,30 +733,30 @@ public class StateListener : IDisposable
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
var newValue = data[flag];
switch (state[flag])
switch (state.Sources[flag])
{
case StateChanged.Source.Game:
case StateSource.Game:
if (state.BaseData.Parameters.Set(flag, newValue))
_manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game);
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
break;
case StateChanged.Source.Manual:
case StateSource.Manual:
if (state.BaseData.Parameters.Set(flag, newValue))
_manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game);
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
else if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;
case StateChanged.Source.Fixed:
case StateSource.Fixed:
state.BaseData.Parameters.Set(flag, newValue);
if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;
case StateChanged.Source.Ipc:
case StateSource.Ipc:
state.BaseData.Parameters.Set(flag, newValue);
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;
case StateChanged.Source.Pending:
case StateSource.Pending:
state.BaseData.Parameters.Set(flag, newValue);
state[flag] = StateChanged.Source.Manual;
state.Sources[flag] = StateSource.Manual;
if (_config.UseAdvancedParameters)
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
break;

View file

@ -1,6 +1,6 @@
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Shader;
using Glamourer.Designs;
using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
@ -13,16 +13,18 @@ using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class StateManager(
public sealed class StateManager(
ActorManager _actors,
ItemManager _items,
StateChanged _event,
StateApplier _applier,
StateEditor _editor,
ItemManager items,
StateChanged @event,
StateApplier applier,
InternalStateEditor editor,
HumanModelList _humans,
ICondition _condition,
IClientState _clientState)
: IReadOnlyDictionary<ActorIdentifier, ActorState>
IClientState _clientState,
Configuration config,
JobChangeState jobChange,
DesignMerger merger)
: StateEditor(editor, applier, @event, jobChange, config, items, merger), IReadOnlyDictionary<ActorIdentifier, ActorState>
{
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
@ -93,7 +95,7 @@ public class StateManager(
// If the given actor is not a character, just return a default character.
if (!actor.IsCharacter)
{
ret.SetDefaultEquipment(_items);
ret.SetDefaultEquipment(Items);
return ret;
}
@ -127,7 +129,7 @@ public class StateManager(
// We can not use the head slot data from the draw object if the hat is hidden.
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
ret.SetItem(EquipSlot.Head, headItem);
ret.SetStain(EquipSlot.Head, head.Stain);
@ -135,7 +137,7 @@ public class StateManager(
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
{
var armor = model.GetArmor(slot);
var item = _items.Identify(slot, armor.Set, armor.Variant);
var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain);
}
@ -157,7 +159,7 @@ public class StateManager(
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var armor = actor.GetArmor(slot);
var item = _items.Identify(slot, armor.Set, armor.Variant);
var item = Items.Identify(slot, armor.Set, armor.Variant);
ret.SetItem(slot, item);
ret.SetStain(slot, armor.Stain);
}
@ -172,8 +174,8 @@ public class StateManager(
}
// Set the weapons regardless of source.
var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
var offItem = _items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
ret.SetItem(EquipSlot.MainHand, mainItem);
ret.SetStain(EquipSlot.MainHand, main.Stain);
ret.SetItem(EquipSlot.OffHand, offItem);
@ -197,7 +199,7 @@ public class StateManager(
if (mainhand.Skeleton.Id is < 1601 or >= 1651)
return;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
var gauntlets = Items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon;
@ -205,279 +207,11 @@ public class StateManager(
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
}
#region Change Values
/// <summary> Turn an actor human. </summary>
public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0)
public void TurnHuman(ActorState state, StateSource source, uint key = 0)
=> ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key);
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source,
uint key = 0)
{
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
return;
var actors = _applier.ForceRedraw(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
}
/// <summary> Change a customization value. </summary>
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key))
return;
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx));
}
/// <summary> Change an entire customization array according to flags. </summary>
public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source,
uint key = 0)
{
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
return;
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied));
}
/// <summary> Change a single piece of equipment without stain. </summary>
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeItem(state, slot, item, source, out var old, key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
}
/// <summary> Change a single piece of equipment including stain. </summary>
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key))
return;
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
var actors = type is StateChanged.Type.Equip
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc,
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
Glamourer.Log.Verbose(
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(type, source, state, actors, (old, item, slot));
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot));
}
/// <summary> Change only the stain of an equipment piece. </summary>
/// <remarks> Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeStain(state, slot, stain, source, out var old, key))
return;
var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
}
/// <summary> Change the crest of an equipment piece. </summary>
public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
return;
var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot));
}
/// <summary> Change the crest of an equipment piece. </summary>
public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value,
StateChanged.Source source, uint key = 0)
{
// Also apply main color to highlights when highlights is off.
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, source, key);
if (!_editor.ChangeParameter(state, flag, value, source, out var old, key))
return;
var @new = state.ModelData.Parameters[flag];
var actors = _applier.ChangeParameters(state, flag, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
Glamourer.Log.Verbose(
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag));
}
/// <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))
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));
}
/// <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))
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));
}
/// <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))
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));
}
/// <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))
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));
}
#endregion
public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0)
{
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(),
source,
out var oldModelId, key))
return;
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
if (design.DoApplyWetness())
_editor.ChangeMetaState(state, ActorState.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);
if (design.DoApplyWeaponVisible())
_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key);
if (design.DoApplyVisorToggle())
_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key);
var flags = state.AllowsRedraw(_condition)
? design.ApplyCustomize
: design.ApplyCustomize & ~CustomizeFlagExtensions.RedrawRequired;
_editor.ChangeHumanCustomize(state, design.DesignData.Customize, flags, source, out _, out var applied, key);
redraw |= applied.RequiresRedraw();
foreach (var slot in EquipSlotExtensions.FullSlots)
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
var paramSource = source is StateChanged.Source.Manual
? StateChanged.Source.Pending
: source;
foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter))
_editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key);
// Do not apply highlights from a design if highlights is unchecked.
if (!state.ModelData.Customize.Highlights)
_editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
state[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);
return;
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
{
var unused = (applyPiece, applyStain) switch
{
(false, false) => false,
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
out _, key),
};
}
}
private ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
{
var actors = _applier.ChangeWetness(state, true);
if (redraw)
{
if (withLock)
state.TempLock();
_applier.ForceRedraw(actors);
}
else
{
_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,
state.ModelData.IsHatVisible());
}
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
_applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
_applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
}
if (state.ModelData.IsHuman)
{
_applier.ChangeHatState(actors, state.ModelData.IsHatVisible());
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
_applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
}
return actors;
}
public void ResetState(ActorState state, StateChanged.Source source, uint key = 0)
public void ResetState(ActorState state, StateSource source, uint key = 0)
{
if (!state.Unlock(key))
return;
@ -489,33 +223,33 @@ public class StateManager(
state.ModelData = state.BaseData;
state.ModelData.SetIsWet(false);
foreach (var index in Enum.GetValues<CustomizeIndex>())
state[index] = StateChanged.Source.Game;
state.Sources[index] = StateSource.Game;
foreach (var slot in EquipSlotExtensions.FullSlots)
{
state[slot, true] = StateChanged.Source.Game;
state[slot, false] = StateChanged.Source.Game;
state.Sources[slot, true] = StateSource.Game;
state.Sources[slot, false] = StateSource.Game;
}
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
state[type] = StateChanged.Source.Game;
foreach (var type in Enum.GetValues<MetaIndex>())
state.Sources[type] = StateSource.Game;
foreach (var slot in CrestExtensions.AllRelevantSet)
state[slot] = StateChanged.Source.Game;
state.Sources[slot] = StateSource.Game;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game;
state.Sources[flag] = StateSource.Game;
var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
actors = ApplyAll(state, redraw, true);
if (source is StateSource.Manual or StateSource.Ipc)
actors = Applier.ApplyAll(state, redraw, true);
Glamourer.Log.Verbose(
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
}
public void ResetAdvancedState(ActorState state, StateChanged.Source source, uint key = 0)
public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0)
{
if (!state.Unlock(key) || !state.ModelData.IsHuman)
return;
@ -523,14 +257,14 @@ public class StateManager(
state.ModelData.Parameters = state.BaseData.Parameters;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game;
state.Sources[flag] = StateSource.Game;
var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
if (source is StateSource.Manual or StateSource.Ipc)
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
Glamourer.Log.Verbose(
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
}
public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0)
@ -538,69 +272,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.Sources[i] is StateSource.Fixed))
{
state[index] = StateChanged.Source.Game;
state.Sources[index] = StateSource.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.Sources[slot, true] is StateSource.Fixed)
{
state[slot, true] = StateChanged.Source.Game;
state.Sources[slot, true] = StateSource.Game;
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
}
if (state[slot, false] is StateChanged.Source.Fixed)
if (state.Sources[slot, false] is StateSource.Fixed)
{
state[slot, false] = StateChanged.Source.Game;
state.Sources[slot, false] = StateSource.Game;
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
}
}
foreach (var slot in CrestExtensions.AllRelevantSet)
{
if (state[slot] is StateChanged.Source.Fixed)
if (state.Sources[slot] is StateSource.Fixed)
{
state[slot] = StateChanged.Source.Game;
state.Sources[slot] = StateSource.Game;
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
}
}
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
switch (state[flag])
switch (state.Sources[flag])
{
case StateChanged.Source.Fixed:
case StateChanged.Source.Manual when !respectManualPalettes:
state[flag] = StateChanged.Source.Game;
case StateSource.Fixed:
case StateSource.Manual when !respectManualPalettes:
state.Sources[flag] = StateSource.Game;
state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag];
break;
}
}
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed)
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed)
{
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game;
state.Sources[MetaIndex.HatState] = StateSource.Game;
state.ModelData.SetHatVisible(state.BaseData.IsHatVisible());
}
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed)
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed)
{
state[ActorState.MetaIndex.VisorState] = StateChanged.Source.Game;
state.Sources[MetaIndex.VisorState] = StateSource.Game;
state.ModelData.SetVisor(state.BaseData.IsVisorToggled());
}
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed)
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed)
{
state[ActorState.MetaIndex.WeaponState] = StateChanged.Source.Game;
state.Sources[MetaIndex.WeaponState] = StateSource.Game;
state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible());
}
if (state[ActorState.MetaIndex.Wetness] is StateChanged.Source.Fixed)
if (state.Sources[MetaIndex.Wetness] is StateSource.Fixed)
{
state[ActorState.MetaIndex.Wetness] = StateChanged.Source.Game;
state.Sources[MetaIndex.Wetness] = StateSource.Game;
state.ModelData.SetIsWet(state.BaseData.IsWet());
}
}
@ -610,7 +344,8 @@ public class StateManager(
if (!GetOrCreate(actor, out var state))
return;
ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
Applier.ApplyAll(state,
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
false);
}

View file

@ -0,0 +1,75 @@
using Penumbra.GameData.Enums;
namespace Glamourer.State;
public enum StateSource : byte
{
Game,
Manual,
Fixed,
Ipc,
// Only used for CustomizeParameters.
Pending,
}
public unsafe struct StateSources
{
public const int Size = (StateIndex.Size + 1) / 2;
private fixed byte _data[Size];
public StateSources()
{ }
public StateSource this[StateIndex index]
{
get
{
var val = _data[index.Value / 2];
return (StateSource)((index.Value & 1) == 1 ? val >> 4 : val & 0x0F);
}
set
{
var val = _data[index.Value / 2];
if ((index.Value & 1) == 1)
val = (byte)((val & 0x0F) | ((byte)value << 4));
else
val = (byte)((val & 0xF0) | (byte)value);
_data[index.Value / 2] = val;
}
}
public StateSource this[EquipSlot slot, bool stain]
{
get => this[slot.ToState(stain)];
set => this[slot.ToState(stain)] = value;
}
public void RemoveFixedDesignSources()
{
for (var i = 0; i < Size; ++i)
{
var value = _data[i];
switch (value)
{
case (byte)StateSource.Fixed | ((byte)StateSource.Fixed << 4):
_data[i] = (byte)StateSource.Manual | ((byte)StateSource.Manual << 4);
break;
case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4):
case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4):
case (byte)StateSource.Ipc | ((byte)StateSource.Fixed << 4):
case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4):
_data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4));
break;
case (byte)StateSource.Fixed:
case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed:
case ((byte)StateSource.Ipc << 4) | (byte)StateSource.Fixed:
case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed:
_data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual);
break;
}
}
}
}

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

@ -1 +1 @@
Subproject commit 1ebaf1d209b7edc783896b3a6af4907c91bb302e
Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4