diff --git a/Glamourer/Api/GlamourerIpc.Apply.cs b/Glamourer/Api/GlamourerIpc.Apply.cs index c2d68aa..d155cf2 100644 --- a/Glamourer/Api/GlamourerIpc.Apply.cs +++ b/Glamourer/Api/GlamourerIpc.Apply.cs @@ -130,7 +130,7 @@ public partial class GlamourerIpc if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode)) { - _stateManager.ApplyDesign(design, state, StateSource.Ipc, lockCode); + _stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode)); state.Lock(lockCode); } } diff --git a/Glamourer/Api/GlamourerIpc.Set.cs b/Glamourer/Api/GlamourerIpc.Set.cs index 983fd71..db5941f 100644 --- a/Glamourer/Api/GlamourerIpc.Set.cs +++ b/Glamourer/Api/GlamourerIpc.Set.cs @@ -1,5 +1,6 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin; +using Glamourer.Designs; using Glamourer.Events; using Glamourer.Services; using Glamourer.State; @@ -56,7 +57,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key)); return GlamourerErrorCode.Success; } @@ -83,7 +84,7 @@ public partial class GlamourerIpc if (!state.ModelData.IsHuman) return GlamourerErrorCode.ActorNotHuman; - _stateManager.ChangeEquip(state, slot, item, stainId, StateSource.Ipc, key); + _stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key)); found = true; } diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index db4058f..f8ca397 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -3,12 +3,9 @@ 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; @@ -18,52 +15,39 @@ namespace Glamourer.Automation; 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 DesignMerger _designMerger; - 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 _jobChange = []; + private readonly JobChangeState _jobChangeState; - private void ResetJobChange() - { - _jobChangeState = null; - _jobChange.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, DesignMerger designMerger) + 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; - _designMerger = designMerger; - _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); @@ -79,45 +63,46 @@ public sealed 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 (_jobChange.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 (_jobChange.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(); } } @@ -135,7 +120,7 @@ public sealed 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; @@ -277,8 +262,9 @@ public sealed class AutoDesignApplier : IDisposable if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId)) return; - var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?) d.Design, d.Type)), state.ModelData, true, false); - ApplyToState(state, mergedDesign, respectManual, fromJobChange, StateSource.Fixed); + var mergedDesign = _designMerger.Merge(set.Designs.Where(d => d.IsActive(actor)).Select(d => ((DesignBase?)d.Design, d.Type)), + state.ModelData, true, false); + _state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false)); } /// Get world-specific first and all-world afterward. @@ -304,218 +290,6 @@ public sealed class AutoDesignApplier : IDisposable } } - private void ApplyToState(ActorState state, MergedDesign mergedDesign, bool respectManual, bool fromJobChange, StateSource source) - { - foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest)) - if (!respectManual || state.Sources[slot] is not StateSource.Manual) - _state.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), mergedDesign.GetSource(slot, source)); - - foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate()) - if (!respectManual || state.Sources[parameter] is not StateSource.Manual and not StateSource.Pending) - _state.ChangeCustomizeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], mergedDesign.GetSource(parameter, source)); - - foreach (var slot in EquipSlotExtensions.EqdpSlots) - { - if (mergedDesign.Design.DoApplyEquip(slot)) - { - if (!respectManual || state.Sources[slot, false] is not StateSource.Manual) - _state.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot), mergedDesign.GetSource(slot, false, source)); - } - - if (mergedDesign.Design.DoApplyStain(slot)) - { - if (!respectManual || state.Sources[slot, true] is not StateSource.Manual) - _state.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot), mergedDesign.GetSource(slot, true, source)); - } - } - - foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots) - { - if (mergedDesign.Design.DoApplyStain(weaponSlot)) - { - if (!respectManual || state.Sources[weaponSlot, true] is not StateSource.Manual) - _state.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot), mergedDesign.GetSource(weaponSlot, true, source)); - } - - if (!mergedDesign.Design.DoApplyEquip(weaponSlot)) - continue; - - if (respectManual && state.Sources[weaponSlot, false] is StateSource.Manual) - continue; - - var currentType = state.ModelData.Item(weaponSlot).Type; - if (fromJobChange) - { - foreach (var (key, (weapon, weaponSource)) in mergedDesign.Weapons) - if (key.ToSlot() == weaponSlot) - _jobChange.TryAdd(key, (weapon, MergedDesign.GetSource(weaponSource, source))); - _jobChangeState = state; - } - else if (mergedDesign.Weapons.TryGetValue(currentType, out var weapon)) - { - _state.ChangeItem(state, weaponSlot, weapon.Item1, MergedDesign.GetSource(weapon.Item2, source)); - } - } - } - - private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual, - StateSource 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.Sources[slot, false] is not StateSource.Manual) - _state.ChangeItem(state, slot, item, source); - totalEquipFlags |= flag; - } - } - - var stainFlag = slot.ToStainFlag(); - if (equipFlags.HasFlag(stainFlag)) - { - if (!respectManual || state.Sources[slot, true] is not StateSource.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.Sources[EquipSlot.MainHand, false] is not StateSource.Manual; - if (checkUnlock && checkState) - { - if (fromJobChange) - { - _jobChange.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.Sources[EquipSlot.OffHand, false] is not StateSource.Manual; - if (checkUnlock && checkState) - { - if (fromJobChange) - { - _jobChange.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.Sources[EquipSlot.MainHand, true] is not StateSource.Manual) - _state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source); - totalEquipFlags |= EquipFlag.MainhandStain; - } - - if (equipFlags.HasFlag(EquipFlag.OffhandStain)) - { - if (!respectManual || state.Sources[EquipSlot.OffHand, true] is not StateSource.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, StateSource 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.Sources[CustomizeIndex.Clan] is not StateSource.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.Sources[CustomizeIndex.Gender] is not StateSource.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.Sources[CustomizeIndex.Face] is not StateSource.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()) - { - 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.Sources[index] is not StateSource.Manual) - _state.ChangeCustomize(state, index, value, source); - totalCustomizeFlags |= flag; - } - } - } - - private void ReduceMeta(ActorState state, in DesignData design, MetaFlag apply, ref MetaFlag totalMetaFlags, bool respectManual, StateSource source) - { - apply &= ~totalMetaFlags; - foreach (var index in MetaExtensions.AllRelevant) - { - if (!respectManual || state.Sources[index] is not StateSource.Manual) - _state.ChangeMeta(state, index, design.GetMeta(index), source); - totalMetaFlags |= index.ToFlag(); - } - } - internal static int NewGearsetId = -1; private void OnEquippedGearset(string name, int id, int prior, byte _, byte job) diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index eb4fefe..d150db5 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -254,12 +254,11 @@ public class DesignEditor( _forceFullItemOff = true; foreach (var slot in EquipSlotExtensions.FullSlots) { - if (other.DoApplyEquip(slot)) - ChangeItem(design, slot, other.DesignData.Item(slot)); - - if (other.DoApplyStain(slot)) - ChangeStain(design, slot, other.DesignData.Stain(slot)); + 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().Where(other.DoApplyCrest)) diff --git a/Glamourer/Designs/DesignManager.cs b/Glamourer/Designs/DesignManager.cs index c247396..408cf27 100644 --- a/Glamourer/Designs/DesignManager.cs +++ b/Glamourer/Designs/DesignManager.cs @@ -12,7 +12,7 @@ using Penumbra.GameData.Enums; namespace Glamourer.Designs; -public class DesignManager : DesignEditor +public sealed class DesignManager : DesignEditor { public readonly DesignStorage Designs; private readonly HumanModelList _humans; diff --git a/Glamourer/Designs/IDesignEditor.cs b/Glamourer/Designs/IDesignEditor.cs index 8cf1a87..a4da53c 100644 --- a/Glamourer/Designs/IDesignEditor.cs +++ b/Glamourer/Designs/IDesignEditor.cs @@ -11,7 +11,26 @@ public readonly record struct ApplySettings( StateSource Source = StateSource.Manual, bool RespectManual = false, bool FromJobChange = false, - bool UseSingleSource = false); + bool UseSingleSource = false) +{ + public static readonly ApplySettings Manual = new() + { + Key = 0, + Source = StateSource.Manual, + FromJobChange = false, + RespectManual = false, + UseSingleSource = false, + }; + + public static readonly ApplySettings Game = new() + { + Key = 0, + Source = StateSource.Game, + FromJobChange = false, + RespectManual = false, + UseSingleSource = false, + }; +} public interface IDesignEditor { diff --git a/Glamourer/Designs/Links/DesignMerger.cs b/Glamourer/Designs/Links/DesignMerger.cs index ef77226..7670498 100644 --- a/Glamourer/Designs/Links/DesignMerger.cs +++ b/Glamourer/Designs/Links/DesignMerger.cs @@ -224,7 +224,7 @@ public class DesignMerger( ret.Sources[CustomizeIndex.Face] = source; } - var set = ret.Design.CustomizeSet; + var set = _customize.Manager.GetSet(customize.Clan, customize.Gender); var face = customize.Face; foreach (var index in Enum.GetValues()) { diff --git a/Glamourer/Designs/Links/MergedDesign.cs b/Glamourer/Designs/Links/MergedDesign.cs index 74e65ca..bcfaa39 100644 --- a/Glamourer/Designs/Links/MergedDesign.cs +++ b/Glamourer/Designs/Links/MergedDesign.cs @@ -48,22 +48,4 @@ public sealed class MergedDesign public readonly Dictionary Weapons = new(4); public readonly SortedList AssociatedMods = []; public StateSources Sources = new(); - - public StateSource GetSource(EquipSlot slot, bool stain, StateSource actualSource) - => GetSource(Sources[slot, stain], actualSource); - - public StateSource GetSource(CrestFlag slot, StateSource actualSource) - => GetSource(Sources[slot], actualSource); - - public StateSource GetSource(CustomizeIndex type, StateSource actualSource) - => GetSource(Sources[type], actualSource); - - public StateSource GetSource(MetaIndex index, StateSource actualSource) - => GetSource(Sources[index], actualSource); - - public StateSource GetSource(CustomizeParameterFlag flag, StateSource actualSource) - => GetSource(Sources[flag], actualSource); - - public static StateSource GetSource(StateSource given, StateSource actualSource) - => given is StateSource.Game ? StateSource.Game : actualSource; } diff --git a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs index 07f3486..d092cde 100644 --- a/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs +++ b/Glamourer/Gui/Customization/CustomizeParameterDrawData.cs @@ -33,7 +33,7 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des { Locked = state.IsLocked, DisplayApplication = false, - ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateSource.Manual), + ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, ApplySettings.Manual), GameValue = state.BaseData.Parameters[flag], AllowRevert = true, }; diff --git a/Glamourer/Gui/DesignQuickBar.cs b/Glamourer/Gui/DesignQuickBar.cs index 37c37e7..78e813e 100644 --- a/Glamourer/Gui/DesignQuickBar.cs +++ b/Glamourer/Gui/DesignQuickBar.cs @@ -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, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } public void DrawRevertButton(Vector2 buttonSize) diff --git a/Glamourer/Gui/Equipment/EquipDrawData.cs b/Glamourer/Gui/Equipment/EquipDrawData.cs index f8dd42c..6bedb02 100644 --- a/Glamourer/Gui/Equipment/EquipDrawData.cs +++ b/Glamourer/Gui/Equipment/EquipDrawData.cs @@ -44,8 +44,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, StateSource.Manual), - StainSetter = i => manager.ChangeStain(state, slot, i, StateSource.Manual), + ItemSetter = i => manager.ChangeItem(state, slot, i, ApplySettings.Manual), + StainSetter = i => manager.ChangeStain(state, slot, i, ApplySettings.Manual), Locked = state.IsLocked, DisplayApplication = false, GameItem = state.BaseData.Item(slot), diff --git a/Glamourer/Gui/PenumbraChangedItemTooltip.cs b/Glamourer/Gui/PenumbraChangedItemTooltip.cs index ecea9ad..4cb11d0 100644 --- a/Glamourer/Gui/PenumbraChangedItemTooltip.cs +++ b/Glamourer/Gui/PenumbraChangedItemTooltip.cs @@ -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, StateSource.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, StateSource.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, StateSource.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, StateSource.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, StateSource.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, StateSource.Manual); + _stateManager.ChangeItem(state, slot, item, ApplySettings.Manual); } return; diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index e1d0eef..15f5bd0 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -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, StateSource.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!, StateSource.Manual); + _stateManager.ApplyDesign(_state!, designBase, ApplySettings.Manual); Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success, false); } @@ -139,7 +138,7 @@ public class ActorPanel( return; if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw)) - _stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateSource.Manual); + _stateManager.ChangeEntireCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, ApplySettings.Manual); 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, StateSource.Manual); + _stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual); } var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); @@ -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!, StateSource.Manual); + _stateManager.ApplyDesign(_state!, design, ApplySettings.Manual); } catch (Exception ex) { @@ -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, - StateSource.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, - StateSource.Manual); + _stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), + ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs index 90302b8..04537b5 100644 --- a/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs +++ b/Glamourer/Gui/Tabs/DebugTab/NpcAppearancePanel.cs @@ -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, StateSource.Manual); - _state.ChangeMeta(state!, MetaIndex.VisorState, data.VisorToggled, StateSource.Manual); - _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateSource.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(); diff --git a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs index 803a226..173a84d 100644 --- a/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs +++ b/Glamourer/Gui/Tabs/DesignTab/DesignPanel.cs @@ -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; @@ -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, StateSource.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); } } @@ -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, StateSource.Manual); + _state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual); } } diff --git a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs index 829a681..bd04a57 100644 --- a/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs +++ b/Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs @@ -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; @@ -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, StateSource.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual); } } @@ -221,7 +219,7 @@ public class NpcPanel( { var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags(); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0); - _state.ApplyDesign(design, state, StateSource.Manual); + _state.ApplyDesign(state, design, ApplySettings.Manual); } } diff --git a/Glamourer/Gui/ToggleDrawData.cs b/Glamourer/Gui/ToggleDrawData.cs index deb1908..e325152 100644 --- a/Glamourer/Gui/ToggleDrawData.cs +++ b/Glamourer/Gui/ToggleDrawData.cs @@ -54,7 +54,7 @@ public ref struct ToggleDrawData 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, StateSource.Manual), + SetValue = v => manager.ChangeCrest(state, slot, v, ApplySettings.Manual), }; public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state) @@ -65,7 +65,7 @@ public ref struct ToggleDrawData Tooltip = index.ToTooltip(), Locked = state.IsLocked, CurrentValue = state.ModelData.GetMeta(index), - SetValue = b => manager.ChangeMeta(state, index, b, StateSource.Manual), + SetValue = b => manager.ChangeMetaState(state, index, b, ApplySettings.Manual), }; } diff --git a/Glamourer/Interop/ContextMenuService.cs b/Glamourer/Interop/ContextMenuService.cs index 6bdac74..3cfca50 100644 --- a/Glamourer/Interop/ContextMenuService.cs +++ b/Glamourer/Interop/ContextMenuService.cs @@ -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, StateSource.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, StateSource.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, StateSource.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, StateSource.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, StateSource.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, StateSource.Manual); + _state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual); } }; } diff --git a/Glamourer/Services/CommandService.cs b/Glamourer/Services/CommandService.cs index e300ab4..ddc7217 100644 --- a/Glamourer/Services/CommandService.cs +++ b/Glamourer/Services/CommandService.cs @@ -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, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } 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, StateSource.Manual); + _stateManager.ApplyDesign(state, design, ApplySettings.Manual); } } } diff --git a/Glamourer/Services/ServiceManager.cs b/Glamourer/Services/ServiceManager.cs index 2f64cf8..d9d76b4 100644 --- a/Glamourer/Services/ServiceManager.cs +++ b/Glamourer/Services/ServiceManager.cs @@ -121,7 +121,7 @@ public static class ServiceManagerA private static ServiceManager AddState(this ServiceManager services) => services.AddSingleton() .AddSingleton() - .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); diff --git a/Glamourer/State/ActorState.cs b/Glamourer/State/ActorState.cs index 0b3204f..b5126da 100644 --- a/Glamourer/State/ActorState.cs +++ b/Glamourer/State/ActorState.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Events; using Penumbra.GameData.Actors; using Penumbra.GameData.Enums; using Dalamud.Game.ClientState.Conditions; diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs new file mode 100644 index 0000000..2bc50c3 --- /dev/null +++ b/Glamourer/State/InternalStateEditor.cs @@ -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) +{ + /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. + /// We currently only allow changing things to humans, not humans to monsters. + 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().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; + } + + /// Change a customization value. + 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; + } + + /// Change an entire customization array according to functions. + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, + Func 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()) + { + if (applied.HasFlag(type.ToFlag())) + state.Sources[type] = source(type); + } + + return true; + } + + /// Change an entire customization array according to functions. + public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, Func applyWhich, + Func source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0) + { + var apply = Enum.GetValues().Where(applyWhich).Aggregate((CustomizeFlag)0, (current, type) => current | type.ToFlag()); + return ChangeHumanCustomize(state, customizeInput, apply, source, out old, out changed, key); + } + + /// Change a single piece of equipment without stain. + 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; + } + + /// Change a single piece of equipment including stain. + 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; + } + + /// Change only the stain of an equipment piece. + 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; + } + + /// Change the crest of an equipment piece. + 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; + } + + /// Change the customize flags of a character. + 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; + } +} diff --git a/Glamourer/State/JobChangeState.cs b/Glamourer/State/JobChangeState.cs new file mode 100644 index 0000000..84aa3cc --- /dev/null +++ b/Glamourer/State/JobChangeState.cs @@ -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, 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; + } +} diff --git a/Glamourer/State/StateApplier.cs b/Glamourer/State/StateApplier.cs index 906beb6..7222a4b 100644 --- a/Glamourer/State/StateApplier.cs +++ b/Glamourer/State/StateApplier.cs @@ -1,5 +1,4 @@ using Glamourer.Designs; -using Glamourer.Events; using Glamourer.GameData; using Glamourer.Interop; using Glamourer.Interop.Penumbra; @@ -277,6 +276,47 @@ public class StateApplier( return data; } + /// Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. + /// The state to apply. + /// Whether a redraw should be forced. + /// Whether a temporary lock should be applied for the redraw. + /// The actor data for the actors who got changed. + 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(); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index 403c106..527daa6 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -1,221 +1,334 @@ -using Dalamud.Plugin.Services; 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; namespace Glamourer.State; -public class StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition) +public class StateEditor( + InternalStateEditor editor, + StateApplier applier, + StateChanged stateChanged, + JobChangeState jobChange, + Configuration config, + ItemManager items) : IDesignEditor { - /// Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. - /// We currently only allow changing things to humans, not humans to monsters. - public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source, - out uint oldModelId, uint key = 0) - { - oldModelId = state.ModelData.ModelId; + 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; - // 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().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; - } - - /// Change a customization value. - 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; - } - - /// Change an entire customization array according to flags. - public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, 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()) - { - if (applied.HasFlag(type.ToFlag())) - state.Sources[type] = source; - } - - return true; - } - - /// Change a single piece of equipment without stain. - 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; - } - - /// Change a single piece of equipment including stain. - 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; - } - - /// Change only the stain of an equipment piece. - 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; - } - - /// Change the crest of an equipment piece. - 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; - } - - /// Change the customize flags of a character. - 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, + /// Turn an actor to. + public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, uint key = 0) { - oldValue = state.ModelData.GetMeta(index); - if (!state.CanUnlock(key)) - return false; + if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key)) + return; - state.ModelData.SetMeta(index, value); - state.Sources[index] = source; - return true; + 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)); + } + + /// + public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings) + { + switch (item.HasValue, stain.HasValue) + { + 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 (data is not ActorState state) + return; + + 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; + + 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)); + } + + /// + public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + 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)); + } + + /// + public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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)); + } + + /// + public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings) + { + if (data is not ActorState state) + return; + + 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 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) + => ApplyDesign(data, new MergedDesign(design), settings with + { + FromJobChange = false, + RespectManual = false, + UseSingleSource = true, + }); + + /// Apply offhand item and potentially gauntlets if configured. + 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); } } diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 09c0673..1833660 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -139,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; @@ -164,7 +164,7 @@ 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, StateSource.Game); + _manager.ChangeEntireCustomize(state, in customize, CustomizeFlagExtensions.All, ApplySettings.Game); return; } @@ -178,7 +178,7 @@ public class StateListener : IDisposable if (newValue != oldValue) { if (set.Validate(index, newValue, out _, model.Face)) - _manager.ChangeCustomize(state, index, newValue, StateSource.Game); + _manager.ChangeCustomize(state, index, newValue, ApplySettings.Game); else customize[index] = oldValue; } @@ -243,8 +243,8 @@ public class StateListener : IDisposable var changed = changedItem.Weapon(stain); if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) { - _manager.ChangeItem(state, slot, currentItem, StateSource.Game); - _manager.ChangeStain(state, slot, current.Stain, StateSource.Game); + _manager.ChangeItem(state, slot, currentItem, ApplySettings.Game); + _manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game); switch (slot) { case EquipSlot.MainHand: @@ -287,12 +287,12 @@ public class StateListener : IDisposable case UpdateState.Transformed: break; case UpdateState.Change: if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; break; @@ -386,12 +386,12 @@ public class StateListener : IDisposable case UpdateState.Change: var apply = false; if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateSource.Game); + _manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game); else apply = true; if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateSource.Game); + _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game); else apply = true; @@ -420,7 +420,7 @@ public class StateListener : IDisposable { case UpdateState.Change: if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc) - _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateSource.Game); + _manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game); else value = state.ModelData.Crest(slot); break; @@ -568,7 +568,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsVisorToggled(); else - _manager.ChangeMeta(state, MetaIndex.VisorState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game); } else { @@ -601,7 +601,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsHatVisible(); else - _manager.ChangeMeta(state, MetaIndex.HatState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game); } else { @@ -634,7 +634,7 @@ public class StateListener : IDisposable if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc) value = state.ModelData.IsWeaponVisible(); else - _manager.ChangeMeta(state, MetaIndex.WeaponState, value, StateSource.Game); + _manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game); } else { @@ -724,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; @@ -737,11 +737,11 @@ public class StateListener : IDisposable { case StateSource.Game: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); break; case StateSource.Manual: if (state.BaseData.Parameters.Set(flag, newValue)) - _manager.ChangeCustomizeParameter(state, flag, newValue, StateSource.Game); + _manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game); else if (_config.UseAdvancedParameters) model.ApplySingleParameterData(flag, state.ModelData.Parameters); break; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 518b435..5d0e01f 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -12,17 +12,17 @@ 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, - Configuration _config) - : IReadOnlyDictionary + Configuration config, + JobChangeState jobChange) + : StateEditor(editor, applier, @event, jobChange, config, items), IReadOnlyDictionary { private readonly Dictionary _states = []; @@ -93,7 +93,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 +127,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 +135,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 +157,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 +172,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 +197,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,251 +205,10 @@ public class StateManager( ret.SetStain(EquipSlot.Hands, mainhand.Stain); } - #region Change Values - /// Turn an actor human. public void TurnHuman(ActorState state, StateSource source, uint key = 0) => ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key); - /// Turn an actor to. - public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source, - uint key = 0) - { - 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")}.]"); - _event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId)); - } - - /// Change a customization value. - public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source, uint key = 0) - { - if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key)) - return; - - var actors = _applier.ChangeCustomize(state, 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")}.]"); - _event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx)); - } - - /// Change an entire customization array according to flags. - public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateSource 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 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")}.]"); - _event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied)); - } - - /// Change a single piece of equipment without stain. - /// Do not use this in the same frame as ChangeStain, use instead. - public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource 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 StateSource.Manual or StateSource.Ipc) - : _applier.ChangeWeapon(state, slot, 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, source, key); - - 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)); - } - - /// Change a single piece of equipment including stain. - public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource 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 StateSource.Manual or StateSource.Ipc) - : _applier.ChangeWeapon(state, slot, 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, source, key); - - 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)); - } - - /// Change only the stain of an equipment piece. - /// Do not use this in the same frame as ChangeEquip, use instead. - public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, uint key = 0) - { - if (!_editor.ChangeStain(state, slot, stain, source, out var old, key)) - return; - - var actors = _applier.ChangeStain(state, slot, 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")}.]"); - _event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot)); - } - - /// Change the crest of an equipment piece. - public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, uint key = 0) - { - if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key)) - return; - - var actors = _applier.ChangeCrests(state, 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")}.]"); - _event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); - } - - /// Change the crest of an equipment piece. - public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, - StateSource 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 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")}.]"); - _event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag)); - } - - /// Change meta state. - public void ChangeMeta(ActorState state, MetaIndex meta, bool value, StateSource source, uint key = 0) - { - if (!_editor.ChangeMetaState(state, meta, value, source, out var old, key)) - return; - - var actors = _applier.ChangeMetaState(state, meta, 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")}.]"); - _event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, MetaIndex.HatState)); - } - - #endregion - - public void ApplyDesign(DesignBase design, ActorState state, StateSource 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.DoApplyMeta(MetaIndex.Wetness)) - _editor.ChangeMetaState(state, MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key); - - if (state.ModelData.IsHuman) - { - if (design.DoApplyMeta(MetaIndex.HatState)) - _editor.ChangeMetaState(state, MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key); - if (design.DoApplyMeta(MetaIndex.WeaponState)) - _editor.ChangeMetaState(state, MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key); - if (design.DoApplyMeta(MetaIndex.VisorState)) - _editor.ChangeMetaState(state, 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 StateSource.Manual - ? StateSource.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.Sources[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.Sources[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.ChangeMetaState(state, MetaIndex.Wetness, 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.Sources[slot, false] is not StateSource.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.ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible()); - _applier.ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible()); - _applier.ChangeMetaState(actors, MetaIndex.VisorState, 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, StateSource source, uint key = 0) { if (!state.Unlock(key)) @@ -481,11 +240,11 @@ public class StateManager( var actors = ActorData.Invalid; if (source is StateSource.Manual or StateSource.Ipc) - actors = ApplyAll(state, redraw, true); + 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, StateSource source, uint key = 0) @@ -500,10 +259,10 @@ public class StateManager( var actors = ActorData.Invalid; if (source is StateSource.Manual or StateSource.Ipc) - actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true); + 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) @@ -583,26 +342,11 @@ 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); } public void DeleteState(ActorIdentifier identifier) => _states.Remove(identifier); - - /// Apply offhand item and potentially gauntlets if configured. - private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, StateSource source, uint key = 0) - { - if (!_config.ChangeEntireItem || 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), source, key); - - 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), source, key); - } }