mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-09 09:24:39 +01:00
Merge branch 'designlinks'
This commit is contained in:
commit
eba27e10fb
69 changed files with 3395 additions and 1997 deletions
|
|
@ -1,8 +1,8 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.State;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
|
|
@ -130,12 +130,12 @@ public partial class GlamourerIpc
|
|||
|
||||
if ((hasModelId || state.ModelData.ModelId == 0) && state.CanUnlock(lockCode))
|
||||
{
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Ipc, lockCode);
|
||||
_stateManager.ApplyDesign(state, design, new ApplySettings(Source:StateSource.Ipc, Key:lockCode));
|
||||
state.Lock(lockCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDesignByGuid(Guid identifier, IEnumerable<ActorIdentifier> actors, uint lockCode)
|
||||
=> ApplyDesign(_designManager.Designs.FirstOrDefault(x => x.Identifier == identifier), actors, DesignConverter.Version, lockCode);
|
||||
=> ApplyDesign(_designManager.Designs.ByIdentifier(identifier), actors, DesignConverter.Version, lockCode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public partial class GlamourerIpc
|
|||
private readonly EventProvider<StateChanged.Type, nint, Lazy<string>> _stateChangedProvider;
|
||||
private readonly EventProvider<bool> _gPoseChangedProvider;
|
||||
|
||||
private void OnStateChanged(StateChanged.Type type, StateChanged.Source source, ActorState state, ActorData actors, object? data = null)
|
||||
private void OnStateChanged(StateChanged.Type type, StateSource source, ActorState state, ActorData actors, object? data = null)
|
||||
{
|
||||
foreach (var actor in actors.Objects)
|
||||
_stateChangedProvider.Invoke(type, actor.Address, new Lazy<string>(() => _designConverter.ShareBase64(state)));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
|
|
@ -82,7 +83,7 @@ public partial class GlamourerIpc
|
|||
foreach (var id in actors)
|
||||
{
|
||||
if (_stateManager.TryGetValue(id, out var state))
|
||||
_stateManager.ResetState(state, StateChanged.Source.Ipc, lockCode);
|
||||
_stateManager.ResetState(state, StateSource.Ipc, lockCode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -55,7 +57,7 @@ public partial class GlamourerIpc
|
|||
if (!state.ModelData.IsHuman)
|
||||
return GlamourerErrorCode.ActorNotHuman;
|
||||
|
||||
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
|
||||
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key:key));
|
||||
return GlamourerErrorCode.Success;
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +84,7 @@ public partial class GlamourerIpc
|
|||
if (!state.ModelData.IsHuman)
|
||||
return GlamourerErrorCode.ActorNotHuman;
|
||||
|
||||
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
|
||||
_stateManager.ChangeEquip(state, slot, item, stainId, new ApplySettings(Source: StateSource.Ipc, Key: key));
|
||||
found = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
68
Glamourer/Automation/ApplicationType.cs
Normal file
68
Glamourer/Automation/ApplicationType.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
[Flags]
|
||||
public enum ApplicationType : byte
|
||||
{
|
||||
Armor = 0x01,
|
||||
Customizations = 0x02,
|
||||
Weapons = 0x04,
|
||||
GearCustomization = 0x08,
|
||||
Accessories = 0x10,
|
||||
|
||||
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
|
||||
}
|
||||
|
||||
public static class ApplicationTypeExtensions
|
||||
{
|
||||
public static readonly IReadOnlyList<(ApplicationType, string)> Types = new[]
|
||||
{
|
||||
(ApplicationType.Customizations,
|
||||
"Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."),
|
||||
(ApplicationType.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(ApplicationType.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(ApplicationType.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
|
||||
(ApplicationType.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
|
||||
};
|
||||
|
||||
public static (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat(
|
||||
this ApplicationType type, DesignBase? design)
|
||||
{
|
||||
var equipFlags = (type.HasFlag(ApplicationType.Weapons) ? WeaponFlags : 0)
|
||||
| (type.HasFlag(ApplicationType.Armor) ? ArmorFlags : 0)
|
||||
| (type.HasFlag(ApplicationType.Accessories) ? AccessoryFlags : 0)
|
||||
| (type.HasFlag(ApplicationType.GearCustomization) ? StainFlags : 0);
|
||||
var customizeFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeFlagExtensions.All : 0;
|
||||
var parameterFlags = type.HasFlag(ApplicationType.Customizations) ? CustomizeParameterExtensions.All : 0;
|
||||
var crestFlag = type.HasFlag(ApplicationType.GearCustomization) ? CrestExtensions.AllRelevant : 0;
|
||||
var metaFlag = (type.HasFlag(ApplicationType.Armor) ? MetaFlag.HatState | MetaFlag.VisorState : 0)
|
||||
| (type.HasFlag(ApplicationType.Weapons) ? MetaFlag.WeaponState : 0)
|
||||
| (type.HasFlag(ApplicationType.Customizations) ? MetaFlag.Wetness : 0);
|
||||
|
||||
if (design == null)
|
||||
return (equipFlags, customizeFlags, crestFlag, parameterFlags, metaFlag);
|
||||
|
||||
return (equipFlags & design!.ApplyEquip, customizeFlags & design.ApplyCustomize, crestFlag & design.ApplyCrest,
|
||||
parameterFlags & design.ApplyParameters, metaFlag & design.ApplyMeta);
|
||||
}
|
||||
|
||||
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
|
||||
public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet;
|
||||
public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger;
|
||||
|
||||
public const EquipFlag StainFlags = EquipFlag.MainhandStain
|
||||
| EquipFlag.OffhandStain
|
||||
| EquipFlag.HeadStain
|
||||
| EquipFlag.BodyStain
|
||||
| EquipFlag.HandsStain
|
||||
| EquipFlag.LegsStain
|
||||
| EquipFlag.FeetStain
|
||||
| EquipFlag.EarsStain
|
||||
| EquipFlag.NeckStain
|
||||
| EquipFlag.WristStain
|
||||
| EquipFlag.RFingerStain
|
||||
| EquipFlag.LFingerStain;
|
||||
}
|
||||
|
|
@ -12,22 +12,10 @@ public class AutoDesign
|
|||
{
|
||||
public const string RevertName = "Revert";
|
||||
|
||||
[Flags]
|
||||
public enum Type : byte
|
||||
{
|
||||
Armor = 0x01,
|
||||
Customizations = 0x02,
|
||||
Weapons = 0x04,
|
||||
GearCustomization = 0x08,
|
||||
Accessories = 0x10,
|
||||
|
||||
All = Armor | Accessories | Customizations | Weapons | GearCustomization,
|
||||
}
|
||||
|
||||
public Design? Design;
|
||||
public JobGroup Jobs;
|
||||
public Type ApplicationType;
|
||||
public short GearsetIndex = -1;
|
||||
public Design? Design;
|
||||
public JobGroup Jobs;
|
||||
public ApplicationType Type;
|
||||
public short GearsetIndex = -1;
|
||||
|
||||
public string Name(bool incognito)
|
||||
=> Revert ? RevertName : incognito ? Design!.Incognito : Design!.Name.Text;
|
||||
|
|
@ -41,10 +29,10 @@ public class AutoDesign
|
|||
public AutoDesign Clone()
|
||||
=> new()
|
||||
{
|
||||
Design = Design,
|
||||
ApplicationType = ApplicationType,
|
||||
Jobs = Jobs,
|
||||
GearsetIndex = GearsetIndex,
|
||||
Design = Design,
|
||||
Type = Type,
|
||||
Jobs = Jobs,
|
||||
GearsetIndex = GearsetIndex,
|
||||
};
|
||||
|
||||
public unsafe bool IsActive(Actor actor)
|
||||
|
|
@ -64,9 +52,9 @@ public class AutoDesign
|
|||
public JObject Serialize()
|
||||
=> new()
|
||||
{
|
||||
["Design"] = Design?.Identifier.ToString(),
|
||||
["ApplicationType"] = (uint)ApplicationType,
|
||||
["Conditions"] = CreateConditionObject(),
|
||||
["Design"] = Design?.Identifier.ToString(),
|
||||
["Type"] = (uint)Type,
|
||||
["Conditions"] = CreateConditionObject(),
|
||||
};
|
||||
|
||||
private JObject CreateConditionObject()
|
||||
|
|
@ -80,44 +68,6 @@ public class AutoDesign
|
|||
return ret;
|
||||
}
|
||||
|
||||
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, bool ApplyHat, bool ApplyVisor, bool
|
||||
ApplyWeapon, bool ApplyWet) ApplyWhat()
|
||||
{
|
||||
var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0)
|
||||
| (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0);
|
||||
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 0;
|
||||
var parameterFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeParameterExtensions.All : 0;
|
||||
var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0;
|
||||
|
||||
if (Revert)
|
||||
return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor),
|
||||
ApplicationType.HasFlag(Type.Armor),
|
||||
ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations));
|
||||
|
||||
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest,
|
||||
parameterFlags & Design.ApplyParameters,
|
||||
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(),
|
||||
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(),
|
||||
ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(),
|
||||
ApplicationType.HasFlag(Type.Customizations) && Design.DoApplyWetness());
|
||||
}
|
||||
|
||||
public const EquipFlag WeaponFlags = EquipFlag.Mainhand | EquipFlag.Offhand;
|
||||
public const EquipFlag ArmorFlags = EquipFlag.Head | EquipFlag.Body | EquipFlag.Hands | EquipFlag.Legs | EquipFlag.Feet;
|
||||
public const EquipFlag AccessoryFlags = EquipFlag.Ears | EquipFlag.Neck | EquipFlag.Wrist | EquipFlag.RFinger | EquipFlag.LFinger;
|
||||
|
||||
public const EquipFlag StainFlags = EquipFlag.MainhandStain
|
||||
| EquipFlag.OffhandStain
|
||||
| EquipFlag.HeadStain
|
||||
| EquipFlag.BodyStain
|
||||
| EquipFlag.HandsStain
|
||||
| EquipFlag.LegsStain
|
||||
| EquipFlag.FeetStain
|
||||
| EquipFlag.EarsStain
|
||||
| EquipFlag.NeckStain
|
||||
| EquipFlag.WristStain
|
||||
| EquipFlag.RFingerStain
|
||||
| EquipFlag.LFingerStain;
|
||||
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, CustomizeParameterFlag Parameters, MetaFlag Meta) ApplyWhat()
|
||||
=> Type.ApplyWhat(Design);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Unlocks;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -15,54 +13,41 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
public class AutoDesignApplier : IDisposable
|
||||
public sealed class AutoDesignApplier : IDisposable
|
||||
{
|
||||
private readonly Configuration _config;
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly StateManager _state;
|
||||
private readonly JobService _jobs;
|
||||
private readonly EquippedGearset _equippedGearset;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly WeaponLoading _weapons;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly Configuration _config;
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly StateManager _state;
|
||||
private readonly JobService _jobs;
|
||||
private readonly EquippedGearset _equippedGearset;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly WeaponLoading _weapons;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly DesignMerger _designMerger;
|
||||
private readonly IClientState _clientState;
|
||||
|
||||
private ActorState? _jobChangeState;
|
||||
private readonly Dictionary<FullEquipType, (EquipItem, StateChanged.Source)> _jobChangeMainhand = [];
|
||||
private readonly Dictionary<FullEquipType, (EquipItem, StateChanged.Source)> _jobChangeOffhand = [];
|
||||
private readonly JobChangeState _jobChangeState;
|
||||
|
||||
private void ResetJobChange()
|
||||
{
|
||||
_jobChangeState = null;
|
||||
_jobChangeMainhand.Clear();
|
||||
_jobChangeOffhand.Clear();
|
||||
}
|
||||
|
||||
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs,
|
||||
CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks,
|
||||
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, ActorManager actors,
|
||||
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
|
||||
EquippedGearset equippedGearset)
|
||||
EquippedGearset equippedGearset, DesignMerger designMerger, JobChangeState jobChangeState)
|
||||
{
|
||||
_config = config;
|
||||
_manager = manager;
|
||||
_state = state;
|
||||
_jobs = jobs;
|
||||
_customizations = customizations;
|
||||
_actors = actors;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
_event = @event;
|
||||
_objects = objects;
|
||||
_weapons = weapons;
|
||||
_humans = humans;
|
||||
_clientState = clientState;
|
||||
_equippedGearset = equippedGearset;
|
||||
_jobs.JobChanged += OnJobChange;
|
||||
_config = config;
|
||||
_manager = manager;
|
||||
_state = state;
|
||||
_jobs = jobs;
|
||||
_actors = actors;
|
||||
_event = @event;
|
||||
_objects = objects;
|
||||
_weapons = weapons;
|
||||
_humans = humans;
|
||||
_clientState = clientState;
|
||||
_equippedGearset = equippedGearset;
|
||||
_designMerger = designMerger;
|
||||
_jobChangeState = jobChangeState;
|
||||
_jobs.JobChanged += OnJobChange;
|
||||
_event.Subscribe(OnAutomationChange, AutomationChanged.Priority.AutoDesignApplier);
|
||||
_weapons.Subscribe(OnWeaponLoading, WeaponLoading.Priority.AutoDesignApplier);
|
||||
_equippedGearset.Subscribe(OnEquippedGearset, EquippedGearset.Priority.AutoDesignApplier);
|
||||
|
|
@ -78,45 +63,46 @@ public class AutoDesignApplier : IDisposable
|
|||
|
||||
private void OnWeaponLoading(Actor actor, EquipSlot slot, ref CharacterWeapon weapon)
|
||||
{
|
||||
if (_jobChangeState == null || !_config.EnableAutoDesigns)
|
||||
if (!_jobChangeState.HasState || !_config.EnableAutoDesigns)
|
||||
return;
|
||||
|
||||
var id = actor.GetIdentifier(_actors);
|
||||
if (id == _jobChangeState.Identifier)
|
||||
{
|
||||
var current = _jobChangeState.BaseData.Item(slot);
|
||||
var state = _jobChangeState.State!;
|
||||
var current = state.BaseData.Item(slot);
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
{
|
||||
if (_jobChangeMainhand.TryGetValue(current.Type, out var data))
|
||||
if (_jobChangeState.TryGetValue(current.Type, out var data))
|
||||
{
|
||||
Glamourer.Log.Verbose(
|
||||
$"Changing Mainhand from {_jobChangeState.ModelData.Weapon(EquipSlot.MainHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||
_state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, data.Item2);
|
||||
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.MainHand);
|
||||
$"Changing Mainhand from {state.ModelData.Weapon(EquipSlot.MainHand)} | {state.BaseData.Weapon(EquipSlot.MainHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||
_state.ChangeItem(_jobChangeState, EquipSlot.MainHand, data.Item1, new ApplySettings(Source: data.Item2));
|
||||
weapon = state.ModelData.Weapon(EquipSlot.MainHand);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case EquipSlot.OffHand when current.Type == _jobChangeState.BaseData.MainhandType.Offhand():
|
||||
case EquipSlot.OffHand when current.Type == state.BaseData.MainhandType.Offhand():
|
||||
{
|
||||
if (_jobChangeOffhand.TryGetValue(current.Type, out var data))
|
||||
if (_jobChangeState.TryGetValue(current.Type, out var data))
|
||||
{
|
||||
Glamourer.Log.Verbose(
|
||||
$"Changing Offhand from {_jobChangeState.ModelData.Weapon(EquipSlot.OffHand)} | {_jobChangeState.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||
_state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, data.Item2);
|
||||
weapon = _jobChangeState.ModelData.Weapon(EquipSlot.OffHand);
|
||||
$"Changing Offhand from {state.ModelData.Weapon(EquipSlot.OffHand)} | {state.BaseData.Weapon(EquipSlot.OffHand)} to {data.Item1} for 0x{actor.Address:X}.");
|
||||
_state.ChangeItem(_jobChangeState, EquipSlot.OffHand, data.Item1, new ApplySettings(Source: data.Item2));
|
||||
weapon = state.ModelData.Weapon(EquipSlot.OffHand);
|
||||
}
|
||||
|
||||
ResetJobChange();
|
||||
_jobChangeState.Reset();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ResetJobChange();
|
||||
_jobChangeState.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +120,7 @@ public class AutoDesignApplier : IDisposable
|
|||
break;
|
||||
case AutomationChanged.Type.ChangeIdentifier when set.Enabled:
|
||||
// Remove fixed state from the old identifiers assigned and the old enabled set, if any.
|
||||
var (oldIds, _, oldSet) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
|
||||
var (oldIds, _, _) = ((ActorIdentifier[], ActorIdentifier, AutoDesignSet?))bonusData!;
|
||||
RemoveOld(oldIds);
|
||||
ApplyNew(set); // Does not need to disable oldSet because same identifiers.
|
||||
break;
|
||||
|
|
@ -182,7 +168,7 @@ public class AutoDesignApplier : IDisposable
|
|||
}
|
||||
else if (_state.TryGetValue(id, out var state))
|
||||
{
|
||||
state.RemoveFixedDesignSources();
|
||||
state.Sources.RemoveFixedDesignSources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -196,9 +182,9 @@ public class AutoDesignApplier : IDisposable
|
|||
{
|
||||
if (id.Type is IdentifierType.Player && id.HomeWorld == WorldId.AnyWorld)
|
||||
foreach (var state in _state.Where(kvp => kvp.Key.PlayerName == id.PlayerName).Select(kvp => kvp.Value))
|
||||
state.RemoveFixedDesignSources();
|
||||
state.Sources.RemoveFixedDesignSources();
|
||||
else if (_state.TryGetValue(id, out var state))
|
||||
state.RemoveFixedDesignSources();
|
||||
state.Sources.RemoveFixedDesignSources();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -255,56 +241,31 @@ public class AutoDesignApplier : IDisposable
|
|||
else if (!GetPlayerSet(identifier, out set!))
|
||||
{
|
||||
if (state.UpdateTerritory(_clientState.TerritoryType) && _config.RevertManualChangesOnZoneChange)
|
||||
_state.ResetState(state, StateChanged.Source.Game);
|
||||
_state.ResetState(state, StateSource.Game);
|
||||
return true;
|
||||
}
|
||||
|
||||
var respectManual = !state.UpdateTerritory(_clientState.TerritoryType) || !_config.RevertManualChangesOnZoneChange;
|
||||
if (!respectManual)
|
||||
_state.ResetState(state, StateChanged.Source.Game);
|
||||
_state.ResetState(state, StateSource.Game);
|
||||
Reduce(actor, state, set, respectManual, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
private unsafe void Reduce(Actor actor, ActorState state, AutoDesignSet set, bool respectManual, bool fromJobChange)
|
||||
{
|
||||
EquipFlag totalEquipFlags = 0;
|
||||
CustomizeFlag totalCustomizeFlags = 0;
|
||||
CrestFlag totalCrestFlags = 0;
|
||||
CustomizeParameterFlag totalParameterFlags = 0;
|
||||
byte totalMetaFlags = 0;
|
||||
if (set.BaseState == AutoDesignSet.Base.Game)
|
||||
_state.ResetStateFixed(state, respectManual);
|
||||
else if (!respectManual)
|
||||
state.RemoveFixedDesignSources();
|
||||
state.Sources.RemoveFixedDesignSources();
|
||||
|
||||
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
||||
return;
|
||||
|
||||
foreach (var design in set.Designs)
|
||||
{
|
||||
if (!design.IsActive(actor))
|
||||
continue;
|
||||
|
||||
if (design.ApplicationType is 0)
|
||||
continue;
|
||||
|
||||
ref readonly var data = ref design.GetDesignData(state);
|
||||
var source = design.Revert ? StateChanged.Source.Game : StateChanged.Source.Fixed;
|
||||
|
||||
if (!data.IsHuman)
|
||||
continue;
|
||||
|
||||
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
|
||||
ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source);
|
||||
ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source);
|
||||
ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange);
|
||||
ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source);
|
||||
ReduceParameters(state, data, parameterFlags, ref totalParameterFlags, respectManual, source);
|
||||
}
|
||||
|
||||
if (totalCustomizeFlags != 0)
|
||||
state.ModelData.ModelId = 0;
|
||||
var mergedDesign = _designMerger.Merge(
|
||||
set.Designs.Where(d => d.IsActive(actor)).SelectMany(d => d.Design?.AllLinks ?? [(d.Design, d.Type)]),
|
||||
state.ModelData, true, false);
|
||||
_state.ApplyDesign(state, mergedDesign, new ApplySettings(0, StateSource.Fixed, respectManual, fromJobChange, false));
|
||||
}
|
||||
|
||||
/// <summary> Get world-specific first and all-world afterward. </summary>
|
||||
|
|
@ -330,221 +291,6 @@ public class AutoDesignApplier : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private void ReduceCrests(ActorState state, in DesignData design, CrestFlag crestFlags, ref CrestFlag totalCrestFlags, bool respectManual,
|
||||
StateChanged.Source source)
|
||||
{
|
||||
crestFlags &= ~totalCrestFlags;
|
||||
if (crestFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
if (!crestFlags.HasFlag(slot))
|
||||
continue;
|
||||
|
||||
if (!respectManual || state[slot] is not StateChanged.Source.Manual)
|
||||
_state.ChangeCrest(state, slot, design.Crest(slot), source);
|
||||
totalCrestFlags |= slot;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceParameters(ActorState state, in DesignData design, CustomizeParameterFlag parameterFlags,
|
||||
ref CustomizeParameterFlag totalParameterFlags, bool respectManual, StateChanged.Source source)
|
||||
{
|
||||
parameterFlags &= ~totalParameterFlags;
|
||||
if (parameterFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
{
|
||||
if (!parameterFlags.HasFlag(flag))
|
||||
continue;
|
||||
|
||||
if (!respectManual || state[flag] is not StateChanged.Source.Manual and not StateChanged.Source.Pending)
|
||||
_state.ChangeCustomizeParameter(state, flag, design.Parameters[flag], source);
|
||||
totalParameterFlags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual,
|
||||
StateChanged.Source source, bool fromJobChange)
|
||||
{
|
||||
equipFlags &= ~totalEquipFlags;
|
||||
if (equipFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var flag = slot.ToFlag();
|
||||
if (equipFlags.HasFlag(flag))
|
||||
{
|
||||
var item = design.Item(slot);
|
||||
if (!_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _))
|
||||
{
|
||||
if (!respectManual || state[slot, false] is not StateChanged.Source.Manual)
|
||||
_state.ChangeItem(state, slot, item, source);
|
||||
totalEquipFlags |= flag;
|
||||
}
|
||||
}
|
||||
|
||||
var stainFlag = slot.ToStainFlag();
|
||||
if (equipFlags.HasFlag(stainFlag))
|
||||
{
|
||||
if (!respectManual || state[slot, true] is not StateChanged.Source.Manual)
|
||||
_state.ChangeStain(state, slot, design.Stain(slot), source);
|
||||
totalEquipFlags |= stainFlag;
|
||||
}
|
||||
}
|
||||
|
||||
if (equipFlags.HasFlag(EquipFlag.Mainhand))
|
||||
{
|
||||
var item = design.Item(EquipSlot.MainHand);
|
||||
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _);
|
||||
var checkState = !respectManual || state[EquipSlot.MainHand, false] is not StateChanged.Source.Manual;
|
||||
if (checkUnlock && checkState)
|
||||
{
|
||||
if (fromJobChange)
|
||||
{
|
||||
_jobChangeMainhand.TryAdd(item.Type, (item, source));
|
||||
_jobChangeState = state;
|
||||
}
|
||||
else if (state.ModelData.Item(EquipSlot.MainHand).Type == item.Type)
|
||||
{
|
||||
_state.ChangeItem(state, EquipSlot.MainHand, item, source);
|
||||
totalEquipFlags |= EquipFlag.Mainhand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (equipFlags.HasFlag(EquipFlag.Offhand))
|
||||
{
|
||||
var item = design.Item(EquipSlot.OffHand);
|
||||
var checkUnlock = !_config.UnlockedItemMode || _itemUnlocks.IsUnlocked(item.Id, out _);
|
||||
var checkState = !respectManual || state[EquipSlot.OffHand, false] is not StateChanged.Source.Manual;
|
||||
if (checkUnlock && checkState)
|
||||
{
|
||||
if (fromJobChange)
|
||||
{
|
||||
_jobChangeOffhand.TryAdd(item.Type, (item, source));
|
||||
_jobChangeState = state;
|
||||
}
|
||||
else if (state.ModelData.Item(EquipSlot.OffHand).Type == item.Type)
|
||||
{
|
||||
_state.ChangeItem(state, EquipSlot.OffHand, item, source);
|
||||
totalEquipFlags |= EquipFlag.Offhand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (equipFlags.HasFlag(EquipFlag.MainhandStain))
|
||||
{
|
||||
if (!respectManual || state[EquipSlot.MainHand, true] is not StateChanged.Source.Manual)
|
||||
_state.ChangeStain(state, EquipSlot.MainHand, design.Stain(EquipSlot.MainHand), source);
|
||||
totalEquipFlags |= EquipFlag.MainhandStain;
|
||||
}
|
||||
|
||||
if (equipFlags.HasFlag(EquipFlag.OffhandStain))
|
||||
{
|
||||
if (!respectManual || state[EquipSlot.OffHand, true] is not StateChanged.Source.Manual)
|
||||
_state.ChangeStain(state, EquipSlot.OffHand, design.Stain(EquipSlot.OffHand), source);
|
||||
totalEquipFlags |= EquipFlag.OffhandStain;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceCustomize(ActorState state, in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag totalCustomizeFlags,
|
||||
bool respectManual, StateChanged.Source source)
|
||||
{
|
||||
customizeFlags &= ~totalCustomizeFlags;
|
||||
if (customizeFlags == 0)
|
||||
return;
|
||||
|
||||
var customize = state.ModelData.Customize;
|
||||
CustomizeFlag fixFlags = 0;
|
||||
|
||||
// Skip anything not human.
|
||||
if (!state.ModelData.IsHuman || !design.IsHuman)
|
||||
return;
|
||||
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Clan))
|
||||
{
|
||||
if (!respectManual || state[CustomizeIndex.Clan] is not StateChanged.Source.Manual)
|
||||
fixFlags |= _customizations.ChangeClan(ref customize, design.Customize.Clan);
|
||||
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
|
||||
totalCustomizeFlags |= CustomizeFlag.Clan | CustomizeFlag.Race;
|
||||
}
|
||||
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Gender))
|
||||
{
|
||||
if (!respectManual || state[CustomizeIndex.Gender] is not StateChanged.Source.Manual)
|
||||
fixFlags |= _customizations.ChangeGender(ref customize, design.Customize.Gender);
|
||||
customizeFlags &= ~CustomizeFlag.Gender;
|
||||
totalCustomizeFlags |= CustomizeFlag.Gender;
|
||||
}
|
||||
|
||||
if (fixFlags != 0)
|
||||
_state.ChangeCustomize(state, customize, fixFlags, source);
|
||||
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Face))
|
||||
{
|
||||
if (!respectManual || state[CustomizeIndex.Face] is not StateChanged.Source.Manual)
|
||||
_state.ChangeCustomize(state, CustomizeIndex.Face, design.Customize.Face, source);
|
||||
customizeFlags &= ~CustomizeFlag.Face;
|
||||
totalCustomizeFlags |= CustomizeFlag.Face;
|
||||
}
|
||||
|
||||
var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
|
||||
var face = state.ModelData.Customize.Face;
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var flag = index.ToFlag();
|
||||
if (!customizeFlags.HasFlag(flag))
|
||||
continue;
|
||||
|
||||
var value = design.Customize[index];
|
||||
if (CustomizeService.IsCustomizationValid(set, face, index, value, out var data))
|
||||
{
|
||||
if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _))
|
||||
continue;
|
||||
|
||||
if (!respectManual || state[index] is not StateChanged.Source.Manual)
|
||||
_state.ChangeCustomize(state, index, value, source);
|
||||
totalCustomizeFlags |= flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceMeta(ActorState state, in DesignData design, bool applyHat, bool applyVisor, bool applyWeapon, bool applyWet,
|
||||
ref byte totalMetaFlags, bool respectManual, StateChanged.Source source)
|
||||
{
|
||||
if (applyHat && (totalMetaFlags & 0x01) == 0)
|
||||
{
|
||||
if (!respectManual || state[ActorState.MetaIndex.HatState] is not StateChanged.Source.Manual)
|
||||
_state.ChangeHatState(state, design.IsHatVisible(), source);
|
||||
totalMetaFlags |= 0x01;
|
||||
}
|
||||
|
||||
if (applyVisor && (totalMetaFlags & 0x02) == 0)
|
||||
{
|
||||
if (!respectManual || state[ActorState.MetaIndex.VisorState] is not StateChanged.Source.Manual)
|
||||
_state.ChangeVisorState(state, design.IsVisorToggled(), source);
|
||||
totalMetaFlags |= 0x02;
|
||||
}
|
||||
|
||||
if (applyWeapon && (totalMetaFlags & 0x04) == 0)
|
||||
{
|
||||
if (!respectManual || state[ActorState.MetaIndex.WeaponState] is not StateChanged.Source.Manual)
|
||||
_state.ChangeWeaponState(state, design.IsWeaponVisible(), source);
|
||||
totalMetaFlags |= 0x04;
|
||||
}
|
||||
|
||||
if (applyWet && (totalMetaFlags & 0x08) == 0)
|
||||
{
|
||||
if (!respectManual || state[ActorState.MetaIndex.Wetness] is not StateChanged.Source.Manual)
|
||||
_state.ChangeWetness(state, design.IsWet(), source);
|
||||
totalMetaFlags |= 0x08;
|
||||
}
|
||||
}
|
||||
|
||||
internal static int NewGearsetId = -1;
|
||||
|
||||
private void OnEquippedGearset(string name, int id, int prior, byte _, byte job)
|
||||
|
|
|
|||
|
|
@ -231,9 +231,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
{
|
||||
var newDesign = new AutoDesign()
|
||||
{
|
||||
Design = design,
|
||||
ApplicationType = AutoDesign.Type.All,
|
||||
Jobs = _jobs.JobGroups[1],
|
||||
Design = design,
|
||||
Type = ApplicationType.All,
|
||||
Jobs = _jobs.JobGroups[1],
|
||||
};
|
||||
set.Designs.Add(newDesign);
|
||||
Save();
|
||||
|
|
@ -328,21 +328,21 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
_event.Invoke(AutomationChanged.Type.ChangedConditions, set, (which, old, index));
|
||||
}
|
||||
|
||||
public void ChangeApplicationType(AutoDesignSet set, int which, AutoDesign.Type type)
|
||||
public void ChangeApplicationType(AutoDesignSet set, int which, ApplicationType applicationType)
|
||||
{
|
||||
if (which >= set.Designs.Count || which < 0)
|
||||
return;
|
||||
|
||||
type &= AutoDesign.Type.All;
|
||||
applicationType &= ApplicationType.All;
|
||||
var design = set.Designs[which];
|
||||
if (design.ApplicationType == type)
|
||||
if (design.Type == applicationType)
|
||||
return;
|
||||
|
||||
var old = design.ApplicationType;
|
||||
design.ApplicationType = type;
|
||||
var old = design.Type;
|
||||
design.Type = applicationType;
|
||||
Save();
|
||||
Glamourer.Log.Debug($"Changed application type from {old} to {type} for associated design {which + 1} in design set.");
|
||||
_event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, type));
|
||||
Glamourer.Log.Debug($"Changed application type from {old} to {applicationType} for associated design {which + 1} in design set.");
|
||||
_event.Invoke(AutomationChanged.Type.ChangedType, set, (which, old, applicationType));
|
||||
}
|
||||
|
||||
public string ToFilename(FilenameService fileNames)
|
||||
|
|
@ -480,8 +480,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
return null;
|
||||
}
|
||||
|
||||
design = _designs.Designs.FirstOrDefault(d => d.Identifier == guid);
|
||||
if (design == null)
|
||||
if (!_designs.Designs.TryGetValue(guid, out design))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: The specified design {guid} does not exist.",
|
||||
|
|
@ -490,12 +489,13 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
}
|
||||
}
|
||||
|
||||
var applicationType = (AutoDesign.Type)(jObj["ApplicationType"]?.ToObject<uint>() ?? 0);
|
||||
// ApplicationType is a migration from an older property name.
|
||||
var applicationType = (ApplicationType)(jObj["Type"]?.ToObject<uint>() ?? jObj["ApplicationType"]?.ToObject<uint>() ?? 0);
|
||||
|
||||
var ret = new AutoDesign()
|
||||
var ret = new AutoDesign
|
||||
{
|
||||
Design = design,
|
||||
ApplicationType = applicationType & AutoDesign.Type.All,
|
||||
Design = design,
|
||||
Type = applicationType & ApplicationType.All,
|
||||
};
|
||||
return ParseConditions(setName, jObj, ret) ? ret : null;
|
||||
}
|
||||
|
|
@ -550,7 +550,24 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
private ActorIdentifier[] GetGroup(ActorIdentifier identifier)
|
||||
{
|
||||
if (!identifier.IsValid)
|
||||
return Array.Empty<ActorIdentifier>();
|
||||
return [];
|
||||
|
||||
return identifier.Type switch
|
||||
{
|
||||
IdentifierType.Player =>
|
||||
[
|
||||
identifier.CreatePermanent(),
|
||||
],
|
||||
IdentifierType.Retainer =>
|
||||
[
|
||||
_actors.CreateRetainer(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
||||
],
|
||||
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||
_ => [],
|
||||
};
|
||||
|
||||
static ActorIdentifier[] CreateNpcs(ActorManager manager, ActorIdentifier identifier)
|
||||
{
|
||||
|
|
@ -566,23 +583,6 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
identifier.Kind,
|
||||
kvp.Key)).ToArray();
|
||||
}
|
||||
|
||||
return identifier.Type switch
|
||||
{
|
||||
IdentifierType.Player => new[]
|
||||
{
|
||||
identifier.CreatePermanent(),
|
||||
},
|
||||
IdentifierType.Retainer => new[]
|
||||
{
|
||||
_actors.CreateRetainer(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
||||
},
|
||||
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||
_ => Array.Empty<ActorIdentifier>(),
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDesignChange(DesignChanged.Type type, Design design, object? data)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ using Penumbra.GameData.Actors;
|
|||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
public class AutoDesignSet
|
||||
public class AutoDesignSet(string name, ActorIdentifier[] identifiers, List<AutoDesign> designs)
|
||||
{
|
||||
public readonly List<AutoDesign> Designs;
|
||||
public readonly List<AutoDesign> Designs = designs;
|
||||
|
||||
public string Name;
|
||||
public ActorIdentifier[] Identifiers;
|
||||
public string Name = name;
|
||||
public ActorIdentifier[] Identifiers = identifiers;
|
||||
public bool Enabled;
|
||||
public Base BaseState = Base.Current;
|
||||
|
||||
|
|
@ -32,13 +32,6 @@ public class AutoDesignSet
|
|||
: this(name, identifiers, new List<AutoDesign>())
|
||||
{ }
|
||||
|
||||
public AutoDesignSet(string name, ActorIdentifier[] identifiers, List<AutoDesign> designs)
|
||||
{
|
||||
Name = name;
|
||||
Identifiers = identifiers;
|
||||
Designs = designs;
|
||||
}
|
||||
|
||||
public enum Base : byte
|
||||
{
|
||||
Current,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public class FixedDesignMigrator(JobService jobs)
|
|||
|
||||
autoManager.AddDesign(set, leaf.Value);
|
||||
autoManager.ChangeJobCondition(set, set.Designs.Count - 1, design.Item2);
|
||||
autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? AutoDesign.Type.All : 0);
|
||||
autoManager.ChangeApplicationType(set, set.Designs.Count - 1, design.Item3 ? ApplicationType.All : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public class Configuration : IPluginConfiguration, ISavable
|
|||
public bool UseFloatForColors { get; set; } = true;
|
||||
public bool UseRgbForColors { get; set; } = true;
|
||||
public bool ShowColorConfig { get; set; } = true;
|
||||
public bool ChangeEntireItem { get; set; } = false;
|
||||
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
|
||||
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -35,14 +37,18 @@ public sealed class Design : DesignBase, ISavable
|
|||
public DateTimeOffset LastEdit { get; internal set; }
|
||||
public LowerString Name { get; internal set; } = LowerString.Empty;
|
||||
public string Description { get; internal set; } = string.Empty;
|
||||
public string[] Tags { get; internal set; } = Array.Empty<string>();
|
||||
public string[] Tags { get; internal set; } = [];
|
||||
public int Index { get; internal set; }
|
||||
public string Color { get; internal set; } = string.Empty;
|
||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = new();
|
||||
public SortedList<Mod, ModSettings> AssociatedMods { get; private set; } = [];
|
||||
public LinkContainer Links { get; private set; } = [];
|
||||
|
||||
public string Incognito
|
||||
=> Identifier.ToString()[..8];
|
||||
|
||||
public IEnumerable<(DesignBase? Design, ApplicationType Flags)> AllLinks
|
||||
=> LinkContainer.GetAllLinks(this).Select(t => ((DesignBase?)t.Link.Link, t.Link.Type));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialization
|
||||
|
|
@ -64,6 +70,7 @@ public sealed class Design : DesignBase, ISavable
|
|||
["Customize"] = SerializeCustomize(),
|
||||
["Parameters"] = SerializeParameters(),
|
||||
["Mods"] = SerializeMods(),
|
||||
["Links"] = Links.Serialize(),
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -95,24 +102,18 @@ public sealed class Design : DesignBase, ISavable
|
|||
|
||||
#region Deserialization
|
||||
|
||||
public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json)
|
||||
public static Design LoadDesign(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
|
||||
{
|
||||
var version = json["FileVersion"]?.ToObject<int>() ?? 0;
|
||||
return version switch
|
||||
{
|
||||
FileVersion => LoadDesignV1(customizations, items, json),
|
||||
FileVersion => LoadDesignV1(customizations, items, linkLoader, json),
|
||||
_ => throw new Exception("The design to be loaded has no valid Version."),
|
||||
};
|
||||
}
|
||||
|
||||
private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json)
|
||||
private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, DesignLinkLoader linkLoader, JObject json)
|
||||
{
|
||||
static string[] ParseTags(JObject json)
|
||||
{
|
||||
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
|
||||
return tags.OrderBy(t => t).Distinct().ToArray();
|
||||
}
|
||||
|
||||
var creationDate = json["CreationDate"]?.ToObject<DateTimeOffset>() ?? throw new ArgumentNullException("CreationDate");
|
||||
|
||||
var design = new Design(customizations, items)
|
||||
|
|
@ -131,8 +132,15 @@ public sealed class Design : DesignBase, ISavable
|
|||
LoadEquip(items, json["Equipment"], design, design.Name, true);
|
||||
LoadMods(json["Mods"], design);
|
||||
LoadParameters(json["Parameters"], design, design.Name);
|
||||
LoadLinks(linkLoader, json["Links"], design);
|
||||
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
|
||||
return design;
|
||||
|
||||
static string[] ParseTags(JObject json)
|
||||
{
|
||||
var tags = json["Tags"]?.ToObject<string[]>() ?? Array.Empty<string>();
|
||||
return tags.OrderBy(t => t).Distinct().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static void LoadMods(JToken? mods, Design design)
|
||||
|
|
@ -161,6 +169,29 @@ public sealed class Design : DesignBase, ISavable
|
|||
}
|
||||
}
|
||||
|
||||
private static void LoadLinks(DesignLinkLoader linkLoader, JToken? links, Design design)
|
||||
{
|
||||
if (links is not JObject obj)
|
||||
return;
|
||||
|
||||
Parse(obj["Before"] as JArray, LinkOrder.Before);
|
||||
Parse(obj["After"] as JArray, LinkOrder.After);
|
||||
return;
|
||||
|
||||
void Parse(JArray? array, LinkOrder order)
|
||||
{
|
||||
if (array == null)
|
||||
return;
|
||||
|
||||
foreach (var obj in array.OfType<JObject>())
|
||||
{
|
||||
var identifier = obj["Design"]?.ToObject<Guid>() ?? throw new ArgumentNullException("Design");
|
||||
var type = (ApplicationType)(obj["Type"]?.ToObject<uint>() ?? 0);
|
||||
linkLoader.AddObject(design, new LinkData(identifier, type, order));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ISavable
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -34,7 +35,7 @@ public class DesignBase
|
|||
_designData = designData;
|
||||
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||
ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
_designFlags = 0;
|
||||
ApplyMeta = 0;
|
||||
CustomizeSet = SetCustomizationSet(customize);
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ public class DesignBase
|
|||
ApplyCustomize = clone.ApplyCustomizeRaw;
|
||||
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
||||
ApplyParameters = clone.ApplyParameters & CustomizeParameterExtensions.All;
|
||||
_designFlags = clone._designFlags & (DesignFlags)0x0F;
|
||||
ApplyMeta = clone.ApplyMeta & MetaExtensions.All;
|
||||
}
|
||||
|
||||
/// <summary> Ensure that the customization set is updated when the design data changes. </summary>
|
||||
|
|
@ -57,16 +58,6 @@ public class DesignBase
|
|||
|
||||
#region Application Data
|
||||
|
||||
[Flags]
|
||||
private enum DesignFlags : byte
|
||||
{
|
||||
ApplyHatVisible = 0x01,
|
||||
ApplyVisorState = 0x02,
|
||||
ApplyWeaponVisible = 0x04,
|
||||
ApplyWetness = 0x08,
|
||||
WriteProtected = 0x10,
|
||||
}
|
||||
|
||||
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
|
||||
public CustomizeSet CustomizeSet { get; private set; }
|
||||
|
||||
|
|
@ -84,9 +75,10 @@ public class DesignBase
|
|||
internal CustomizeFlag ApplyCustomizeRaw
|
||||
=> _applyCustomize;
|
||||
|
||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
||||
internal EquipFlag ApplyEquip = EquipFlagExtensions.All;
|
||||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
internal MetaFlag ApplyMeta = MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||
private bool _writeProtected;
|
||||
|
||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||
{
|
||||
|
|
@ -98,69 +90,30 @@ public class DesignBase
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool DoApplyHatVisible()
|
||||
=> _designFlags.HasFlag(DesignFlags.ApplyHatVisible);
|
||||
|
||||
public bool DoApplyVisorToggle()
|
||||
=> _designFlags.HasFlag(DesignFlags.ApplyVisorState);
|
||||
|
||||
public bool DoApplyWeaponVisible()
|
||||
=> _designFlags.HasFlag(DesignFlags.ApplyWeaponVisible);
|
||||
|
||||
public bool DoApplyWetness()
|
||||
=> _designFlags.HasFlag(DesignFlags.ApplyWetness);
|
||||
public bool DoApplyMeta(MetaIndex index)
|
||||
=> ApplyMeta.HasFlag(index.ToFlag());
|
||||
|
||||
public bool WriteProtected()
|
||||
=> _designFlags.HasFlag(DesignFlags.WriteProtected);
|
||||
=> _writeProtected;
|
||||
|
||||
public bool SetApplyHatVisible(bool value)
|
||||
public bool SetApplyMeta(MetaIndex index, bool value)
|
||||
{
|
||||
var newFlag = value ? _designFlags | DesignFlags.ApplyHatVisible : _designFlags & ~DesignFlags.ApplyHatVisible;
|
||||
if (newFlag == _designFlags)
|
||||
var newFlag = value ? ApplyMeta | index.ToFlag() : ApplyMeta & ~index.ToFlag();
|
||||
if (newFlag == ApplyMeta)
|
||||
return false;
|
||||
|
||||
_designFlags = newFlag;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetApplyVisorToggle(bool value)
|
||||
{
|
||||
var newFlag = value ? _designFlags | DesignFlags.ApplyVisorState : _designFlags & ~DesignFlags.ApplyVisorState;
|
||||
if (newFlag == _designFlags)
|
||||
return false;
|
||||
|
||||
_designFlags = newFlag;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetApplyWeaponVisible(bool value)
|
||||
{
|
||||
var newFlag = value ? _designFlags | DesignFlags.ApplyWeaponVisible : _designFlags & ~DesignFlags.ApplyWeaponVisible;
|
||||
if (newFlag == _designFlags)
|
||||
return false;
|
||||
|
||||
_designFlags = newFlag;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetApplyWetness(bool value)
|
||||
{
|
||||
var newFlag = value ? _designFlags | DesignFlags.ApplyWetness : _designFlags & ~DesignFlags.ApplyWetness;
|
||||
if (newFlag == _designFlags)
|
||||
return false;
|
||||
|
||||
_designFlags = newFlag;
|
||||
ApplyMeta = newFlag;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetWriteProtected(bool value)
|
||||
{
|
||||
var newFlag = value ? _designFlags | DesignFlags.WriteProtected : _designFlags & ~DesignFlags.WriteProtected;
|
||||
if (newFlag == _designFlags)
|
||||
if (value == _writeProtected)
|
||||
return false;
|
||||
|
||||
_designFlags = newFlag;
|
||||
_writeProtected = value;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public bool DoApplyEquip(EquipSlot slot)
|
||||
|
|
@ -298,9 +251,9 @@ public class DesignBase
|
|||
ret[slot.ToString()] = Serialize(item.Id, stain, crest, DoApplyEquip(slot), DoApplyStain(slot), DoApplyCrest(crestSlot));
|
||||
}
|
||||
|
||||
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyHatVisible()).ToJObject("Show", "Apply");
|
||||
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyVisorToggle()).ToJObject("IsToggled", "Apply");
|
||||
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyWeaponVisible()).ToJObject("Show", "Apply");
|
||||
ret["Hat"] = new QuadBool(_designData.IsHatVisible(), DoApplyMeta(MetaIndex.HatState)).ToJObject("Show", "Apply");
|
||||
ret["Visor"] = new QuadBool(_designData.IsVisorToggled(), DoApplyMeta(MetaIndex.VisorState)).ToJObject("IsToggled", "Apply");
|
||||
ret["Weapon"] = new QuadBool(_designData.IsWeaponVisible(), DoApplyMeta(MetaIndex.WeaponState)).ToJObject("Show", "Apply");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -344,7 +297,7 @@ public class DesignBase
|
|||
ret["Wetness"] = new JObject()
|
||||
{
|
||||
["Value"] = _designData.IsWet(),
|
||||
["Apply"] = DoApplyWetness(),
|
||||
["Apply"] = DoApplyMeta(MetaIndex.Wetness),
|
||||
};
|
||||
|
||||
return ret;
|
||||
|
|
@ -478,7 +431,7 @@ public class DesignBase
|
|||
// Load the token and set application.
|
||||
bool TryGetToken(CustomizeParameterFlag flag, [NotNullWhen(true)] out JToken? token)
|
||||
{
|
||||
token = parameters![flag.ToString()];
|
||||
token = parameters[flag.ToString()];
|
||||
if (token != null)
|
||||
{
|
||||
var apply = token["Apply"]?.ToObject<bool>() ?? false;
|
||||
|
|
@ -493,8 +446,8 @@ public class DesignBase
|
|||
|
||||
void MigrateLipOpacity()
|
||||
{
|
||||
var token = parameters!["LipOpacity"]?["Percentage"]?.ToObject<float>();
|
||||
var actualToken = parameters![CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"];
|
||||
var token = parameters["LipOpacity"]?["Percentage"]?.ToObject<float>();
|
||||
var actualToken = parameters[CustomizeParameterFlag.LipDiffuse.ToString()]?["Alpha"];
|
||||
if (token != null && actualToken == null)
|
||||
design.GetDesignDataRef().Parameters.LipDiffuse.W = token.Value;
|
||||
}
|
||||
|
|
@ -575,15 +528,15 @@ public class DesignBase
|
|||
design.SetApplyCrest(CrestFlag.OffHand, applyCrestOff);
|
||||
}
|
||||
var metaValue = QuadBool.FromJObject(equip["Hat"], "Show", "Apply", QuadBool.NullFalse);
|
||||
design.SetApplyHatVisible(metaValue.Enabled);
|
||||
design.SetApplyMeta(MetaIndex.HatState, metaValue.Enabled);
|
||||
design._designData.SetHatVisible(metaValue.ForcedValue);
|
||||
|
||||
metaValue = QuadBool.FromJObject(equip["Weapon"], "Show", "Apply", QuadBool.NullFalse);
|
||||
design.SetApplyWeaponVisible(metaValue.Enabled);
|
||||
design.SetApplyMeta(MetaIndex.WeaponState, metaValue.Enabled);
|
||||
design._designData.SetWeaponVisible(metaValue.ForcedValue);
|
||||
|
||||
metaValue = QuadBool.FromJObject(equip["Visor"], "IsToggled", "Apply", QuadBool.NullFalse);
|
||||
design.SetApplyVisorToggle(metaValue.Enabled);
|
||||
design.SetApplyMeta(MetaIndex.VisorState, metaValue.Enabled);
|
||||
design._designData.SetVisor(metaValue.ForcedValue);
|
||||
}
|
||||
|
||||
|
|
@ -610,7 +563,7 @@ public class DesignBase
|
|||
|
||||
var wetness = QuadBool.FromJObject(json["Wetness"], "Value", "Apply", QuadBool.NullFalse);
|
||||
design._designData.SetIsWet(wetness.ForcedValue);
|
||||
design.SetApplyWetness(wetness.Enabled);
|
||||
design.SetApplyMeta(MetaIndex.Wetness, wetness.Enabled);
|
||||
|
||||
design._designData.ModelId = json["ModelId"]?.ToObject<uint>() ?? 0;
|
||||
PrintWarning(customizations.ValidateModelId(design._designData.ModelId, out design._designData.ModelId,
|
||||
|
|
@ -664,17 +617,13 @@ public class DesignBase
|
|||
try
|
||||
{
|
||||
_designData = DesignBase64Migration.MigrateBase64(items, humans, base64, out var equipFlags, out var customizeFlags,
|
||||
out var writeProtected,
|
||||
out var applyHat, out var applyVisor, out var applyWeapon);
|
||||
out var writeProtected, out var applyMeta);
|
||||
ApplyEquip = equipFlags;
|
||||
ApplyCustomize = customizeFlags;
|
||||
ApplyParameters = 0;
|
||||
ApplyCrest = 0;
|
||||
ApplyMeta = applyMeta;
|
||||
SetWriteProtected(writeProtected);
|
||||
SetApplyHatVisible(applyHat);
|
||||
SetApplyVisorToggle(applyVisor);
|
||||
SetApplyWeaponVisible(applyWeapon);
|
||||
SetApplyWetness(true);
|
||||
CustomizeSet = SetCustomizationSet(customize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -13,7 +14,7 @@ public class DesignBase64Migration
|
|||
public const int Base64SizeV4 = 95;
|
||||
|
||||
public static unsafe DesignData MigrateBase64(ItemManager items, HumanModelList humans, string base64, out EquipFlag equipFlags,
|
||||
out CustomizeFlag customizeFlags, out bool writeProtected, out bool applyHat, out bool applyVisor, out bool applyWeapon)
|
||||
out CustomizeFlag customizeFlags, out bool writeProtected, out MetaFlag metaFlags)
|
||||
{
|
||||
static void CheckSize(int length, int requiredLength)
|
||||
{
|
||||
|
|
@ -25,9 +26,7 @@ public class DesignBase64Migration
|
|||
byte applicationFlags;
|
||||
ushort equipFlagsS;
|
||||
var bytes = Convert.FromBase64String(base64);
|
||||
applyHat = false;
|
||||
applyVisor = false;
|
||||
applyWeapon = false;
|
||||
metaFlags = MetaFlag.Wetness;
|
||||
var data = new DesignData();
|
||||
switch (bytes[0])
|
||||
{
|
||||
|
|
@ -77,9 +76,12 @@ public class DesignBase64Migration
|
|||
|
||||
customizeFlags = (applicationFlags & 0x01) != 0 ? CustomizeFlagExtensions.All : 0;
|
||||
data.SetIsWet((applicationFlags & 0x02) != 0);
|
||||
applyHat = (applicationFlags & 0x04) != 0;
|
||||
applyWeapon = (applicationFlags & 0x08) != 0;
|
||||
applyVisor = (applicationFlags & 0x10) != 0;
|
||||
if ((applicationFlags & 0x04) != 0)
|
||||
metaFlags |= MetaFlag.HatState;
|
||||
if ((applicationFlags & 0x08) != 0)
|
||||
metaFlags |= MetaFlag.WeaponState;
|
||||
if ((applicationFlags & 0x10) != 0)
|
||||
metaFlags |= MetaFlag.VisorState;
|
||||
writeProtected = (applicationFlags & 0x20) != 0;
|
||||
|
||||
equipFlags = 0;
|
||||
|
|
@ -161,16 +163,15 @@ public class DesignBase64Migration
|
|||
}
|
||||
}
|
||||
|
||||
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags,
|
||||
bool setHat, bool setVisor, bool setWeapon, bool writeProtected, float alpha = 1.0f)
|
||||
public static unsafe string CreateOldBase64(in DesignData save, EquipFlag equipFlags, CustomizeFlag customizeFlags, MetaFlag meta, bool writeProtected, float alpha = 1.0f)
|
||||
{
|
||||
var data = stackalloc byte[Base64SizeV4];
|
||||
data[0] = 5;
|
||||
data[1] = (byte)((customizeFlags == CustomizeFlagExtensions.All ? 0x01 : 0)
|
||||
| (save.IsWet() ? 0x02 : 0)
|
||||
| (setHat ? 0x04 : 0)
|
||||
| (setWeapon ? 0x08 : 0)
|
||||
| (setVisor ? 0x10 : 0)
|
||||
| (meta.HasFlag(MetaFlag.HatState) ? 0x04 : 0)
|
||||
| (meta.HasFlag(MetaFlag.WeaponState) ? 0x08 : 0)
|
||||
| (meta.HasFlag(MetaFlag.VisorState) ? 0x10 : 0)
|
||||
| (writeProtected ? 0x20 : 0));
|
||||
data[2] = (byte)((equipFlags.HasFlag(EquipFlag.Mainhand) ? 0x01 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.Offhand) ? 0x02 : 0)
|
||||
|
|
|
|||
|
|
@ -11,21 +11,10 @@ using OtterGui.Classes;
|
|||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignColorUi
|
||||
public class DesignColorUi(DesignColors colors, Configuration config)
|
||||
{
|
||||
private readonly DesignColors _colors;
|
||||
private readonly DesignManager _designs;
|
||||
private readonly Configuration _config;
|
||||
|
||||
private string _newName = string.Empty;
|
||||
|
||||
public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config)
|
||||
{
|
||||
_colors = colors;
|
||||
_designs = designs;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var table = ImRaii.Table("designColors", 3, ImGuiTableFlags.RowBg);
|
||||
|
|
@ -44,7 +33,7 @@ public class DesignColorUi
|
|||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Recycle.ToIconString(), buttonSize,
|
||||
"Revert the color used for missing design colors to its default.", _colors.MissingColor == DesignColors.MissingColorDefault,
|
||||
"Revert the color used for missing design colors to its default.", colors.MissingColor == DesignColors.MissingColorDefault,
|
||||
true))
|
||||
{
|
||||
changeString = DesignColors.MissingColorName;
|
||||
|
|
@ -52,7 +41,7 @@ public class DesignColorUi
|
|||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (DrawColorButton(DesignColors.MissingColorName, _colors.MissingColor, out var newColor))
|
||||
if (DrawColorButton(DesignColors.MissingColorName, colors.MissingColor, out var newColor))
|
||||
{
|
||||
changeString = DesignColors.MissingColorName;
|
||||
changeValue = newColor;
|
||||
|
|
@ -64,12 +53,12 @@ public class DesignColorUi
|
|||
ImGuiUtil.HoverTooltip("This color is used when the color specified in a design is not available.");
|
||||
|
||||
|
||||
var disabled = !_config.DeleteDesignModifier.IsActive();
|
||||
var disabled = !config.DeleteDesignModifier.IsActive();
|
||||
var tt = "Delete this color. This does not remove it from designs using it.";
|
||||
if (disabled)
|
||||
tt += $"\nHold {_config.DeleteDesignModifier} to delete.";
|
||||
tt += $"\nHold {config.DeleteDesignModifier} to delete.";
|
||||
|
||||
foreach (var ((name, color), idx) in _colors.WithIndex())
|
||||
foreach (var ((name, color), idx) in colors.WithIndex())
|
||||
{
|
||||
using var id = ImRaii.PushId(idx);
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -97,7 +86,7 @@ public class DesignColorUi
|
|||
? ("Specify a name for a new color first.", true)
|
||||
: _newName is DesignColors.MissingColorName or DesignColors.AutomaticName
|
||||
? ($"You can not use the name {DesignColors.MissingColorName} or {DesignColors.AutomaticName}, choose a different one.", true)
|
||||
: _colors.ContainsKey(_newName)
|
||||
: colors.ContainsKey(_newName)
|
||||
? ($"The color {_newName} already exists, please choose a different name.", true)
|
||||
: ($"Add a new color {_newName} to your list.", false);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize, tt, disabled, true))
|
||||
|
|
@ -119,9 +108,9 @@ public class DesignColorUi
|
|||
if (changeString.Length > 0)
|
||||
{
|
||||
if (!changeValue.HasValue)
|
||||
_colors.DeleteColor(changeString);
|
||||
colors.DeleteColor(changeString);
|
||||
else
|
||||
_colors.SetColor(changeString, changeValue.Value);
|
||||
colors.SetColor(changeString, changeValue.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Glamourer.GameData;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Utility;
|
||||
|
|
@ -10,7 +11,7 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans)
|
||||
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans, DesignLinkLoader _linkLoader)
|
||||
{
|
||||
public const byte Version = 6;
|
||||
|
||||
|
|
@ -54,10 +55,10 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
design.ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||
design.ApplyCrest = crestFlags & CrestExtensions.All;
|
||||
design.ApplyParameters = parameterFlags & CustomizeParameterExtensions.All;
|
||||
design.SetApplyHatVisible(design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
||||
design.SetApplyWetness(true);
|
||||
design.SetApplyMeta(MetaIndex.HatState, design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyMeta(MetaIndex.VisorState, design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyMeta(MetaIndex.WeaponState, design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
||||
design.SetApplyMeta(MetaIndex.Wetness, true);
|
||||
design.SetDesignData(_customize, data);
|
||||
return design;
|
||||
}
|
||||
|
|
@ -75,7 +76,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
case (byte)'{':
|
||||
var jObj1 = JObject.Parse(Encoding.UTF8.GetString(bytes));
|
||||
ret = jObj1["Identifier"] != null
|
||||
? Design.LoadDesign(_customize, _items, jObj1)
|
||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj1)
|
||||
: DesignBase.LoadDesignBase(_customize, _items, jObj1);
|
||||
break;
|
||||
case 1:
|
||||
|
|
@ -90,7 +91,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
var jObj2 = JObject.Parse(decompressed);
|
||||
Debug.Assert(version == 3);
|
||||
ret = jObj2["Identifier"] != null
|
||||
? Design.LoadDesign(_customize, _items, jObj2)
|
||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||
break;
|
||||
}
|
||||
|
|
@ -101,7 +102,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
var jObj2 = JObject.Parse(decompressed);
|
||||
Debug.Assert(version == 5);
|
||||
ret = jObj2["Identifier"] != null
|
||||
? Design.LoadDesign(_customize, _items, jObj2)
|
||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||
break;
|
||||
}
|
||||
|
|
@ -111,7 +112,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
var jObj2 = JObject.Parse(decompressed);
|
||||
Debug.Assert(version == 6);
|
||||
ret = jObj2["Identifier"] != null
|
||||
? Design.LoadDesign(_customize, _items, jObj2)
|
||||
? Design.LoadDesign(_customize, _items, _linkLoader, jObj2)
|
||||
: DesignBase.LoadDesignBase(_customize, _items, jObj2);
|
||||
break;
|
||||
}
|
||||
|
|
@ -125,16 +126,14 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
return null;
|
||||
}
|
||||
|
||||
ret.SetApplyWetness(customize);
|
||||
ret.SetApplyMeta(MetaIndex.Wetness, customize);
|
||||
ret.ApplyCustomize = customize ? CustomizeFlagExtensions.AllRelevant : 0;
|
||||
|
||||
if (!equip)
|
||||
{
|
||||
ret.ApplyEquip = 0;
|
||||
ret.ApplyCrest = 0;
|
||||
ret.SetApplyHatVisible(false);
|
||||
ret.SetApplyWeaponVisible(false);
|
||||
ret.SetApplyVisorToggle(false);
|
||||
ret.ApplyEquip = 0;
|
||||
ret.ApplyCrest = 0;
|
||||
ret.ApplyMeta &= ~(MetaFlag.HatState | MetaFlag.WeaponState | MetaFlag.VisorState);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@ public unsafe struct DesignData
|
|||
=> CrestVisibility.HasFlag(slot);
|
||||
|
||||
|
||||
public FullEquipType MainhandType
|
||||
public readonly FullEquipType MainhandType
|
||||
=> _typeMainhand;
|
||||
|
||||
public FullEquipType OffhandType
|
||||
public readonly FullEquipType OffhandType
|
||||
=> _typeOffhand;
|
||||
|
||||
public readonly EquipItem Item(EquipSlot slot)
|
||||
|
|
@ -186,6 +186,26 @@ public unsafe struct DesignData
|
|||
return true;
|
||||
}
|
||||
|
||||
public readonly bool GetMeta(MetaIndex index)
|
||||
=> index switch
|
||||
{
|
||||
MetaIndex.Wetness => IsWet(),
|
||||
MetaIndex.HatState => IsHatVisible(),
|
||||
MetaIndex.VisorState => IsVisorToggled(),
|
||||
MetaIndex.WeaponState => IsWeaponVisible(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public bool SetMeta(MetaIndex index, bool value)
|
||||
=> index switch
|
||||
{
|
||||
MetaIndex.Wetness => SetIsWet(value),
|
||||
MetaIndex.HatState => SetHatVisible(value),
|
||||
MetaIndex.VisorState => SetVisor(value),
|
||||
MetaIndex.WeaponState => SetWeaponVisible(value),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public readonly bool IsWet()
|
||||
=> (_states & 0x01) == 0x01;
|
||||
|
||||
|
|
|
|||
297
Glamourer/Designs/DesignEditor.cs
Normal file
297
Glamourer/Designs/DesignEditor.cs
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignEditor(
|
||||
SaveService saveService,
|
||||
DesignChanged designChanged,
|
||||
CustomizeService customizations,
|
||||
ItemManager items,
|
||||
Configuration config)
|
||||
: IDesignEditor
|
||||
{
|
||||
protected readonly DesignChanged DesignChanged = designChanged;
|
||||
protected readonly SaveService SaveService = saveService;
|
||||
protected readonly ItemManager Items = items;
|
||||
protected readonly CustomizeService Customizations = customizations;
|
||||
protected readonly Configuration Config = config;
|
||||
protected readonly Dictionary<Guid, DesignData> UndoStore = [];
|
||||
|
||||
private bool _forceFullItemOff;
|
||||
|
||||
/// <summary> Whether an Undo for the given design is possible. </summary>
|
||||
public bool CanUndo(Design? design)
|
||||
=> design != null && UndoStore.ContainsKey(design.Identifier);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
var oldValue = design.DesignData.Customize[idx];
|
||||
switch (idx)
|
||||
{
|
||||
case CustomizeIndex.Race:
|
||||
case CustomizeIndex.BodyType:
|
||||
Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen.");
|
||||
return;
|
||||
case CustomizeIndex.Clan:
|
||||
{
|
||||
var customize = design.DesignData.Customize;
|
||||
if (Customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(Customizations, customize))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
case CustomizeIndex.Gender:
|
||||
{
|
||||
var customize = design.DesignData.Customize;
|
||||
if (Customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(Customizations, customize))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!Customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender,
|
||||
design.DesignData.Customize.Face, idx, value)
|
||||
|| !design.GetDesignDataRef().Customize.Set(idx, value))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
||||
SaveService.QueueSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeEntireCustomize(object data, in CustomizeArray customize, CustomizeFlag apply, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
var (newCustomize, applied, changed) = Customizations.Combine(design.DesignData.Customize, customize, apply, true);
|
||||
if (changed == 0)
|
||||
return;
|
||||
|
||||
var oldCustomize = design.DesignData.Customize;
|
||||
design.SetCustomize(Customizations, newCustomize);
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Changed entire customize with resulting flags {applied} and {changed}.");
|
||||
SaveService.QueueSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.EntireCustomize, design, (oldCustomize, applied, changed));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
var old = design.DesignData.Parameters[flag];
|
||||
if (!design.GetDesignDataRef().Parameters.Set(flag, value))
|
||||
return;
|
||||
|
||||
var @new = design.DesignData.Parameters[flag];
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
|
||||
SaveService.QueueSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
{
|
||||
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
|
||||
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
|
||||
if (!Items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
|
||||
return;
|
||||
|
||||
if (!ChangeMainhandPeriphery(design, currentMain, currentOff, item, out var newOff, out var newGauntlets))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff, newGauntlets));
|
||||
return;
|
||||
}
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
|
||||
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
|
||||
if (!Items.IsOffhandValid(currentOff.Type, item.ItemId, out item))
|
||||
return;
|
||||
|
||||
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
DesignChanged.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
|
||||
return;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!Items.IsItemValid(slot, item.Id, out item))
|
||||
return;
|
||||
|
||||
var old = design.DesignData.Item(slot);
|
||||
if (!design.GetDesignDataRef().SetItem(slot, item))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
SaveService.QueueSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
if (Items.ValidateStain(stain, out var _, false).Length > 0)
|
||||
return;
|
||||
|
||||
var oldStain = design.DesignData.Stain(slot);
|
||||
if (!design.GetDesignDataRef().SetStain(slot, stain))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings _ = default)
|
||||
{
|
||||
if (item.HasValue)
|
||||
ChangeItem(data, slot, item.Value, _);
|
||||
if (stain.HasValue)
|
||||
ChangeStain(data, slot, stain.Value, _);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
var oldCrest = design.DesignData.Crest(slot);
|
||||
if (!design.GetDesignDataRef().SetCrest(slot, crest))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeMetaState(object data, MetaIndex metaIndex, bool value, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
if (!design.GetDesignDataRef().SetMeta(metaIndex, value))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
|
||||
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default)
|
||||
=> ApplyDesign(data, other.Design);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ApplyDesign(object data, DesignBase other, ApplySettings _ = default)
|
||||
{
|
||||
var design = (Design)data;
|
||||
UndoStore[design.Identifier] = design.DesignData;
|
||||
foreach (var index in MetaExtensions.AllRelevant.Where(other.DoApplyMeta))
|
||||
design.GetDesignDataRef().SetMeta(index, other.DesignData.GetMeta(index));
|
||||
|
||||
if (!design.DesignData.IsHuman)
|
||||
return;
|
||||
|
||||
ChangeEntireCustomize(design, other.DesignData.Customize, other.ApplyCustomize);
|
||||
|
||||
_forceFullItemOff = true;
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
ChangeEquip(design, slot,
|
||||
other.DoApplyEquip(slot) ? other.DesignData.Item(slot) : null,
|
||||
other.DoApplyStain(slot) ? other.DesignData.Stain(slot) : null);
|
||||
}
|
||||
|
||||
_forceFullItemOff = false;
|
||||
|
||||
foreach (var slot in Enum.GetValues<CrestFlag>().Where(other.DoApplyCrest))
|
||||
ChangeCrest(design, slot, other.DesignData.Crest(slot));
|
||||
|
||||
foreach (var parameter in CustomizeParameterExtensions.AllFlags.Where(other.DoApplyParameter))
|
||||
ChangeCustomizeParameter(design, parameter, other.DesignData.Parameters[parameter]);
|
||||
}
|
||||
|
||||
/// <summary> Change a mainhand weapon and either fix or apply appropriate offhand and potentially gauntlets. </summary>
|
||||
private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain, out EquipItem? newOff,
|
||||
out EquipItem? newGauntlets)
|
||||
{
|
||||
newOff = null;
|
||||
newGauntlets = null;
|
||||
if (newMain.Type != currentMain.Type)
|
||||
{
|
||||
var defaultOffhand = Items.GetDefaultOffhand(newMain);
|
||||
if (!Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
|
||||
return false;
|
||||
|
||||
newOff = o;
|
||||
}
|
||||
else if (!_forceFullItemOff && Config.ChangeEntireItem)
|
||||
{
|
||||
var defaultOffhand = Items.GetDefaultOffhand(newMain);
|
||||
if (Items.IsOffhandValid(newMain, defaultOffhand.ItemId, out var o))
|
||||
newOff = o;
|
||||
|
||||
if (newMain.Type is FullEquipType.Fists && Items.ItemData.Tertiary.TryGetValue(newMain.ItemId, out var g))
|
||||
newGauntlets = g;
|
||||
}
|
||||
|
||||
if (!design.GetDesignDataRef().SetItem(EquipSlot.MainHand, newMain))
|
||||
return false;
|
||||
|
||||
if (newOff.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff.Value))
|
||||
{
|
||||
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newGauntlets.HasValue && !design.GetDesignDataRef().SetItem(EquipSlot.Hands, newGauntlets.Value))
|
||||
{
|
||||
design.GetDesignDataRef().SetItem(EquipSlot.MainHand, currentMain);
|
||||
design.GetDesignDataRef().SetItem(EquipSlot.OffHand, currentOff);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -105,7 +105,8 @@ public sealed class DesignFileSystem : FileSystem<Design>, IDisposable, ISavable
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.", NotificationType.Error);
|
||||
Glamourer.Messager.NotificationMessage(ex, $"Could not move design to {path} because the folder could not be created.",
|
||||
NotificationType.Error);
|
||||
}
|
||||
|
||||
CreateDuplicateLeaf(parent, design.Name.Text, design);
|
||||
|
|
|
|||
|
|
@ -1,65 +1,58 @@
|
|||
using Dalamud.Utility;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignManager
|
||||
public sealed class DesignManager : DesignEditor
|
||||
{
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly ItemManager _items;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly List<Design> _designs = [];
|
||||
private readonly Dictionary<Guid, DesignData> _undoStore = [];
|
||||
|
||||
public IReadOnlyList<Design> Designs
|
||||
=> _designs;
|
||||
public readonly DesignStorage Designs;
|
||||
private readonly HumanModelList _humans;
|
||||
|
||||
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
|
||||
DesignChanged @event, HumanModelList humans)
|
||||
DesignChanged @event, HumanModelList humans, DesignStorage storage, DesignLinkLoader designLinkLoader, Configuration config)
|
||||
: base(saveService, @event, customizations, items, config)
|
||||
{
|
||||
_saveService = saveService;
|
||||
_items = items;
|
||||
_customizations = customizations;
|
||||
_event = @event;
|
||||
_humans = humans;
|
||||
Designs = storage;
|
||||
_humans = humans;
|
||||
|
||||
LoadDesigns(designLinkLoader);
|
||||
CreateDesignFolder(saveService);
|
||||
LoadDesigns();
|
||||
MigrateOldDesigns();
|
||||
designLinkLoader.SetAllObjects();
|
||||
}
|
||||
|
||||
#region Design Management
|
||||
|
||||
/// <summary>
|
||||
/// Clear currently loaded designs and load all designs anew from file.
|
||||
/// Invalid data is fixed, but changes are not saved until manual changes.
|
||||
/// </summary>
|
||||
public void LoadDesigns()
|
||||
private void LoadDesigns(DesignLinkLoader linkLoader)
|
||||
{
|
||||
_humans.Awaiter.Wait();
|
||||
_customizations.Awaiter.Wait();
|
||||
_items.ItemData.Awaiter.Wait();
|
||||
Customizations.Awaiter.Wait();
|
||||
Items.ItemData.Awaiter.Wait();
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_designs.Clear();
|
||||
Designs.Clear();
|
||||
var skipped = 0;
|
||||
ThreadLocal<List<(Design, string)>> designs = new(() => [], true);
|
||||
Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) =>
|
||||
Parallel.ForEach(SaveService.FileNames.Designs(), (f, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(f.FullName);
|
||||
var data = JObject.Parse(text);
|
||||
var design = Design.LoadDesign(_customizations, _items, data);
|
||||
var design = Design.LoadDesign(Customizations, Items, linkLoader, data);
|
||||
designs.Value!.Add((design, f.FullName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -74,15 +67,15 @@ public class DesignManager
|
|||
{
|
||||
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path))
|
||||
invalidNames.Add((design, path));
|
||||
if (_designs.Any(d => d.Identifier == design.Identifier))
|
||||
if (Designs.Contains(design.Identifier))
|
||||
{
|
||||
Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique.");
|
||||
++skipped;
|
||||
continue;
|
||||
}
|
||||
|
||||
design.Index = _designs.Count;
|
||||
_designs.Add(design);
|
||||
design.Index = Designs.Count;
|
||||
Designs.Add(design);
|
||||
}
|
||||
|
||||
var failed = MoveInvalidNames(invalidNames);
|
||||
|
|
@ -91,34 +84,30 @@ public class DesignManager
|
|||
$"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}");
|
||||
|
||||
Glamourer.Log.Information(
|
||||
$"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
|
||||
_event.Invoke(DesignChanged.Type.ReloadedAll, null!, null);
|
||||
$"Loaded {Designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
|
||||
DesignChanged.Invoke(DesignChanged.Type.ReloadedAll, null!, null);
|
||||
}
|
||||
|
||||
/// <summary> Whether an Undo for the given design is possible. </summary>
|
||||
public bool CanUndo(Design? design)
|
||||
=> design != null && _undoStore.ContainsKey(design.Identifier);
|
||||
|
||||
/// <summary> Create a new temporary design without adding it to the manager. </summary>
|
||||
public DesignBase CreateTemporary()
|
||||
=> new(_customizations, _items);
|
||||
=> new(Customizations, Items);
|
||||
|
||||
/// <summary> Create a new design of a given name. </summary>
|
||||
public Design CreateEmpty(string name, bool handlePath)
|
||||
{
|
||||
var (actualName, path) = ParseName(name, handlePath);
|
||||
var design = new Design(_customizations, _items)
|
||||
var design = new Design(Customizations, Items)
|
||||
{
|
||||
CreationDate = DateTimeOffset.UtcNow,
|
||||
LastEdit = DateTimeOffset.UtcNow,
|
||||
Identifier = CreateNewGuid(),
|
||||
Name = actualName,
|
||||
Index = _designs.Count,
|
||||
Index = Designs.Count,
|
||||
};
|
||||
_designs.Add(design);
|
||||
Designs.Add(design);
|
||||
Glamourer.Log.Debug($"Added new design {design.Identifier}.");
|
||||
_saveService.ImmediateSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Created, design, path);
|
||||
SaveService.ImmediateSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
||||
return design;
|
||||
}
|
||||
|
||||
|
|
@ -132,13 +121,13 @@ public class DesignManager
|
|||
LastEdit = DateTimeOffset.UtcNow,
|
||||
Identifier = CreateNewGuid(),
|
||||
Name = actualName,
|
||||
Index = _designs.Count,
|
||||
Index = Designs.Count,
|
||||
};
|
||||
|
||||
_designs.Add(design);
|
||||
Designs.Add(design);
|
||||
Glamourer.Log.Debug($"Added new design {design.Identifier} by cloning Temporary Design.");
|
||||
_saveService.ImmediateSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Created, design, path);
|
||||
SaveService.ImmediateSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
||||
return design;
|
||||
}
|
||||
|
||||
|
|
@ -152,26 +141,30 @@ public class DesignManager
|
|||
LastEdit = DateTimeOffset.UtcNow,
|
||||
Identifier = CreateNewGuid(),
|
||||
Name = actualName,
|
||||
Index = _designs.Count,
|
||||
Index = Designs.Count,
|
||||
};
|
||||
_designs.Add(design);
|
||||
Designs.Add(design);
|
||||
Glamourer.Log.Debug(
|
||||
$"Added new design {design.Identifier} by cloning {clone.Identifier.ToString()}.");
|
||||
_saveService.ImmediateSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Created, design, path);
|
||||
SaveService.ImmediateSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, path);
|
||||
return design;
|
||||
}
|
||||
|
||||
/// <summary> Delete a design. </summary>
|
||||
public void Delete(Design design)
|
||||
{
|
||||
foreach (var d in _designs.Skip(design.Index + 1))
|
||||
foreach (var d in Designs.Skip(design.Index + 1))
|
||||
--d.Index;
|
||||
_designs.RemoveAt(design.Index);
|
||||
_saveService.ImmediateDelete(design);
|
||||
_event.Invoke(DesignChanged.Type.Deleted, design, null);
|
||||
Designs.RemoveAt(design.Index);
|
||||
SaveService.ImmediateDelete(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Deleted, design, null);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edit Information
|
||||
|
||||
/// <summary> Rename a design. </summary>
|
||||
public void Rename(Design design, string newName)
|
||||
{
|
||||
|
|
@ -181,9 +174,9 @@ public class DesignManager
|
|||
|
||||
design.Name = newName;
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Renamed design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.Renamed, design, oldName);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Renamed, design, oldName);
|
||||
}
|
||||
|
||||
/// <summary> Change the description of a design. </summary>
|
||||
|
|
@ -195,9 +188,9 @@ public class DesignManager
|
|||
|
||||
design.Description = description;
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Changed description of design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
|
||||
DesignChanged.Invoke(DesignChanged.Type.ChangedDescription, design, oldDescription);
|
||||
}
|
||||
|
||||
public void ChangeColor(Design design, string newColor)
|
||||
|
|
@ -208,9 +201,9 @@ public class DesignManager
|
|||
|
||||
design.Color = newColor;
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedColor, design, oldColor);
|
||||
DesignChanged.Invoke(DesignChanged.Type.ChangedColor, design, oldColor);
|
||||
}
|
||||
|
||||
/// <summary> Add a new tag to a design. The tags remain sorted. </summary>
|
||||
|
|
@ -222,15 +215,11 @@ public class DesignManager
|
|||
design.Tags = design.Tags.Append(tag).OrderBy(t => t).ToArray();
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
var idx = design.Tags.IndexOf(tag);
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Added tag {tag} at {idx} to design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx));
|
||||
DesignChanged.Invoke(DesignChanged.Type.AddedTag, design, (tag, idx));
|
||||
}
|
||||
|
||||
/// <summary> Remove a tag from a design if it exists. </summary>
|
||||
public void RemoveTag(Design design, string tag)
|
||||
=> RemoveTag(design, design.Tags.IndexOf(tag));
|
||||
|
||||
/// <summary> Remove a tag from a design by its index. </summary>
|
||||
public void RemoveTag(Design design, int tagIdx)
|
||||
{
|
||||
|
|
@ -240,9 +229,9 @@ public class DesignManager
|
|||
var oldTag = design.Tags[tagIdx];
|
||||
design.Tags = design.Tags.Take(tagIdx).Concat(design.Tags.Skip(tagIdx + 1)).ToArray();
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Removed tag {oldTag} at {tagIdx} from design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx));
|
||||
DesignChanged.Invoke(DesignChanged.Type.RemovedTag, design, (oldTag, tagIdx));
|
||||
}
|
||||
|
||||
/// <summary> Rename a tag from a design by its index. The tags stay sorted.</summary>
|
||||
|
|
@ -255,9 +244,9 @@ public class DesignManager
|
|||
design.Tags[tagIdx] = newTag;
|
||||
Array.Sort(design.Tags);
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Renamed tag {oldTag} at {tagIdx} to {newTag} in design {design.Identifier} and reordered tags.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
|
||||
DesignChanged.Invoke(DesignChanged.Type.ChangedTag, design, (oldTag, newTag, tagIdx));
|
||||
}
|
||||
|
||||
/// <summary> Add an associated mod to a design. </summary>
|
||||
|
|
@ -267,9 +256,9 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Added associated mod {mod.DirectoryName} to design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
||||
DesignChanged.Invoke(DesignChanged.Type.AddedMod, design, (mod, settings));
|
||||
}
|
||||
|
||||
/// <summary> Remove an associated mod from a design. </summary>
|
||||
|
|
@ -279,9 +268,9 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Removed associated mod {mod.DirectoryName} from design {design.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
|
||||
DesignChanged.Invoke(DesignChanged.Type.RemovedMod, design, (mod, settings));
|
||||
}
|
||||
|
||||
/// <summary> Set the write protection status of a design. </summary>
|
||||
|
|
@ -290,56 +279,14 @@ public class DesignManager
|
|||
if (!design.SetWriteProtected(value))
|
||||
return;
|
||||
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set design {design.Identifier} to {(value ? "no longer be " : string.Empty)} write-protected.");
|
||||
_event.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
||||
DesignChanged.Invoke(DesignChanged.Type.WriteProtection, design, value);
|
||||
}
|
||||
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public void ChangeCustomize(Design design, CustomizeIndex idx, CustomizeValue value)
|
||||
{
|
||||
var oldValue = design.DesignData.Customize[idx];
|
||||
#endregion
|
||||
|
||||
switch (idx)
|
||||
{
|
||||
case CustomizeIndex.Race:
|
||||
case CustomizeIndex.BodyType:
|
||||
Glamourer.Log.Error("Somehow race or body type was changed in a design. This should not happen.");
|
||||
return;
|
||||
case CustomizeIndex.Clan:
|
||||
{
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
case CustomizeIndex.Gender:
|
||||
{
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!_customizations.IsCustomizationValid(design.DesignData.Customize.Clan, design.DesignData.Customize.Gender,
|
||||
design.DesignData.Customize.Face, idx, value)
|
||||
|| !design.GetDesignDataRef().Customize.Set(idx, value))
|
||||
return;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Changed customize {idx.ToDefaultName()} in design {design.Identifier} from {oldValue.Value} to {value.Value}.");
|
||||
_saveService.QueueSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Customize, design, (oldValue, value, idx));
|
||||
}
|
||||
#region Edit Application Rules
|
||||
|
||||
/// <summary> Change whether to apply a specific customize value. </summary>
|
||||
public void ChangeApplyCustomize(Design design, CustomizeIndex idx, bool value)
|
||||
|
|
@ -348,115 +295,21 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of customization {idx.ToDefaultName()} to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
||||
}
|
||||
|
||||
/// <summary> Change a non-weapon equipment piece. </summary>
|
||||
public void ChangeEquip(Design design, EquipSlot slot, EquipItem item)
|
||||
{
|
||||
if (!_items.IsItemValid(slot, item.Id, out item))
|
||||
return;
|
||||
|
||||
var old = design.DesignData.Item(slot);
|
||||
if (!design.GetDesignDataRef().SetItem(slot, item))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {slot.ToName()} equipment piece in design {design.Identifier} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
_saveService.QueueSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Equip, design, (old, item, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change a weapon. </summary>
|
||||
public void ChangeWeapon(Design design, EquipSlot slot, EquipItem item)
|
||||
{
|
||||
var currentMain = design.DesignData.Item(EquipSlot.MainHand);
|
||||
var currentOff = design.DesignData.Item(EquipSlot.OffHand);
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
var newOff = currentOff;
|
||||
if (!_items.IsItemValid(EquipSlot.MainHand, item.ItemId, out item))
|
||||
return;
|
||||
|
||||
if (item.Type != currentMain.Type)
|
||||
{
|
||||
var defaultOffhand = _items.GetDefaultOffhand(item);
|
||||
if (!_items.IsOffhandValid(item, defaultOffhand.ItemId, out newOff))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(design.GetDesignDataRef().SetItem(EquipSlot.MainHand, item)
|
||||
| design.GetDesignDataRef().SetItem(EquipSlot.OffHand, newOff)))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {EquipSlot.MainHand.ToName()} weapon in design {design.Identifier} from {currentMain.Name} ({currentMain.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, item, newOff));
|
||||
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
if (!_items.IsOffhandValid(currentOff.Type, item.ItemId, out item))
|
||||
return;
|
||||
|
||||
if (!design.GetDesignDataRef().SetItem(EquipSlot.OffHand, item))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug(
|
||||
$"Set {EquipSlot.OffHand.ToName()} weapon in design {design.Identifier} from {currentOff.Name} ({currentOff.ItemId}) to {item.Name} ({item.ItemId}).");
|
||||
_event.Invoke(DesignChanged.Type.Weapon, design, (currentMain, currentOff, currentMain, item));
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Change a customize parameter. </summary>
|
||||
public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, CustomizeParameterValue value)
|
||||
{
|
||||
var old = design.DesignData.Parameters[flag];
|
||||
if (!design.GetDesignDataRef().Parameters.Set(flag, value))
|
||||
return;
|
||||
|
||||
var @new = design.DesignData.Parameters[flag];
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Set customize parameter {flag} in design {design.Identifier} from {old} to {@new}.");
|
||||
_saveService.QueueSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Parameter, design, (old, @new, flag));
|
||||
DesignChanged.Invoke(DesignChanged.Type.ApplyCustomize, design, idx);
|
||||
}
|
||||
|
||||
/// <summary> Change whether to apply a specific equipment piece. </summary>
|
||||
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
|
||||
public void ChangeApplyItem(Design design, EquipSlot slot, bool value)
|
||||
{
|
||||
if (!design.SetApplyEquip(slot, value))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of {slot} equipment piece to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the stain for any equipment piece. </summary>
|
||||
public void ChangeStain(Design design, EquipSlot slot, StainId stain)
|
||||
{
|
||||
if (_items.ValidateStain(stain, out _, false).Length > 0)
|
||||
return;
|
||||
|
||||
var oldStain = design.DesignData.Stain(slot);
|
||||
if (!design.GetDesignDataRef().SetStain(slot, stain))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set stain of {slot} equipment piece to {stain.Id}.");
|
||||
_event.Invoke(DesignChanged.Type.Stain, design, (oldStain, stain, slot));
|
||||
DesignChanged.Invoke(DesignChanged.Type.ApplyEquip, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change whether to apply a specific stain. </summary>
|
||||
|
|
@ -466,22 +319,9 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of stain of {slot} equipment piece to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the crest visibility for any equipment piece. </summary>
|
||||
public void ChangeCrest(Design design, CrestFlag slot, bool crest)
|
||||
{
|
||||
var oldCrest = design.DesignData.Crest(slot);
|
||||
if (!design.GetDesignDataRef().SetCrest(slot, crest))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set crest visibility of {slot} equipment piece to {crest}.");
|
||||
_event.Invoke(DesignChanged.Type.Crest, design, (oldCrest, crest, slot));
|
||||
DesignChanged.Invoke(DesignChanged.Type.ApplyStain, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change whether to apply a specific crest visibility. </summary>
|
||||
|
|
@ -491,49 +331,21 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of crest visibility of {slot} equipment piece to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the bool value of one of the meta flags. </summary>
|
||||
public void ChangeMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
|
||||
{
|
||||
var change = metaIndex switch
|
||||
{
|
||||
ActorState.MetaIndex.Wetness => design.GetDesignDataRef().SetIsWet(value),
|
||||
ActorState.MetaIndex.HatState => design.GetDesignDataRef().SetHatVisible(value),
|
||||
ActorState.MetaIndex.VisorState => design.GetDesignDataRef().SetVisor(value),
|
||||
ActorState.MetaIndex.WeaponState => design.GetDesignDataRef().SetWeaponVisible(value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
|
||||
};
|
||||
if (!change)
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set value of {metaIndex} to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
|
||||
DesignChanged.Invoke(DesignChanged.Type.ApplyCrest, design, slot);
|
||||
}
|
||||
|
||||
/// <summary> Change the application value of one of the meta flags. </summary>
|
||||
public void ChangeApplyMeta(Design design, ActorState.MetaIndex metaIndex, bool value)
|
||||
public void ChangeApplyMeta(Design design, MetaIndex metaIndex, bool value)
|
||||
{
|
||||
var change = metaIndex switch
|
||||
{
|
||||
ActorState.MetaIndex.Wetness => design.SetApplyWetness(value),
|
||||
ActorState.MetaIndex.HatState => design.SetApplyHatVisible(value),
|
||||
ActorState.MetaIndex.VisorState => design.SetApplyVisorToggle(value),
|
||||
ActorState.MetaIndex.WeaponState => design.SetApplyWeaponVisible(value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(metaIndex), metaIndex, null),
|
||||
};
|
||||
if (!change)
|
||||
if (!design.SetApplyMeta(metaIndex, value))
|
||||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of {metaIndex} to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
|
||||
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
|
||||
}
|
||||
|
||||
/// <summary> Change the application value of a customize parameter. </summary>
|
||||
|
|
@ -543,83 +355,35 @@ public class DesignManager
|
|||
return;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
SaveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}.");
|
||||
_event.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
|
||||
DesignChanged.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
|
||||
}
|
||||
|
||||
/// <summary> Apply an entire design based on its appliance rules piece by piece. </summary>
|
||||
public void ApplyDesign(Design design, DesignBase other)
|
||||
{
|
||||
_undoStore[design.Identifier] = design.DesignData;
|
||||
if (other.DoApplyWetness())
|
||||
design.GetDesignDataRef().SetIsWet(other.DesignData.IsWet());
|
||||
if (other.DoApplyHatVisible())
|
||||
design.GetDesignDataRef().SetHatVisible(other.DesignData.IsHatVisible());
|
||||
if (other.DoApplyVisorToggle())
|
||||
design.GetDesignDataRef().SetVisor(other.DesignData.IsVisorToggled());
|
||||
if (other.DoApplyWeaponVisible())
|
||||
design.GetDesignDataRef().SetWeaponVisible(other.DesignData.IsWeaponVisible());
|
||||
|
||||
if (design.DesignData.IsHuman)
|
||||
{
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
if (other.DoApplyCustomize(index))
|
||||
ChangeCustomize(design, index, other.DesignData.Customize[index]);
|
||||
}
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
if (other.DoApplyEquip(slot))
|
||||
ChangeEquip(design, slot, other.DesignData.Item(slot));
|
||||
|
||||
if (other.DoApplyStain(slot))
|
||||
ChangeStain(design, slot, other.DesignData.Stain(slot));
|
||||
}
|
||||
|
||||
foreach (var slot in Enum.GetValues<CrestFlag>())
|
||||
{
|
||||
if (other.DoApplyCrest(slot))
|
||||
ChangeCrest(design, slot, other.DesignData.Crest(slot));
|
||||
}
|
||||
}
|
||||
|
||||
if (other.DoApplyEquip(EquipSlot.MainHand))
|
||||
ChangeWeapon(design, EquipSlot.MainHand, other.DesignData.Item(EquipSlot.MainHand));
|
||||
|
||||
if (other.DoApplyEquip(EquipSlot.OffHand))
|
||||
ChangeWeapon(design, EquipSlot.OffHand, other.DesignData.Item(EquipSlot.OffHand));
|
||||
|
||||
if (other.DoApplyStain(EquipSlot.MainHand))
|
||||
ChangeStain(design, EquipSlot.MainHand, other.DesignData.Stain(EquipSlot.MainHand));
|
||||
|
||||
if (other.DoApplyStain(EquipSlot.OffHand))
|
||||
ChangeStain(design, EquipSlot.OffHand, other.DesignData.Stain(EquipSlot.OffHand));
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void UndoDesignChange(Design design)
|
||||
{
|
||||
if (!_undoStore.Remove(design.Identifier, out var otherData))
|
||||
if (!UndoStore.Remove(design.Identifier, out var otherData))
|
||||
return;
|
||||
|
||||
var other = CreateTemporary();
|
||||
other.SetDesignData(_customizations, otherData);
|
||||
other.SetDesignData(Customizations, otherData);
|
||||
ApplyDesign(design, other);
|
||||
}
|
||||
|
||||
private void MigrateOldDesigns()
|
||||
{
|
||||
if (!File.Exists(_saveService.FileNames.MigrationDesignFile))
|
||||
if (!File.Exists(SaveService.FileNames.MigrationDesignFile))
|
||||
return;
|
||||
|
||||
var errors = 0;
|
||||
var skips = 0;
|
||||
var successes = 0;
|
||||
var oldDesigns = _designs.ToList();
|
||||
var oldDesigns = Designs.ToList();
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(_saveService.FileNames.MigrationDesignFile);
|
||||
var text = File.ReadAllText(SaveService.FileNames.MigrationDesignFile);
|
||||
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(text) ?? new Dictionary<string, string>();
|
||||
var migratedFileSystemPaths = new Dictionary<string, string>(dict.Count);
|
||||
foreach (var (name, base64) in dict)
|
||||
|
|
@ -627,14 +391,14 @@ public class DesignManager
|
|||
try
|
||||
{
|
||||
var actualName = Path.GetFileName(name);
|
||||
var design = new Design(_customizations, _items)
|
||||
var design = new Design(Customizations, Items)
|
||||
{
|
||||
CreationDate = File.GetCreationTimeUtc(_saveService.FileNames.MigrationDesignFile),
|
||||
LastEdit = File.GetLastWriteTimeUtc(_saveService.FileNames.MigrationDesignFile),
|
||||
CreationDate = File.GetCreationTimeUtc(SaveService.FileNames.MigrationDesignFile),
|
||||
LastEdit = File.GetLastWriteTimeUtc(SaveService.FileNames.MigrationDesignFile),
|
||||
Identifier = CreateNewGuid(),
|
||||
Name = actualName,
|
||||
};
|
||||
design.MigrateBase64(_customizations, _items, _humans, base64);
|
||||
design.MigrateBase64(Customizations, Items, _humans, base64);
|
||||
if (!oldDesigns.Any(d => d.Name == design.Name && d.CreationDate == design.CreationDate))
|
||||
{
|
||||
Add(design, $"Migrated old design to {design.Identifier}.");
|
||||
|
|
@ -655,24 +419,24 @@ public class DesignManager
|
|||
}
|
||||
}
|
||||
|
||||
DesignFileSystem.MigrateOldPaths(_saveService, migratedFileSystemPaths);
|
||||
DesignFileSystem.MigrateOldPaths(SaveService, migratedFileSystemPaths);
|
||||
Glamourer.Log.Information(
|
||||
$"Successfully migrated {successes} old designs. Skipped {skips} already migrated designs. Failed to migrate {errors} designs.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not migrate old design file {_saveService.FileNames.MigrationDesignFile}:\n{e}");
|
||||
Glamourer.Log.Error($"Could not migrate old design file {SaveService.FileNames.MigrationDesignFile}:\n{e}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(_saveService.FileNames.MigrationDesignFile,
|
||||
Path.ChangeExtension(_saveService.FileNames.MigrationDesignFile, ".json.bak"));
|
||||
Glamourer.Log.Information($"Moved migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file.");
|
||||
File.Move(SaveService.FileNames.MigrationDesignFile,
|
||||
Path.ChangeExtension(SaveService.FileNames.MigrationDesignFile, ".json.bak"));
|
||||
Glamourer.Log.Information($"Moved migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not move migrated design file {_saveService.FileNames.MigrationDesignFile} to backup file:\n{ex}");
|
||||
Glamourer.Log.Error($"Could not move migrated design file {SaveService.FileNames.MigrationDesignFile} to backup file:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -702,7 +466,7 @@ public class DesignManager
|
|||
{
|
||||
try
|
||||
{
|
||||
var correctName = _saveService.FileNames.DesignFile(design);
|
||||
var correctName = SaveService.FileNames.DesignFile(design);
|
||||
File.Move(name, correctName, false);
|
||||
Glamourer.Log.Information($"Moved invalid design file from {Path.GetFileName(name)} to {Path.GetFileName(correctName)}.");
|
||||
}
|
||||
|
|
@ -722,7 +486,7 @@ public class DesignManager
|
|||
while (true)
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
if (_designs.All(d => d.Identifier != guid))
|
||||
if (!Designs.Contains(guid))
|
||||
return guid;
|
||||
}
|
||||
}
|
||||
|
|
@ -732,18 +496,17 @@ public class DesignManager
|
|||
/// Returns false if the design is already contained or if the identifier is already in use.
|
||||
/// The design is treated as newly created and invokes an event.
|
||||
/// </summary>
|
||||
private bool Add(Design design, string? message)
|
||||
private void Add(Design design, string? message)
|
||||
{
|
||||
if (_designs.Any(d => d == design || d.Identifier == design.Identifier))
|
||||
return false;
|
||||
if (Designs.Any(d => d == design || d.Identifier == design.Identifier))
|
||||
return;
|
||||
|
||||
design.Index = _designs.Count;
|
||||
_designs.Add(design);
|
||||
design.Index = Designs.Count;
|
||||
Designs.Add(design);
|
||||
if (!message.IsNullOrEmpty())
|
||||
Glamourer.Log.Debug(message);
|
||||
_saveService.ImmediateSave(design);
|
||||
_event.Invoke(DesignChanged.Type.Created, design, null);
|
||||
return true;
|
||||
SaveService.ImmediateSave(design);
|
||||
DesignChanged.Invoke(DesignChanged.Type.Created, design, null);
|
||||
}
|
||||
|
||||
/// <summary> Split a given string into its folder path and its name, if <paramref name="handlePath"/> is true. </summary>
|
||||
|
|
|
|||
18
Glamourer/Designs/DesignStorage.cs
Normal file
18
Glamourer/Designs/DesignStorage.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public class DesignStorage : List<Design>, IService
|
||||
{
|
||||
public bool TryGetValue(Guid identifier, [NotNullWhen(true)] out Design? design)
|
||||
{
|
||||
design = ByIdentifier(identifier);
|
||||
return design != null;
|
||||
}
|
||||
|
||||
public Design? ByIdentifier(Guid identifier)
|
||||
=> this.FirstOrDefault(d => d.Identifier == identifier);
|
||||
|
||||
public bool Contains(Guid identifier)
|
||||
=> ByIdentifier(identifier) != null;
|
||||
}
|
||||
71
Glamourer/Designs/IDesignEditor.cs
Normal file
71
Glamourer/Designs/IDesignEditor.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using Glamourer.Designs.Links;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public readonly record struct ApplySettings(
|
||||
uint Key = 0,
|
||||
StateSource Source = StateSource.Manual,
|
||||
bool RespectManual = false,
|
||||
bool FromJobChange = false,
|
||||
bool UseSingleSource = false,
|
||||
bool MergeLinks = false)
|
||||
{
|
||||
public static readonly ApplySettings Manual = new()
|
||||
{
|
||||
Key = 0,
|
||||
Source = StateSource.Manual,
|
||||
FromJobChange = false,
|
||||
RespectManual = false,
|
||||
UseSingleSource = false,
|
||||
MergeLinks = false,
|
||||
};
|
||||
|
||||
public static readonly ApplySettings Game = new()
|
||||
{
|
||||
Key = 0,
|
||||
Source = StateSource.Game,
|
||||
FromJobChange = false,
|
||||
RespectManual = false,
|
||||
UseSingleSource = false,
|
||||
MergeLinks = false,
|
||||
};
|
||||
}
|
||||
|
||||
public interface IDesignEditor
|
||||
{
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change an entire customize array according to the given flags. </summary>
|
||||
public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change a customize parameter. </summary>
|
||||
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue v, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change an equipment piece. </summary>
|
||||
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
|
||||
=> ChangeEquip(data, slot, item, null, settings);
|
||||
|
||||
/// <summary> Change the stain for any equipment piece. </summary>
|
||||
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings = default)
|
||||
=> ChangeEquip(data, slot, null, stain, settings);
|
||||
|
||||
/// <summary> Change an equipment piece and its stain at the same time. </summary>
|
||||
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change the crest visibility for any equipment piece. </summary>
|
||||
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change the bool value of one of the meta flags. </summary>
|
||||
public void ChangeMetaState(object data, MetaIndex slot, bool value, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change all values applies from the given design. </summary>
|
||||
public void ApplyDesign(object data, MergedDesign design, ApplySettings settings = default);
|
||||
|
||||
/// <summary> Change all values applies from the given design. </summary>
|
||||
public void ApplyDesign(object data, DesignBase design, ApplySettings settings = default);
|
||||
}
|
||||
19
Glamourer/Designs/Links/DesignLink.cs
Normal file
19
Glamourer/Designs/Links/DesignLink.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using Glamourer.Automation;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public record struct DesignLink(Design Link, ApplicationType Type);
|
||||
|
||||
public readonly record struct LinkData(Guid Identity, ApplicationType Type, LinkOrder Order)
|
||||
{
|
||||
public override string ToString()
|
||||
=> Identity.ToString();
|
||||
}
|
||||
|
||||
public enum LinkOrder : byte
|
||||
{
|
||||
Self,
|
||||
After,
|
||||
Before,
|
||||
None,
|
||||
};
|
||||
27
Glamourer/Designs/Links/DesignLinkLoader.cs
Normal file
27
Glamourer/Designs/Links/DesignLinkLoader.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public sealed class DesignLinkLoader(DesignStorage designStorage, MessageService messager)
|
||||
: DelayedReferenceLoader<Design, LinkData>(messager), IService
|
||||
{
|
||||
protected override bool TryGetObject(LinkData data, [NotNullWhen(true)] out Design? obj)
|
||||
=> designStorage.FindFirst(d => d.Identifier == data.Identity, out obj);
|
||||
|
||||
protected override bool SetObject(Design parent, Design child, LinkData data, out string error)
|
||||
=> LinkContainer.AddLink(parent, child, data.Type, data.Order, out error);
|
||||
|
||||
protected override void HandleChildNotFound(Design parent, LinkData data)
|
||||
{
|
||||
Messager.AddMessage(new Notification(
|
||||
$"Could not find the design {data.Identity}. If this design was deleted, please re-save {parent.Identifier}.",
|
||||
NotificationType.Warning));
|
||||
}
|
||||
|
||||
protected override void HandleChildNotSet(Design parent, Design child, string error)
|
||||
=> Messager.AddMessage(new Notification($"Could not link {child.Identifier} to {parent.Identifier}: {error}",
|
||||
NotificationType.Warning));
|
||||
}
|
||||
85
Glamourer/Designs/Links/DesignLinkManager.cs
Normal file
85
Glamourer/Designs/Links/DesignLinkManager.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
using Glamourer.Automation;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public sealed class DesignLinkManager : IService, IDisposable
|
||||
{
|
||||
private readonly DesignStorage _storage;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly SaveService _saveService;
|
||||
|
||||
public DesignLinkManager(DesignStorage storage, DesignChanged @event, SaveService saveService)
|
||||
{
|
||||
_storage = storage;
|
||||
_event = @event;
|
||||
_saveService = saveService;
|
||||
|
||||
_event.Subscribe(OnDesignChanged, DesignChanged.Priority.DesignLinkManager);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _event.Unsubscribe(OnDesignChanged);
|
||||
|
||||
public void MoveDesignLink(Design parent, int idxFrom, LinkOrder orderFrom, int idxTo, LinkOrder orderTo)
|
||||
{
|
||||
if (!parent.Links.Reorder(idxFrom, orderFrom, idxTo, orderTo))
|
||||
return;
|
||||
|
||||
parent.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(parent);
|
||||
Glamourer.Log.Debug($"Moved link from {orderFrom} {idxFrom} to {idxTo} {orderTo}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
||||
}
|
||||
|
||||
public void AddDesignLink(Design parent, Design child, LinkOrder order)
|
||||
{
|
||||
if (!LinkContainer.AddLink(parent, child, ApplicationType.All, order, out _))
|
||||
return;
|
||||
|
||||
parent.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(parent);
|
||||
Glamourer.Log.Debug($"Added new {order} link to {child.Identifier} for {parent.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
||||
}
|
||||
|
||||
public void RemoveDesignLink(Design parent, int idx, LinkOrder order)
|
||||
{
|
||||
if (!parent.Links.Remove(idx, order))
|
||||
return;
|
||||
|
||||
parent.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(parent);
|
||||
Glamourer.Log.Debug($"Removed the {order} link at {idx} for {parent.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
||||
}
|
||||
|
||||
public void ChangeApplicationType(Design parent, int idx, LinkOrder order, ApplicationType applicationType)
|
||||
{
|
||||
applicationType &= ApplicationType.All;
|
||||
if (!parent.Links.ChangeApplicationRules(idx, order, applicationType, out var old))
|
||||
return;
|
||||
|
||||
_saveService.QueueSave(parent);
|
||||
Glamourer.Log.Debug($"Changed link application type from {old} to {applicationType} for design link {order} {idx + 1} in design {parent.Identifier}.");
|
||||
_event.Invoke(DesignChanged.Type.ChangedLink, parent, null);
|
||||
}
|
||||
|
||||
private void OnDesignChanged(DesignChanged.Type type, Design deletedDesign, object? _)
|
||||
{
|
||||
if (type is not DesignChanged.Type.Deleted)
|
||||
return;
|
||||
|
||||
foreach (var design in _storage)
|
||||
{
|
||||
if (!design.Links.Remove(deletedDesign))
|
||||
continue;
|
||||
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
Glamourer.Log.Debug($"Removed {deletedDesign.Identifier} from {design.Identifier} links due to deletion.");
|
||||
_saveService.QueueSave(design);
|
||||
}
|
||||
}
|
||||
}
|
||||
272
Glamourer/Designs/Links/DesignMerger.cs
Normal file
272
Glamourer/Designs/Links/DesignMerger.cs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
using Glamourer.Automation;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Unlocks;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public class DesignMerger(
|
||||
DesignManager designManager,
|
||||
CustomizeService _customize,
|
||||
Configuration _config,
|
||||
ItemUnlockManager _itemUnlocks,
|
||||
CustomizeUnlockManager _customizeUnlocks) : IService
|
||||
{
|
||||
public MergedDesign Merge(LinkContainer designs, in DesignData baseRef, bool respectOwnership, bool modAssociations)
|
||||
=> Merge(designs.Select(d => ((DesignBase?) d.Link, d.Type)), baseRef, respectOwnership, modAssociations);
|
||||
|
||||
public MergedDesign Merge(IEnumerable<(DesignBase?, ApplicationType)> designs, in DesignData baseRef, bool respectOwnership,
|
||||
bool modAssociations)
|
||||
{
|
||||
var ret = new MergedDesign(designManager);
|
||||
CustomizeFlag fixFlags = 0;
|
||||
respectOwnership &= _config.UnlockedItemMode;
|
||||
foreach (var (design, type) in designs)
|
||||
{
|
||||
if (type is 0)
|
||||
continue;
|
||||
|
||||
ref readonly var data = ref design == null ? ref baseRef : ref design.GetDesignDataRef();
|
||||
var source = design == null ? StateSource.Game : StateSource.Manual;
|
||||
|
||||
if (!data.IsHuman)
|
||||
continue;
|
||||
|
||||
var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyMeta) = type.ApplyWhat(design);
|
||||
ReduceMeta(data, applyMeta, ret, source);
|
||||
ReduceCustomize(data, customizeFlags, ref fixFlags, ret, source, respectOwnership);
|
||||
ReduceEquip(data, equipFlags, ret, source, respectOwnership);
|
||||
ReduceMainhands(data, equipFlags, ret, source, respectOwnership);
|
||||
ReduceOffhands(data, equipFlags, ret, source, respectOwnership);
|
||||
ReduceCrests(data, crestFlags, ret, source);
|
||||
ReduceParameters(data, parameterFlags, ret, source);
|
||||
ReduceMods(design as Design, ret, modAssociations);
|
||||
}
|
||||
|
||||
ApplyFixFlags(ret, fixFlags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations)
|
||||
{
|
||||
if (design == null || !modAssociations)
|
||||
return;
|
||||
|
||||
foreach (var (mod, settings) in design.AssociatedMods)
|
||||
ret.AssociatedMods.TryAdd(mod, settings);
|
||||
}
|
||||
|
||||
private static void ReduceMeta(in DesignData design, MetaFlag applyMeta, MergedDesign ret, StateSource source)
|
||||
{
|
||||
applyMeta &= ~ret.Design.ApplyMeta;
|
||||
|
||||
foreach (var index in MetaExtensions.AllRelevant)
|
||||
{
|
||||
if (!applyMeta.HasFlag(index.ToFlag()))
|
||||
continue;
|
||||
|
||||
ret.Design.SetApplyMeta(index, true);
|
||||
ret.Design.GetDesignDataRef().SetMeta(index, design.GetMeta(index));
|
||||
ret.Sources[index] = source;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReduceCrests(in DesignData design, CrestFlag crestFlags, MergedDesign ret, StateSource source)
|
||||
{
|
||||
crestFlags &= ~ret.Design.ApplyCrest;
|
||||
if (crestFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
if (!crestFlags.HasFlag(slot))
|
||||
continue;
|
||||
|
||||
ret.Design.GetDesignDataRef().SetCrest(slot, design.Crest(slot));
|
||||
ret.Design.SetApplyCrest(slot, true);
|
||||
ret.Sources[slot] = source;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReduceParameters(in DesignData design, CustomizeParameterFlag parameterFlags, MergedDesign ret,
|
||||
StateSource source)
|
||||
{
|
||||
parameterFlags &= ~ret.Design.ApplyParameters;
|
||||
if (parameterFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
{
|
||||
if (!parameterFlags.HasFlag(flag))
|
||||
continue;
|
||||
|
||||
ret.Design.GetDesignDataRef().Parameters.Set(flag, design.Parameters[flag]);
|
||||
ret.Design.SetApplyParameter(flag, true);
|
||||
ret.Sources[flag] = source;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceEquip(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||
bool respectOwnership)
|
||||
{
|
||||
equipFlags &= ~ret.Design.ApplyEquip;
|
||||
if (equipFlags == 0)
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var flag = slot.ToFlag();
|
||||
|
||||
if (equipFlags.HasFlag(flag))
|
||||
{
|
||||
var item = design.Item(slot);
|
||||
if (!respectOwnership || _itemUnlocks.IsUnlocked(item.Id, out _))
|
||||
ret.Design.GetDesignDataRef().SetItem(slot, item);
|
||||
ret.Design.SetApplyEquip(slot, true);
|
||||
ret.Sources[slot, false] = source;
|
||||
}
|
||||
|
||||
var stainFlag = slot.ToStainFlag();
|
||||
if (equipFlags.HasFlag(stainFlag))
|
||||
{
|
||||
ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot));
|
||||
ret.Design.SetApplyStain(slot, true);
|
||||
ret.Sources[slot, true] = source;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.WeaponSlots)
|
||||
{
|
||||
var stainFlag = slot.ToStainFlag();
|
||||
if (equipFlags.HasFlag(stainFlag))
|
||||
{
|
||||
ret.Design.GetDesignDataRef().SetStain(slot, design.Stain(slot));
|
||||
ret.Design.SetApplyStain(slot, true);
|
||||
ret.Sources[slot, true] = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReduceMainhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source,
|
||||
bool respectOwnership)
|
||||
{
|
||||
if (!equipFlags.HasFlag(EquipFlag.Mainhand))
|
||||
return;
|
||||
|
||||
var weapon = design.Item(EquipSlot.MainHand);
|
||||
if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _))
|
||||
return;
|
||||
|
||||
if (!ret.Design.DoApplyEquip(EquipSlot.MainHand))
|
||||
{
|
||||
ret.Design.SetApplyEquip(EquipSlot.MainHand, true);
|
||||
ret.Design.GetDesignDataRef().SetItem(EquipSlot.MainHand, weapon);
|
||||
}
|
||||
|
||||
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
|
||||
}
|
||||
|
||||
private void ReduceOffhands(in DesignData design, EquipFlag equipFlags, MergedDesign ret, StateSource source, bool respectOwnership)
|
||||
{
|
||||
if (!equipFlags.HasFlag(EquipFlag.Offhand))
|
||||
return;
|
||||
|
||||
var weapon = design.Item(EquipSlot.OffHand);
|
||||
if (respectOwnership && !_itemUnlocks.IsUnlocked(weapon.Id, out _))
|
||||
return;
|
||||
|
||||
if (!ret.Design.DoApplyEquip(EquipSlot.OffHand))
|
||||
{
|
||||
ret.Design.SetApplyEquip(EquipSlot.OffHand, true);
|
||||
ret.Design.GetDesignDataRef().SetItem(EquipSlot.OffHand, weapon);
|
||||
}
|
||||
|
||||
if (weapon.Valid)
|
||||
ret.Weapons.TryAdd(weapon.Type, (weapon, source));
|
||||
}
|
||||
|
||||
private void ReduceCustomize(in DesignData design, CustomizeFlag customizeFlags, ref CustomizeFlag fixFlags, MergedDesign ret,
|
||||
StateSource source, bool respectOwnership)
|
||||
{
|
||||
customizeFlags &= ~ret.Design.ApplyCustomizeRaw;
|
||||
if (customizeFlags == 0)
|
||||
return;
|
||||
|
||||
// Skip anything not human.
|
||||
if (!ret.Design.DesignData.IsHuman || !design.IsHuman)
|
||||
return;
|
||||
|
||||
var customize = ret.Design.DesignData.Customize;
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Clan))
|
||||
{
|
||||
fixFlags |= _customize.ChangeClan(ref customize, design.Customize.Clan);
|
||||
ret.Design.SetApplyCustomize(CustomizeIndex.Clan, true);
|
||||
ret.Design.SetApplyCustomize(CustomizeIndex.Race, true);
|
||||
customizeFlags &= ~(CustomizeFlag.Clan | CustomizeFlag.Race);
|
||||
ret.Sources[CustomizeIndex.Clan] = source;
|
||||
ret.Sources[CustomizeIndex.Race] = source;
|
||||
}
|
||||
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Gender))
|
||||
{
|
||||
fixFlags |= _customize.ChangeGender(ref customize, design.Customize.Gender);
|
||||
ret.Design.SetApplyCustomize(CustomizeIndex.Gender, true);
|
||||
customizeFlags &= ~CustomizeFlag.Gender;
|
||||
ret.Sources[CustomizeIndex.Gender] = source;
|
||||
}
|
||||
|
||||
if (customizeFlags.HasFlag(CustomizeFlag.Face))
|
||||
{
|
||||
customize[CustomizeIndex.Face] = design.Customize.Face;
|
||||
ret.Design.SetApplyCustomize(CustomizeIndex.Face, true);
|
||||
customizeFlags &= ~CustomizeFlag.Face;
|
||||
ret.Sources[CustomizeIndex.Face] = source;
|
||||
}
|
||||
|
||||
var set = _customize.Manager.GetSet(customize.Clan, customize.Gender);
|
||||
var face = customize.Face;
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var flag = index.ToFlag();
|
||||
if (!customizeFlags.HasFlag(flag))
|
||||
continue;
|
||||
|
||||
var value = design.Customize[index];
|
||||
if (!CustomizeService.IsCustomizationValid(set, face, index, value, out var data))
|
||||
continue;
|
||||
|
||||
if (data.HasValue && respectOwnership && !_customizeUnlocks.IsUnlocked(data.Value, out _))
|
||||
continue;
|
||||
|
||||
customize[index] = data?.Value ?? value;
|
||||
ret.Design.SetApplyCustomize(index, true);
|
||||
ret.Sources[index] = source;
|
||||
fixFlags &= ~flag;
|
||||
}
|
||||
|
||||
ret.Design.SetCustomize(_customize, customize);
|
||||
}
|
||||
|
||||
private static void ApplyFixFlags(MergedDesign ret, CustomizeFlag fixFlags)
|
||||
{
|
||||
if (fixFlags == 0)
|
||||
return;
|
||||
|
||||
var source = ret.Design.DoApplyCustomize(CustomizeIndex.Clan)
|
||||
? ret.Sources[CustomizeIndex.Clan]
|
||||
: ret.Sources[CustomizeIndex.Gender];
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var flag = index.ToFlag();
|
||||
if (!fixFlags.HasFlag(flag))
|
||||
continue;
|
||||
|
||||
ret.Sources[index] = source;
|
||||
ret.Design.SetApplyCustomize(index, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
193
Glamourer/Designs/Links/LinkContainer.cs
Normal file
193
Glamourer/Designs/Links/LinkContainer.cs
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
using Glamourer.Automation;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Filesystem;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public sealed class LinkContainer : List<DesignLink>
|
||||
{
|
||||
public List<DesignLink> Before
|
||||
=> this;
|
||||
|
||||
public readonly List<DesignLink> After = [];
|
||||
|
||||
public new int Count
|
||||
=> base.Count + After.Count;
|
||||
|
||||
public bool Reorder(int fromIndex, LinkOrder fromOrder, int toIndex, LinkOrder toOrder)
|
||||
{
|
||||
var fromList = fromOrder switch
|
||||
{
|
||||
LinkOrder.Before => Before,
|
||||
LinkOrder.After => After,
|
||||
_ => throw new ArgumentException("Invalid link order."),
|
||||
};
|
||||
|
||||
var toList = toOrder switch
|
||||
{
|
||||
LinkOrder.Before => Before,
|
||||
LinkOrder.After => After,
|
||||
_ => throw new ArgumentException("Invalid link order."),
|
||||
};
|
||||
|
||||
if (fromList == toList)
|
||||
return fromList.Move(fromIndex, toIndex);
|
||||
|
||||
if (fromIndex < 0 || fromIndex >= fromList.Count)
|
||||
return false;
|
||||
|
||||
toIndex = Math.Clamp(toIndex, 0, toList.Count);
|
||||
toList.Insert(toIndex, fromList[fromIndex]);
|
||||
fromList.RemoveAt(fromIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(int idx, LinkOrder order)
|
||||
{
|
||||
var list = order switch
|
||||
{
|
||||
LinkOrder.Before => Before,
|
||||
LinkOrder.After => After,
|
||||
_ => throw new ArgumentException("Invalid link order."),
|
||||
};
|
||||
if (idx < 0 || idx >= list.Count)
|
||||
return false;
|
||||
|
||||
list.RemoveAt(idx);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeApplicationRules(int idx, LinkOrder order, ApplicationType type, out ApplicationType old)
|
||||
{
|
||||
var list = order switch
|
||||
{
|
||||
LinkOrder.Before => Before,
|
||||
LinkOrder.After => After,
|
||||
_ => throw new ArgumentException("Invalid link order."),
|
||||
};
|
||||
old = list[idx].Type;
|
||||
if (idx < 0 || idx >= list.Count || old == type)
|
||||
return false;
|
||||
|
||||
list[idx] = list[idx] with { Type = type };
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool CanAddLink(Design parent, Design child, LinkOrder order, out string error)
|
||||
{
|
||||
if (parent == child)
|
||||
{
|
||||
error = $"Can not link {parent.Incognito} with itself.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parent.Links.Contains(child))
|
||||
{
|
||||
error = $"Design {parent.Incognito} already contains a direct link to {child.Incognito}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetAllLinks(parent).Any(l => l.Link.Link == child && l.Order != order))
|
||||
{
|
||||
error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the parent already links to the child in the opposite direction.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetAllLinks(child).Any(l => l.Link.Link == parent && l.Order == order))
|
||||
{
|
||||
error = $"Adding {child.Incognito} to {parent.Incognito}s links would create a circle, the child already links to the parent in the opposite direction.";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool AddLink(Design parent, Design child, ApplicationType type, LinkOrder order, out string error)
|
||||
{
|
||||
if (!CanAddLink(parent, child, order, out error))
|
||||
return false;
|
||||
|
||||
var list = order switch
|
||||
{
|
||||
LinkOrder.Before => parent.Links.Before,
|
||||
LinkOrder.After => parent.Links.After,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
error = $"Order {order} is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
type &= ApplicationType.All;
|
||||
list.Add(new DesignLink(child, type));
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Design child)
|
||||
=> Before.Any(l => l.Link == child) || After.Any(l => l.Link == child);
|
||||
|
||||
public bool Remove(Design child)
|
||||
=> Before.RemoveAll(l => l.Link == child) + After.RemoveAll(l => l.Link == child) > 0;
|
||||
|
||||
public static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(Design design)
|
||||
{
|
||||
var set = new HashSet<Design>(design.Links.Count * 4);
|
||||
return GetAllLinks(new DesignLink(design, ApplicationType.All), LinkOrder.Self, set);
|
||||
}
|
||||
|
||||
private static IEnumerable<(DesignLink Link, LinkOrder Order)> GetAllLinks(DesignLink design, LinkOrder currentOrder, ISet<Design> visited)
|
||||
{
|
||||
if (design.Link.Links.Count == 0)
|
||||
{
|
||||
if (visited.Add(design.Link))
|
||||
yield return (design, currentOrder);
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var link in design.Link.Links.Before
|
||||
.Where(l => !visited.Contains(l.Link))
|
||||
.SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.After ? LinkOrder.After : LinkOrder.Before, visited)))
|
||||
yield return link;
|
||||
|
||||
if (visited.Add(design.Link))
|
||||
yield return (design, currentOrder);
|
||||
|
||||
foreach (var link in design.Link.Links.After.Where(l => !visited.Contains(l.Link))
|
||||
.SelectMany(l => GetAllLinks(l, currentOrder == LinkOrder.Before ? LinkOrder.Before : LinkOrder.After, visited)))
|
||||
yield return link;
|
||||
}
|
||||
|
||||
public JObject Serialize()
|
||||
{
|
||||
var before = new JArray();
|
||||
foreach (var link in Before)
|
||||
{
|
||||
before.Add(new JObject
|
||||
{
|
||||
["Design"] = link.Link.Identifier,
|
||||
["Type"] = (uint)link.Type,
|
||||
});
|
||||
}
|
||||
|
||||
var after = new JArray();
|
||||
foreach (var link in After)
|
||||
{
|
||||
after.Add(new JObject
|
||||
{
|
||||
["Design"] = link.Link.Identifier,
|
||||
["Type"] = (uint)link.Type,
|
||||
});
|
||||
}
|
||||
|
||||
return new JObject
|
||||
{
|
||||
[nameof(Before)] = before,
|
||||
[nameof(After)] = after,
|
||||
};
|
||||
}
|
||||
}
|
||||
51
Glamourer/Designs/Links/MergedDesign.cs
Normal file
51
Glamourer/Designs/Links/MergedDesign.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Designs.Links;
|
||||
|
||||
public sealed class MergedDesign
|
||||
{
|
||||
public MergedDesign(DesignManager designManager)
|
||||
{
|
||||
Design = designManager.CreateTemporary();
|
||||
Design.ApplyEquip = 0;
|
||||
Design.ApplyCustomize = 0;
|
||||
Design.ApplyCrest = 0;
|
||||
Design.ApplyParameters = 0;
|
||||
Design.ApplyMeta = 0;
|
||||
}
|
||||
|
||||
public MergedDesign(DesignBase design)
|
||||
{
|
||||
Design = design;
|
||||
if (design.DoApplyEquip(EquipSlot.MainHand))
|
||||
{
|
||||
var weapon = design.DesignData.Item(EquipSlot.MainHand);
|
||||
if (weapon.Valid)
|
||||
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
|
||||
}
|
||||
|
||||
if (design.DoApplyEquip(EquipSlot.OffHand))
|
||||
{
|
||||
var weapon = design.DesignData.Item(EquipSlot.OffHand);
|
||||
if (weapon.Valid)
|
||||
Weapons.TryAdd(weapon.Type, (weapon, StateSource.Manual));
|
||||
}
|
||||
}
|
||||
|
||||
public MergedDesign(Design design)
|
||||
: this((DesignBase)design)
|
||||
{
|
||||
foreach (var (mod, settings) in design.AssociatedMods)
|
||||
AssociatedMods[mod] = settings;
|
||||
}
|
||||
|
||||
public readonly DesignBase Design;
|
||||
public readonly Dictionary<FullEquipType, (EquipItem, StateSource)> Weapons = new(4);
|
||||
public readonly SortedList<Mod, ModSettings> AssociatedMods = [];
|
||||
public StateSources Sources = new();
|
||||
}
|
||||
69
Glamourer/Designs/MetaIndex.cs
Normal file
69
Glamourer/Designs/MetaIndex.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using Glamourer.State;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public enum MetaIndex
|
||||
{
|
||||
Wetness = StateIndex.MetaWetness,
|
||||
HatState = StateIndex.MetaHatState,
|
||||
VisorState = StateIndex.MetaVisorState,
|
||||
WeaponState = StateIndex.MetaWeaponState,
|
||||
ModelId = StateIndex.MetaModelId,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MetaFlag : byte
|
||||
{
|
||||
Wetness = 0x01,
|
||||
HatState = 0x02,
|
||||
VisorState = 0x04,
|
||||
WeaponState = 0x08,
|
||||
}
|
||||
|
||||
public static class MetaExtensions
|
||||
{
|
||||
public static readonly IReadOnlyList<MetaIndex> AllRelevant =
|
||||
[MetaIndex.Wetness, MetaIndex.HatState, MetaIndex.VisorState, MetaIndex.WeaponState];
|
||||
|
||||
public const MetaFlag All = MetaFlag.Wetness | MetaFlag.HatState | MetaFlag.VisorState | MetaFlag.WeaponState;
|
||||
|
||||
public static MetaFlag ToFlag(this MetaIndex index)
|
||||
=> index switch
|
||||
{
|
||||
MetaIndex.Wetness => MetaFlag.Wetness,
|
||||
MetaIndex.HatState => MetaFlag.HatState,
|
||||
MetaIndex.VisorState => MetaFlag.VisorState,
|
||||
MetaIndex.WeaponState => MetaFlag.WeaponState,
|
||||
_ => (MetaFlag)byte.MaxValue,
|
||||
};
|
||||
|
||||
public static MetaIndex ToIndex(this MetaFlag index)
|
||||
=> index switch
|
||||
{
|
||||
MetaFlag.Wetness => MetaIndex.Wetness,
|
||||
MetaFlag.HatState => MetaIndex.HatState,
|
||||
MetaFlag.VisorState => MetaIndex.VisorState,
|
||||
MetaFlag.WeaponState => MetaIndex.WeaponState,
|
||||
_ => (MetaIndex)byte.MaxValue,
|
||||
};
|
||||
|
||||
public static string ToName(this MetaIndex index)
|
||||
=> index switch
|
||||
{
|
||||
MetaIndex.HatState => "Hat Visible",
|
||||
MetaIndex.VisorState => "Visor Toggled",
|
||||
MetaIndex.WeaponState => "Weapon Visible",
|
||||
MetaIndex.Wetness => "Force Wetness",
|
||||
_ => "Unknown Meta",
|
||||
};
|
||||
|
||||
public static string ToTooltip(this MetaIndex index)
|
||||
=> index switch
|
||||
{
|
||||
MetaIndex.HatState => "Hide or show the characters head gear.",
|
||||
MetaIndex.VisorState => "Toggle the visor state of the characters head gear.",
|
||||
MetaIndex.WeaponState => "Hide or show the characters weapons when not drawn.",
|
||||
MetaIndex.Wetness => "Force the character to be wet or not.",
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ namespace Glamourer.Events;
|
|||
/// <item>Parameter is any additional data depending on the type of change. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class DesignChanged()
|
||||
public sealed class DesignChanged()
|
||||
: EventWrapper<DesignChanged.Type, Design, object?, DesignChanged.Priority>(nameof(DesignChanged))
|
||||
{
|
||||
public enum Type
|
||||
|
|
@ -50,13 +50,19 @@ public sealed class DesignChanged()
|
|||
/// <summary> An existing design had an existing associated mod removed. Data is the Mod and its Settings [(Mod, ModSettings)]. </summary>
|
||||
RemovedMod,
|
||||
|
||||
/// <summary> An existing design had a link to a different design added, removed or moved. Data is null. </summary>
|
||||
ChangedLink,
|
||||
|
||||
/// <summary> An existing design had a customization changed. Data is the old value, the new value and the type [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
||||
Customize,
|
||||
|
||||
/// <summary> An existing design had its entire customize array changed. Data is the old array, the applied flags and the changed flags. [(CustomizeArray, CustomizeFlag, CustomizeFlag)]. </summary>
|
||||
EntireCustomize,
|
||||
|
||||
/// <summary> An existing design had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
||||
Equip,
|
||||
|
||||
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
|
||||
/// <summary> An existing design had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand, the new offhand (if any) and the new gauntlets (if any). [(EquipItem, EquipItem, EquipItem, EquipItem?, EquipItem?)]. </summary>
|
||||
Weapon,
|
||||
|
||||
/// <summary> An existing design had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
|
|
@ -92,6 +98,9 @@ public sealed class DesignChanged()
|
|||
|
||||
public enum Priority
|
||||
{
|
||||
/// <seealso cref="Designs.Links.DesignLinkManager.OnDesignChange"/>
|
||||
DesignLinkManager = 1,
|
||||
|
||||
/// <seealso cref="Automation.AutoDesignManager.OnDesignChange"/>
|
||||
AutoDesignManager = 1,
|
||||
|
||||
|
|
|
|||
|
|
@ -2,68 +2,59 @@ using Glamourer.Interop.Structs;
|
|||
using Glamourer.State;
|
||||
using OtterGui.Classes;
|
||||
|
||||
namespace Glamourer.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when a Design is edited in any way.
|
||||
/// <list type="number">
|
||||
/// <item>Parameter is the type of the change </item>
|
||||
/// <item>Parameter is the changed saved state. </item>
|
||||
/// <item>Parameter is the existing actors using this saved state. </item>
|
||||
/// <item>Parameter is any additional data depending on the type of change. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class StateChanged()
|
||||
: EventWrapper<StateChanged.Type, StateChanged.Source, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
|
||||
namespace Glamourer.Events
|
||||
{
|
||||
public enum Type
|
||||
/// <summary>
|
||||
/// Triggered when a Design is edited in any way.
|
||||
/// <list type="number">
|
||||
/// <item>Parameter is the type of the change </item>
|
||||
/// <item>Parameter is the changed saved state. </item>
|
||||
/// <item>Parameter is the existing actors using this saved state. </item>
|
||||
/// <item>Parameter is any additional data depending on the type of change. </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public sealed class StateChanged()
|
||||
: EventWrapper<StateChanged.Type, StateSource, ActorState, ActorData, object?, StateChanged.Priority>(nameof(StateChanged))
|
||||
{
|
||||
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
|
||||
Model,
|
||||
public enum Type
|
||||
{
|
||||
/// <summary> A characters saved state had the model id changed. This means everything may have changed. Data is the old model id and the new model id. [(uint, uint)] </summary>
|
||||
Model,
|
||||
|
||||
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
|
||||
EntireCustomize,
|
||||
/// <summary> A characters saved state had multiple customization values changed. TData is the old customize array and the applied changes. [(Customize, CustomizeFlag)] </summary>
|
||||
EntireCustomize,
|
||||
|
||||
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
||||
Customize,
|
||||
/// <summary> A characters saved state had a customization value changed. Data is the old value, the new value and the type. [(CustomizeValue, CustomizeValue, CustomizeIndex)]. </summary>
|
||||
Customize,
|
||||
|
||||
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
||||
Equip,
|
||||
/// <summary> A characters saved state had an equipment piece changed. Data is the old value, the new value and the slot [(EquipItem, EquipItem, EquipSlot)]. </summary>
|
||||
Equip,
|
||||
|
||||
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
|
||||
Weapon,
|
||||
/// <summary> A characters saved state had its weapons changed. Data is the old mainhand, the old offhand, the new mainhand and the new offhand [(EquipItem, EquipItem, EquipItem, EquipItem)]. </summary>
|
||||
Weapon,
|
||||
|
||||
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Stain,
|
||||
/// <summary> A characters saved state had a stain changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Stain,
|
||||
|
||||
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
||||
Crest,
|
||||
/// <summary> A characters saved state had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
|
||||
Crest,
|
||||
|
||||
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
||||
Parameter,
|
||||
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
|
||||
Parameter,
|
||||
|
||||
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
||||
Design,
|
||||
/// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
|
||||
Design,
|
||||
|
||||
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
|
||||
Reset,
|
||||
/// <summary> A characters saved state had its state reset to its game values. This means everything may have changed. Data is null. </summary>
|
||||
Reset,
|
||||
|
||||
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Other,
|
||||
}
|
||||
/// <summary> A characters saved state had a meta toggle changed. Data is the old stain id, the new stain id and the slot [(StainId, StainId, EquipSlot)]. </summary>
|
||||
Other,
|
||||
}
|
||||
|
||||
public enum Source : byte
|
||||
{
|
||||
Game,
|
||||
Manual,
|
||||
Fixed,
|
||||
Ipc,
|
||||
// Only used for CustomizeParameters.
|
||||
Pending,
|
||||
}
|
||||
|
||||
public enum Priority
|
||||
{
|
||||
GlamourerIpc = int.MinValue,
|
||||
public enum Priority
|
||||
{
|
||||
GlamourerIpc = int.MinValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,28 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.State;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
|
||||
public struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in DesignData data)
|
||||
{
|
||||
private IDesignEditor _editor;
|
||||
private object _object;
|
||||
public readonly CustomizeParameterFlag Flag = flag;
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
public bool AllowRevert;
|
||||
|
||||
public Action<CustomizeParameterValue> ValueSetter = null!;
|
||||
public Action<bool> ApplySetter = null!;
|
||||
public readonly void ChangeParameter(CustomizeParameterValue value)
|
||||
=> _editor.ChangeCustomizeParameter(_object, Flag, value, ApplySettings.Manual);
|
||||
|
||||
public readonly void ChangeApplyParameter(bool value)
|
||||
{
|
||||
var manager = (DesignManager)_editor;
|
||||
var design = (Design)_object;
|
||||
manager.ChangeApplyParameter(design, Flag, value);
|
||||
}
|
||||
|
||||
public CustomizeParameterValue CurrentValue = data.Parameters[flag];
|
||||
public CustomizeParameterValue GameValue;
|
||||
public bool CurrentApply;
|
||||
|
|
@ -21,19 +30,20 @@ public ref struct CustomizeParameterDrawData(CustomizeParameterFlag flag, in Des
|
|||
public static CustomizeParameterDrawData FromDesign(DesignManager manager, Design design, CustomizeParameterFlag flag)
|
||||
=> new(flag, design.DesignData)
|
||||
{
|
||||
_editor = manager,
|
||||
_object = design,
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
CurrentApply = design.DoApplyParameter(flag),
|
||||
ValueSetter = v => manager.ChangeCustomizeParameter(design, flag, v),
|
||||
ApplySetter = v => manager.ChangeApplyParameter(design, flag, v),
|
||||
};
|
||||
|
||||
public static CustomizeParameterDrawData FromState(StateManager manager, ActorState state, CustomizeParameterFlag flag)
|
||||
=> new(flag, state.ModelData)
|
||||
{
|
||||
_editor = manager,
|
||||
_object = state,
|
||||
Locked = state.IsLocked,
|
||||
DisplayApplication = false,
|
||||
ValueSetter = v => manager.ChangeCustomizeParameter(state, flag, v, StateChanged.Source.Manual),
|
||||
GameValue = state.BaseData.Parameters[flag],
|
||||
AllowRevert = true,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
using (_ = ImRaii.Disabled(data.Locked || noHighlights))
|
||||
{
|
||||
if (ImGui.ColorEdit3("##value", ref value, GetFlags()))
|
||||
data.ValueSetter(new CustomizeParameterValue(value));
|
||||
data.ChangeParameter(new CustomizeParameterValue(value));
|
||||
}
|
||||
|
||||
if (noHighlights)
|
||||
|
|
@ -215,7 +215,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
using (_ = ImRaii.Disabled(data.Locked))
|
||||
{
|
||||
if (ImGui.ColorEdit4("##value", ref value, GetFlags() | ImGuiColorEditFlags.AlphaPreviewHalf))
|
||||
data.ValueSetter(new CustomizeParameterValue(value));
|
||||
data.ChangeParameter(new CustomizeParameterValue(value));
|
||||
}
|
||||
|
||||
DrawRevert(data);
|
||||
|
|
@ -231,7 +231,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
using (_ = ImRaii.Disabled(data.Locked))
|
||||
{
|
||||
if (ImGui.InputFloat("##value", ref value, 0.1f, 0.5f))
|
||||
data.ValueSetter(new CustomizeParameterValue(value));
|
||||
data.ChangeParameter(new CustomizeParameterValue(value));
|
||||
}
|
||||
|
||||
DrawRevert(data);
|
||||
|
|
@ -247,7 +247,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
using (_ = ImRaii.Disabled(data.Locked))
|
||||
{
|
||||
if (ImGui.SliderFloat("##value", ref value, -100f, 300, "%.2f"))
|
||||
data.ValueSetter(new CustomizeParameterValue(value / 100f));
|
||||
data.ChangeParameter(new CustomizeParameterValue(value / 100f));
|
||||
ImGuiUtil.HoverTooltip("You can control-click this to enter arbitrary values by hand instead of dragging.");
|
||||
}
|
||||
|
||||
|
|
@ -262,7 +262,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
return;
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right) && ImGui.GetIO().KeyCtrl)
|
||||
data.ValueSetter(data.GameValue);
|
||||
data.ChangeParameter(data.GameValue);
|
||||
|
||||
ImGuiUtil.HoverTooltip("Hold Control and Right-click to revert to game values.");
|
||||
}
|
||||
|
|
@ -271,7 +271,7 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
{
|
||||
if (UiHelpers.DrawCheckbox("##apply", "Apply this custom parameter when applying the Design.", data.CurrentApply, out var enabled,
|
||||
data.Locked))
|
||||
data.ApplySetter(enabled);
|
||||
data.ChangeApplyParameter(enabled);
|
||||
}
|
||||
|
||||
private void DrawApplyAndLabel(in CustomizeParameterDrawData data)
|
||||
|
|
@ -310,6 +310,6 @@ public class CustomizeParameterDrawer(Configuration config, PaletteImport import
|
|||
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Paste.ToIconString(), new Vector2(ImGui.GetFrameHeight()),
|
||||
_copy.HasValue ? "Paste the currently copied value." : "No value copied yet.", locked || !_copy.HasValue, true))
|
||||
data.ValueSetter(_copy!.Value);
|
||||
data.ChangeParameter(_copy!.Value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using Dalamud.Interface.Utility.Raii;
|
|||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.State;
|
||||
|
|
@ -32,7 +32,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
private readonly ImRaii.Style _windowPadding = new();
|
||||
private readonly ImRaii.Color _windowColor = new();
|
||||
private DateTime _keyboardToggle = DateTime.UnixEpoch;
|
||||
private int _numButtons = 0;
|
||||
private int _numButtons;
|
||||
|
||||
public DesignQuickBar(Configuration config, DesignCombo designCombo, StateManager stateManager, IKeyState keyState,
|
||||
ObjectManager objects, AutoDesignApplier autoDesignApplier)
|
||||
|
|
@ -163,7 +163,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(state, design, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
public void DrawRevertButton(Vector2 buttonSize)
|
||||
|
|
@ -189,7 +189,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
|
||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.UndoAlt, buttonSize, tooltip, available);
|
||||
if (clicked)
|
||||
_stateManager.ResetState(state!, StateChanged.Source.Manual);
|
||||
_stateManager.ResetState(state!, StateSource.Manual);
|
||||
}
|
||||
|
||||
public void DrawRevertAutomationButton(Vector2 buttonSize)
|
||||
|
|
@ -257,7 +257,7 @@ public sealed class DesignQuickBar : Window, IDisposable
|
|||
ImGui.SameLine();
|
||||
var (clicked, _, _, state) = ResolveTarget(FontAwesomeIcon.Palette, buttonSize, tooltip, available);
|
||||
if (clicked)
|
||||
_stateManager.ResetAdvancedState(state!, StateChanged.Source.Manual);
|
||||
_stateManager.ResetAdvancedState(state!, StateSource.Manual);
|
||||
}
|
||||
|
||||
private (bool, ActorIdentifier, ActorData, ActorState?) ResolveTarget(FontAwesomeIcon icon, Vector2 buttonSize, string tooltip,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,45 @@
|
|||
using Dalamud.Game.Inventory;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Equipment;
|
||||
|
||||
public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||
public struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
||||
{
|
||||
public readonly EquipSlot Slot = slot;
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
public bool AllowRevert;
|
||||
private IDesignEditor _editor;
|
||||
private object _object;
|
||||
public readonly EquipSlot Slot = slot;
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
public bool AllowRevert;
|
||||
|
||||
public Action<EquipItem> ItemSetter = null!;
|
||||
public Action<StainId> StainSetter = null!;
|
||||
public Action<bool> ApplySetter = null!;
|
||||
public Action<bool> ApplyStainSetter = null!;
|
||||
public EquipItem CurrentItem = designData.Item(slot);
|
||||
public StainId CurrentStain = designData.Stain(slot);
|
||||
public EquipItem GameItem = default;
|
||||
public StainId GameStain = default;
|
||||
public bool CurrentApply;
|
||||
public bool CurrentApplyStain;
|
||||
public readonly void SetItem(EquipItem item)
|
||||
=> _editor.ChangeItem(_object, Slot, item, ApplySettings.Manual);
|
||||
|
||||
public readonly void SetStain(StainId stain)
|
||||
=> _editor.ChangeStain(_object, Slot, stain, ApplySettings.Manual);
|
||||
|
||||
public readonly void SetApplyItem(bool value)
|
||||
{
|
||||
var manager = (DesignManager)_editor;
|
||||
var design = (Design)_object;
|
||||
manager.ChangeApplyItem(design, Slot, value);
|
||||
}
|
||||
|
||||
public readonly void SetApplyStain(bool value)
|
||||
{
|
||||
var manager = (DesignManager)_editor;
|
||||
var design = (Design)_object;
|
||||
manager.ChangeApplyStain(design, Slot, value);
|
||||
}
|
||||
|
||||
public EquipItem CurrentItem = designData.Item(slot);
|
||||
public StainId CurrentStain = designData.Stain(slot);
|
||||
public EquipItem GameItem = default;
|
||||
public StainId GameStain = default;
|
||||
public bool CurrentApply;
|
||||
public bool CurrentApplyStain;
|
||||
|
||||
public readonly Gender CurrentGender = designData.Customize.Gender;
|
||||
public readonly Race CurrentRace = designData.Customize.Race;
|
||||
|
|
@ -31,12 +47,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
|||
public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot)
|
||||
=> new(slot, design.DesignData)
|
||||
{
|
||||
ItemSetter = slot.IsEquipment() || slot.IsAccessory()
|
||||
? i => manager.ChangeEquip(design, slot, i)
|
||||
: i => manager.ChangeWeapon(design, slot, i),
|
||||
StainSetter = i => manager.ChangeStain(design, slot, i),
|
||||
ApplySetter = b => manager.ChangeApplyEquip(design, slot, b),
|
||||
ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b),
|
||||
_editor = manager,
|
||||
_object = design,
|
||||
CurrentApply = design.DoApplyEquip(slot),
|
||||
CurrentApplyStain = design.DoApplyStain(slot),
|
||||
Locked = design.WriteProtected(),
|
||||
|
|
@ -46,8 +58,8 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
|
|||
public static EquipDrawData FromState(StateManager manager, ActorState state, EquipSlot slot)
|
||||
=> new(slot, state.ModelData)
|
||||
{
|
||||
ItemSetter = i => manager.ChangeItem(state, slot, i, StateChanged.Source.Manual),
|
||||
StainSetter = i => manager.ChangeStain(state, slot, i, StateChanged.Source.Manual),
|
||||
_editor = manager,
|
||||
_object = state,
|
||||
Locked = state.IsLocked,
|
||||
DisplayApplication = false,
|
||||
GameItem = state.BaseData.Item(slot),
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != current.CurrentItem.PrimaryId.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
|
||||
current.SetItem(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -214,7 +214,7 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue);
|
||||
if (newType.Id != current.CurrentItem.SecondaryId.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
|
||||
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -223,7 +223,7 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue);
|
||||
if (newVariant.Id != current.CurrentItem.Variant.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
|
||||
current.SetItem(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
|
||||
newVariant));
|
||||
}
|
||||
}
|
||||
|
|
@ -239,7 +239,7 @@ public class EquipmentDrawer
|
|||
|
||||
var newStainId = (StainId)Math.Clamp(stainId, 0, byte.MaxValue);
|
||||
if (newStainId != data.CurrentStain.Id)
|
||||
data.StainSetter(newStainId);
|
||||
data.SetStain(newStainId);
|
||||
}
|
||||
|
||||
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
|
||||
|
|
@ -252,7 +252,7 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != data.CurrentItem.PrimaryId.Id)
|
||||
data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
|
||||
data.SetItem(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -261,7 +261,7 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
|
||||
if (newVariant != data.CurrentItem.Variant)
|
||||
data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
|
||||
data.SetItem(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ public class EquipmentDrawer
|
|||
mainhand.CurrentItem.DrawIcon(_textures, _iconSize, EquipSlot.MainHand);
|
||||
var left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
||||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
using (ImRaii.Group())
|
||||
{
|
||||
DrawMainhand(ref mainhand, ref offhand, out var mainhandLabel, allWeapons, false, left);
|
||||
if (mainhand.DisplayApplication)
|
||||
|
|
@ -391,7 +391,7 @@ public class EquipmentDrawer
|
|||
var right = ImGui.IsItemClicked(ImGuiMouseButton.Right);
|
||||
left = ImGui.IsItemClicked(ImGuiMouseButton.Left);
|
||||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
using (ImRaii.Group())
|
||||
{
|
||||
DrawOffhand(mainhand, offhand, out var offhandLabel, false, right, left);
|
||||
if (offhand.DisplayApplication)
|
||||
|
|
@ -420,12 +420,12 @@ public class EquipmentDrawer
|
|||
: _stainCombo.Draw($"##stain{data.Slot}", stain.RgbaColor, stain.Name, found, stain.Gloss, _comboLength);
|
||||
if (change)
|
||||
if (_stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain))
|
||||
data.StainSetter(stain.RowIndex);
|
||||
data.SetStain(stain.RowIndex);
|
||||
else if (_stainCombo.CurrentSelection.Key == Stain.None.RowIndex)
|
||||
data.StainSetter(Stain.None.RowIndex);
|
||||
data.SetStain(Stain.None.RowIndex);
|
||||
|
||||
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out var id))
|
||||
data.StainSetter(Stain.None.RowIndex);
|
||||
if (ResetOrClear(data.Locked, false, data.AllowRevert, true, data.CurrentStain, data.GameStain, Stain.None.RowIndex, out _))
|
||||
data.SetStain(Stain.None.RowIndex);
|
||||
}
|
||||
|
||||
private void DrawItem(in EquipDrawData data, out string label, bool small, bool clear, bool open)
|
||||
|
|
@ -441,13 +441,13 @@ public class EquipmentDrawer
|
|||
var change = combo.Draw(data.CurrentItem.Name, data.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||
_requiredComboWidth);
|
||||
if (change)
|
||||
data.ItemSetter(combo.CurrentSelection);
|
||||
data.SetItem(combo.CurrentSelection);
|
||||
else if (combo.CustomVariant.Id > 0)
|
||||
data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
|
||||
data.SetItem(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
|
||||
|
||||
if (ResetOrClear(data.Locked, clear, data.AllowRevert, true, data.CurrentItem, data.GameItem, ItemManager.NothingItem(data.Slot),
|
||||
out var item))
|
||||
data.ItemSetter(item);
|
||||
data.SetItem(item);
|
||||
}
|
||||
|
||||
private static bool ResetOrClear<T>(bool locked, bool clicked, bool allowRevert, bool allowClear,
|
||||
|
|
@ -467,9 +467,9 @@ public class EquipmentDrawer
|
|||
(true, true, true) => ("Right-click to clear. Control and Right-Click to revert to game.", revertItem, true),
|
||||
(true, true, false) => ("Right-click to clear. Control and Right-Click to revert to game.", clearItem, true),
|
||||
(true, false, true) => ("Control and Right-Click to revert to game.", revertItem, true),
|
||||
(true, false, false) => ("Control and Right-Click to revert to game.", (T?)default, false),
|
||||
(true, false, false) => ("Control and Right-Click to revert to game.", default, false),
|
||||
(false, true, _) => ("Right-click to clear.", clearItem, true),
|
||||
(false, false, _) => (string.Empty, (T?)default, false),
|
||||
(false, false, _) => (string.Empty, default, false),
|
||||
};
|
||||
ImGuiUtil.HoverTooltip(tt);
|
||||
|
||||
|
|
@ -502,11 +502,11 @@ public class EquipmentDrawer
|
|||
|
||||
if (changedItem != null)
|
||||
{
|
||||
mainhand.ItemSetter(changedItem.Value);
|
||||
mainhand.SetItem(changedItem.Value);
|
||||
if (changedItem.Value.Type.ValidOffhand() != mainhand.CurrentItem.Type.ValidOffhand())
|
||||
{
|
||||
offhand.CurrentItem = _items.GetDefaultOffhand(changedItem.Value);
|
||||
offhand.ItemSetter(offhand.CurrentItem);
|
||||
offhand.SetItem(offhand.CurrentItem);
|
||||
}
|
||||
|
||||
mainhand.CurrentItem = changedItem.Value;
|
||||
|
|
@ -533,18 +533,18 @@ public class EquipmentDrawer
|
|||
UiHelpers.OpenCombo($"##{combo.Label}");
|
||||
if (combo.Draw(offhand.CurrentItem.Name, offhand.CurrentItem.ItemId, small ? _comboLength - ImGui.GetFrameHeight() : _comboLength,
|
||||
_requiredComboWidth))
|
||||
offhand.ItemSetter(combo.CurrentSelection);
|
||||
offhand.SetItem(combo.CurrentSelection);
|
||||
|
||||
var defaultOffhand = _items.GetDefaultOffhand(mainhand.CurrentItem);
|
||||
if (ResetOrClear(locked, open, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
|
||||
offhand.ItemSetter(item);
|
||||
if (ResetOrClear(locked, clear, offhand.AllowRevert, true, offhand.CurrentItem, offhand.GameItem, defaultOffhand, out var item))
|
||||
offhand.SetItem(item);
|
||||
}
|
||||
|
||||
private static void DrawApply(in EquipDrawData data)
|
||||
{
|
||||
if (UiHelpers.DrawCheckbox($"##apply{data.Slot}", "Apply this item when applying the Design.", data.CurrentApply, out var enabled,
|
||||
data.Locked))
|
||||
data.ApplySetter(enabled);
|
||||
data.SetApplyItem(enabled);
|
||||
}
|
||||
|
||||
private static void DrawApplyStain(in EquipDrawData data)
|
||||
|
|
@ -552,7 +552,7 @@ public class EquipmentDrawer
|
|||
if (UiHelpers.DrawCheckbox($"##applyStain{data.Slot}", "Apply this item when applying the Design.", data.CurrentApplyStain,
|
||||
out var enabled,
|
||||
data.Locked))
|
||||
data.ApplyStainSetter(enabled);
|
||||
data.SetApplyStain(enabled);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Glamourer.Events;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
|
|
@ -11,7 +11,7 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
public class PenumbraChangedItemTooltip : IDisposable
|
||||
public sealed class PenumbraChangedItemTooltip : IDisposable
|
||||
{
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly StateManager _stateManager;
|
||||
|
|
@ -111,24 +111,24 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
switch (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift)
|
||||
{
|
||||
case (false, false):
|
||||
Glamourer.Log.Information($"Applying {item.Name} to Right Finger.");
|
||||
Glamourer.Log.Debug($"Applying {item.Name} to Right Finger.");
|
||||
SetLastItem(EquipSlot.RFinger, item, state);
|
||||
_stateManager.ChangeItem(state, EquipSlot.RFinger, item, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, EquipSlot.RFinger, item, ApplySettings.Manual);
|
||||
break;
|
||||
case (false, true):
|
||||
Glamourer.Log.Information($"Applying {item.Name} to Left Finger.");
|
||||
Glamourer.Log.Debug($"Applying {item.Name} to Left Finger.");
|
||||
SetLastItem(EquipSlot.LFinger, item, state);
|
||||
_stateManager.ChangeItem(state, EquipSlot.LFinger, item, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, EquipSlot.LFinger, item, ApplySettings.Manual);
|
||||
break;
|
||||
case (true, false) when last.Valid:
|
||||
Glamourer.Log.Information($"Re-Applying {last.Name} to Right Finger.");
|
||||
Glamourer.Log.Debug($"Re-Applying {last.Name} to Right Finger.");
|
||||
SetLastItem(EquipSlot.RFinger, default, state);
|
||||
_stateManager.ChangeItem(state, EquipSlot.RFinger, last, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, EquipSlot.RFinger, last, ApplySettings.Manual);
|
||||
break;
|
||||
case (true, true) when _lastItems[EquipSlot.LFinger.ToIndex()].Valid:
|
||||
Glamourer.Log.Information($"Re-Applying {last.Name} to Left Finger.");
|
||||
Glamourer.Log.Debug($"Re-Applying {last.Name} to Left Finger.");
|
||||
SetLastItem(EquipSlot.LFinger, default, state);
|
||||
_stateManager.ChangeItem(state, EquipSlot.LFinger, last, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, EquipSlot.LFinger, last, ApplySettings.Manual);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -136,15 +136,15 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
default:
|
||||
if (ImGui.GetIO().KeyCtrl && last.Valid)
|
||||
{
|
||||
Glamourer.Log.Information($"Re-Applying {last.Name} to {slot.ToName()}.");
|
||||
Glamourer.Log.Debug($"Re-Applying {last.Name} to {slot.ToName()}.");
|
||||
SetLastItem(slot, default, state);
|
||||
_stateManager.ChangeItem(state, slot, last, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, slot, last, ApplySettings.Manual);
|
||||
}
|
||||
else
|
||||
{
|
||||
Glamourer.Log.Information($"Applying {item.Name} to {slot.ToName()}.");
|
||||
Glamourer.Log.Debug($"Applying {item.Name} to {slot.ToName()}.");
|
||||
SetLastItem(slot, item, state);
|
||||
_stateManager.ChangeItem(state, slot, item, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeItem(state, slot, item, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Dalamud.Plugin.Services;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -61,13 +60,13 @@ public class ActorPanel(
|
|||
|
||||
if (_importService.CreateDatTarget(out var dat))
|
||||
{
|
||||
_stateManager.ChangeCustomize(_state!, dat.Customize, CustomizeApplicationFlags, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeEntireCustomize(_state!, dat.Customize, CustomizeApplicationFlags, ApplySettings.Manual);
|
||||
Glamourer.Messager.NotificationMessage($"Applied games .dat file {dat.Description} customizations to {_state.Identifier}.",
|
||||
NotificationType.Success, false);
|
||||
}
|
||||
else if (_importService.CreateCharaTarget(out var designBase, out var name))
|
||||
{
|
||||
_stateManager.ApplyDesign(designBase, _state!, StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(_state!, designBase, ApplySettings.Manual);
|
||||
Glamourer.Messager.NotificationMessage($"Applied Anamnesis .chara file {name} to {_state.Identifier}.", NotificationType.Success,
|
||||
false);
|
||||
}
|
||||
|
|
@ -139,9 +138,9 @@ public class ActorPanel(
|
|||
return;
|
||||
|
||||
if (_customizationDrawer.Draw(_state!.ModelData.Customize, _state.IsLocked, _lockedRedraw))
|
||||
_stateManager.ChangeCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeEntireCustomize(_state, _customizationDrawer.Customize, _customizationDrawer.Changed, ApplySettings.Manual);
|
||||
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.Wetness, _stateManager, _state));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.Wetness, _stateManager, _state));
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +158,7 @@ public class ActorPanel(
|
|||
var data = EquipDrawData.FromState(_stateManager, _state!, slot);
|
||||
_equipmentDrawer.DrawEquip(data);
|
||||
if (usedAllStain)
|
||||
_stateManager.ChangeStain(_state, slot, newAllStain, StateChanged.Source.Manual);
|
||||
_stateManager.ChangeStain(_state, slot, newAllStain, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand);
|
||||
|
|
@ -187,21 +186,21 @@ public class ActorPanel(
|
|||
{
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.HatState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.VisorState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(MetaIndex.WeaponState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +263,7 @@ public class ActorPanel(
|
|||
}
|
||||
|
||||
if (turnHuman)
|
||||
_stateManager.TurnHuman(_state, StateChanged.Source.Manual);
|
||||
_stateManager.TurnHuman(_state, StateSource.Manual);
|
||||
}
|
||||
|
||||
private HeaderDrawer.Button SetFromClipboardButton()
|
||||
|
|
@ -316,9 +315,9 @@ public class ActorPanel(
|
|||
private void SaveDesignOpen()
|
||||
{
|
||||
ImGui.OpenPopup("Save as Design");
|
||||
_newName = _state!.Identifier.ToName();
|
||||
_newName = _state!.Identifier.ToName();
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
}
|
||||
|
||||
private void SaveDesignDrawPopup()
|
||||
|
|
@ -340,7 +339,7 @@ public class ActorPanel(
|
|||
var text = ImGui.GetClipboardText();
|
||||
var design = _converter.FromBase64(text, applyCustomize, applyGear, out _)
|
||||
?? throw new Exception("The clipboard did not contain valid data.");
|
||||
_stateManager.ApplyDesign(design, _state!, StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(_state!, design, ApplySettings.Manual with { MergeLinks = true });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -368,7 +367,7 @@ public class ActorPanel(
|
|||
{
|
||||
if (ImGuiUtil.DrawDisabledButton("Revert to Game", Vector2.Zero, "Revert the character to its actual state in the game.",
|
||||
_state!.IsLocked))
|
||||
_stateManager.ResetState(_state!, StateChanged.Source.Manual);
|
||||
_stateManager.ResetState(_state!, StateSource.Manual);
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton("Reapply State", Vector2.Zero, "Try to reapply the configured state if something went wrong.",
|
||||
|
|
@ -395,8 +394,8 @@ public class ActorPanel(
|
|||
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
|
||||
StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
|
||||
ApplySettings.Manual);
|
||||
}
|
||||
|
||||
private void DrawApplyToTarget()
|
||||
|
|
@ -413,7 +412,7 @@ public class ActorPanel(
|
|||
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
|
||||
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
|
||||
StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(state, _converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters),
|
||||
ApplySettings.Manual);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ public class SetPanel(
|
|||
var size = new Vector2(ImGui.GetFrameHeight());
|
||||
size.X += ImGuiHelpers.GlobalScale;
|
||||
|
||||
var (equipFlags, customizeFlags, _, _, _, _, _, _) = design.ApplyWhat();
|
||||
var (equipFlags, customizeFlags, _, _, _) = design.ApplyWhat();
|
||||
var sb = new StringBuilder();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
|
||||
{
|
||||
|
|
@ -369,13 +369,13 @@ public class SetPanel(
|
|||
private void DrawApplicationTypeBoxes(AutoDesignSet set, AutoDesign design, int autoDesignIndex, bool singleLine)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale));
|
||||
var newType = design.ApplicationType;
|
||||
var newType = design.Type;
|
||||
var newTypeInt = (uint)newType;
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
|
||||
{
|
||||
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
|
||||
newType = (AutoDesign.Type)newTypeInt;
|
||||
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All))
|
||||
newType = (ApplicationType)newTypeInt;
|
||||
}
|
||||
|
||||
style.Pop();
|
||||
|
|
@ -384,8 +384,8 @@ public class SetPanel(
|
|||
{
|
||||
void Box(int idx)
|
||||
{
|
||||
var (type, description) = Types[idx];
|
||||
var value = design.ApplicationType.HasFlag(type);
|
||||
var (type, description) = ApplicationTypeExtensions.Types[idx];
|
||||
var value = design.Type.HasFlag(type);
|
||||
if (ImGui.Checkbox($"##{(byte)type}", ref value))
|
||||
newType = value ? newType | type : newType & ~type;
|
||||
ImGuiUtil.HoverTooltip(description);
|
||||
|
|
@ -428,40 +428,20 @@ public class SetPanel(
|
|||
_manager.ChangeIdentifier(setIndex, _identifierDrawer.MannequinIdentifier);
|
||||
}
|
||||
|
||||
|
||||
private static readonly IReadOnlyList<(AutoDesign.Type, string)> Types = new[]
|
||||
private sealed class JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
|
||||
: FilterComboCache<JobGroup>(() => jobs.JobGroups.Values.ToList(), log)
|
||||
{
|
||||
(AutoDesign.Type.Customizations,
|
||||
"Apply all customization changes that are enabled in this design and that are valid in a fixed design and for the given race and gender."),
|
||||
(AutoDesign.Type.Armor, "Apply all armor piece changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(AutoDesign.Type.Accessories, "Apply all accessory changes that are enabled in this design and that are valid in a fixed design."),
|
||||
(AutoDesign.Type.GearCustomization, "Apply all dye and crest changes that are enabled in this design."),
|
||||
(AutoDesign.Type.Weapons, "Apply all weapon changes that are enabled in this design and that are valid with the current weapon worn."),
|
||||
};
|
||||
|
||||
private sealed class JobGroupCombo : FilterComboCache<JobGroup>
|
||||
{
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly JobService _jobs;
|
||||
|
||||
public JobGroupCombo(AutoDesignManager manager, JobService jobs, Logger log)
|
||||
: base(() => jobs.JobGroups.Values.ToList(), log)
|
||||
{
|
||||
_manager = manager;
|
||||
_jobs = jobs;
|
||||
}
|
||||
|
||||
public void Draw(AutoDesignSet set, AutoDesign design, int autoDesignIndex)
|
||||
{
|
||||
CurrentSelection = design.Jobs;
|
||||
CurrentSelectionIdx = _jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
|
||||
CurrentSelectionIdx = jobs.JobGroups.Values.IndexOf(j => j.Id == design.Jobs.Id);
|
||||
if (Draw("##JobGroups", design.Jobs.Name,
|
||||
"Select for which job groups this design should be applied.\nControl + Right-Click to set to all classes.",
|
||||
ImGui.GetContentRegionAvail().X, ImGui.GetTextLineHeightWithSpacing())
|
||||
&& CurrentSelectionIdx >= 0)
|
||||
_manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
|
||||
manager.ChangeJobCondition(set, autoDesignIndex, CurrentSelection);
|
||||
else if (ImGui.GetIO().KeyCtrl && ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||
_manager.ChangeJobCondition(set, autoDesignIndex, _jobs.JobGroups[1]);
|
||||
manager.ChangeJobCondition(set, autoDesignIndex, jobs.JobGroups[1]);
|
||||
}
|
||||
|
||||
protected override string ToString(JobGroup obj)
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
|
|||
ImGuiUtil.DrawTableColumn(state.Identifier.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Reset"))
|
||||
stateManager.ResetState(state, StateChanged.Source.Manual);
|
||||
stateManager.ResetState(state, StateSource.Manual);
|
||||
|
||||
ImGui.TableNextRow();
|
||||
|
||||
static void PrintRow<T>(string label, T actor, T model, StateChanged.Source source) where T : notnull
|
||||
static void PrintRow<T>(string label, T actor, T model, StateSource source) where T : notnull
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(label);
|
||||
ImGuiUtil.DrawTableColumn(actor.ToString()!);
|
||||
|
|
@ -70,44 +70,44 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
|
|||
return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
|
||||
}
|
||||
|
||||
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]);
|
||||
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state.Sources[MetaIndex.ModelId]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state[ActorState.MetaIndex.Wetness]);
|
||||
PrintRow("Wetness", state.BaseData.IsWet(), state.ModelData.IsWet(), state.Sources[MetaIndex.Wetness]);
|
||||
ImGui.TableNextRow();
|
||||
|
||||
if (state.BaseData.IsHuman && state.ModelData.IsHuman)
|
||||
{
|
||||
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state[ActorState.MetaIndex.HatState]);
|
||||
PrintRow("Hat Visible", state.BaseData.IsHatVisible(), state.ModelData.IsHatVisible(), state.Sources[MetaIndex.HatState]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Visor Toggled", state.BaseData.IsVisorToggled(), state.ModelData.IsVisorToggled(),
|
||||
state[ActorState.MetaIndex.VisorState]);
|
||||
state.Sources[MetaIndex.VisorState]);
|
||||
ImGui.TableNextRow();
|
||||
PrintRow("Weapon Visible", state.BaseData.IsWeaponVisible(), state.ModelData.IsWeaponVisible(),
|
||||
state[ActorState.MetaIndex.WeaponState]);
|
||||
state.Sources[MetaIndex.WeaponState]);
|
||||
ImGui.TableNextRow();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Prepend(EquipSlot.OffHand).Prepend(EquipSlot.MainHand))
|
||||
{
|
||||
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state[slot, false]);
|
||||
PrintRow(slot.ToName(), ItemString(state.BaseData, slot), ItemString(state.ModelData, slot), state.Sources[slot, false]);
|
||||
ImGuiUtil.DrawTableColumn(state.BaseData.Stain(slot).Id.ToString());
|
||||
ImGuiUtil.DrawTableColumn(state.ModelData.Stain(slot).Id.ToString());
|
||||
ImGuiUtil.DrawTableColumn(state[slot, true].ToString());
|
||||
ImGuiUtil.DrawTableColumn(state.Sources[slot, true].ToString());
|
||||
}
|
||||
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state[type]);
|
||||
PrintRow(type.ToDefaultName(), state.BaseData.Customize[type].Value, state.ModelData.Customize[type].Value, state.Sources[type]);
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
foreach (var crest in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]);
|
||||
PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state.Sources[crest]);
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
{
|
||||
PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]);
|
||||
PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state.Sources[flag]);
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDr
|
|||
foreach (var (design, designIdx) in set.Designs.WithIndex())
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn($"{design.Name(false)} ({designIdx})");
|
||||
ImGuiUtil.DrawTableColumn($"{design.ApplicationType} {design.Jobs.Name}");
|
||||
ImGuiUtil.DrawTableColumn($"{design.Type} {design.Jobs.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,8 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
|
|||
continue;
|
||||
|
||||
DrawDesign(design, _designFileSystem);
|
||||
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw,
|
||||
design.DoApplyHatVisible(),
|
||||
design.DoApplyVisorToggle(), design.DoApplyWeaponVisible(), design.WriteProtected());
|
||||
var base64 = DesignBase64Migration.CreateOldBase64(design.DesignData, design.ApplyEquip, design.ApplyCustomizeRaw, design.ApplyMeta,
|
||||
design.WriteProtected());
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGuiUtil.TextWrapped(base64);
|
||||
if (ImGui.IsItemClicked())
|
||||
|
|
@ -85,18 +84,12 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
|
|||
ImGuiUtil.DrawTableColumn(applyCrest ? "Apply" : "Keep");
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Hat Visible");
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.IsHatVisible().ToString());
|
||||
ImGuiUtil.DrawTableColumn(design.DoApplyHatVisible() ? "Apply" : "Keep");
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Visor Toggled");
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.IsVisorToggled().ToString());
|
||||
ImGuiUtil.DrawTableColumn(design.DoApplyVisorToggle() ? "Apply" : "Keep");
|
||||
ImGui.TableNextRow();
|
||||
ImGuiUtil.DrawTableColumn("Weapon Visible");
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.IsWeaponVisible().ToString());
|
||||
ImGuiUtil.DrawTableColumn(design.DoApplyWeaponVisible() ? "Apply" : "Keep");
|
||||
ImGui.TableNextRow();
|
||||
foreach (var index in MetaExtensions.AllRelevant)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(index.ToName());
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.GetMeta(index).ToString());
|
||||
ImGuiUtil.DrawTableColumn(design.DoApplyMeta(index) ? "Apply" : "Keep");
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Model ID");
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.ModelId.ToString());
|
||||
|
|
@ -111,9 +104,5 @@ public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _
|
|||
ImGuiUtil.DrawTableColumn(apply ? "Apply" : "Keep");
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Is Wet");
|
||||
ImGuiUtil.DrawTableColumn(design.DesignData.IsWet().ToString());
|
||||
ImGui.TableNextRow();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,10 +55,8 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGa
|
|||
|
||||
try
|
||||
{
|
||||
_parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var ah,
|
||||
out var av,
|
||||
out var aw);
|
||||
_restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, ah, av, aw, wp);
|
||||
_parse64 = DesignBase64Migration.MigrateBase64(_items, _humans, _base64, out var ef, out var cf, out var wp, out var meta);
|
||||
_restore = DesignBase64Migration.CreateOldBase64(in _parse64, ef, cf, meta, wp);
|
||||
_restoreBytes = Convert.FromBase64String(_restore);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using Dalamud.Interface.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
|
|
@ -67,9 +66,9 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled))
|
||||
{
|
||||
foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true))
|
||||
_state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual);
|
||||
_state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual);
|
||||
_state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state!, slot, item, stain, ApplySettings.Manual);
|
||||
_state.ChangeMetaState(state!, MetaIndex.VisorState, data.VisorToggled, ApplySettings.Manual);
|
||||
_state.ChangeEntireCustomize(state!, data.Customize, CustomizeFlagExtensions.All, ApplySettings.Manual);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
|
|||
|
|
@ -128,7 +128,11 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
{
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, "Create a new design with default configuration.", false,
|
||||
true))
|
||||
{
|
||||
_cloneDesign = null;
|
||||
_clipboardText = null;
|
||||
ImGui.OpenPopup("##NewDesign");
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportDesignButton(Vector2 size)
|
||||
|
|
@ -139,6 +143,7 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
|
||||
try
|
||||
{
|
||||
_cloneDesign = null;
|
||||
_clipboardText = ImGui.GetClipboardText();
|
||||
ImGui.OpenPopup("##NewDesign");
|
||||
}
|
||||
|
|
@ -156,7 +161,8 @@ public sealed class DesignFileSystemSelector : FileSystemSelector<Design, Design
|
|||
if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Clone.ToIconString(), size, tt, SelectedLeaf == null, true))
|
||||
return;
|
||||
|
||||
_cloneDesign = Selected!;
|
||||
_clipboardText = null;
|
||||
_cloneDesign = Selected!;
|
||||
ImGui.OpenPopup("##NewDesign");
|
||||
}
|
||||
|
||||
|
|
|
|||
217
Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs
Normal file
217
Glamourer/Gui/Tabs/DesignTab/DesignLinkDrawer.cs
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs.Links;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DesignTab;
|
||||
|
||||
public class DesignLinkDrawer(DesignLinkManager _linkManager, DesignFileSystemSelector _selector, DesignCombo _combo) : IUiService
|
||||
{
|
||||
private int _dragDropIndex = -1;
|
||||
private LinkOrder _dragDropOrder = LinkOrder.None;
|
||||
private int _dragDropTargetIndex = -1;
|
||||
private LinkOrder _dragDropTargetOrder = LinkOrder.None;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
using var header = ImRaii.CollapsingHeader("Design Links");
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
DrawList();
|
||||
}
|
||||
|
||||
private void MoveLink()
|
||||
{
|
||||
if (_dragDropTargetIndex < 0 || _dragDropIndex < 0)
|
||||
return;
|
||||
|
||||
if (_dragDropOrder is LinkOrder.Self)
|
||||
switch (_dragDropTargetOrder)
|
||||
{
|
||||
case LinkOrder.Before:
|
||||
for (var i = _selector.Selected!.Links.Before.Count - 1; i >= _dragDropTargetIndex; --i)
|
||||
_linkManager.MoveDesignLink(_selector.Selected!, i, LinkOrder.Before, 0, LinkOrder.After);
|
||||
break;
|
||||
case LinkOrder.After:
|
||||
for (var i = 0; i <= _dragDropTargetIndex; ++i)
|
||||
{
|
||||
_linkManager.MoveDesignLink(_selector.Selected!, 0, LinkOrder.After, _selector.Selected!.Links.Before.Count,
|
||||
LinkOrder.Before);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else if (_dragDropTargetOrder is LinkOrder.Self)
|
||||
_linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _selector.Selected!.Links.Before.Count,
|
||||
LinkOrder.Before);
|
||||
else
|
||||
_linkManager.MoveDesignLink(_selector.Selected!, _dragDropIndex, _dragDropOrder, _dragDropTargetIndex, _dragDropTargetOrder);
|
||||
|
||||
_dragDropIndex = -1;
|
||||
_dragDropTargetIndex = -1;
|
||||
_dragDropOrder = LinkOrder.None;
|
||||
_dragDropTargetOrder = LinkOrder.None;
|
||||
}
|
||||
|
||||
private void DrawList()
|
||||
{
|
||||
using var table = ImRaii.Table("table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersOuter);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
ImGui.TableSetupColumn("Del", ImGuiTableColumnFlags.WidthFixed, ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("Detail", ImGuiTableColumnFlags.WidthFixed,
|
||||
6 * ImGui.GetFrameHeight() + 5 * ImGui.GetStyle().ItemInnerSpacing.X);
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
|
||||
DrawSubList(_selector.Selected!.Links.Before, LinkOrder.Before);
|
||||
DrawSelf();
|
||||
DrawSubList(_selector.Selected!.Links.After, LinkOrder.After);
|
||||
DrawNew();
|
||||
MoveLink();
|
||||
}
|
||||
|
||||
private void DrawSelf()
|
||||
{
|
||||
using var id = ImRaii.PushId((int)LinkOrder.Self);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Selectable(_selector.IncognitoMode ? _selector.Selected!.Incognito : _selector.Selected!.Name.Text);
|
||||
DrawDragDrop(_selector.Selected!, LinkOrder.Self, 0);
|
||||
ImGui.TableNextColumn();
|
||||
}
|
||||
|
||||
private void DrawSubList(IReadOnlyList<DesignLink> list, LinkOrder order)
|
||||
{
|
||||
using var id = ImRaii.PushId((int)order);
|
||||
|
||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
id.Push(i);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
var delete = ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, "Delete this link.", false, true);
|
||||
var (design, flags) = list[i];
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Selectable(_selector.IncognitoMode ? design.Incognito : design.Name.Text);
|
||||
DrawDragDrop(design, order, i);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
DrawApplicationBoxes(i, order, flags);
|
||||
|
||||
if (delete)
|
||||
_linkManager.RemoveDesignLink(_selector.Selected!, i--, order);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawNew()
|
||||
{
|
||||
var buttonSize = new Vector2(ImGui.GetFrameHeight());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
_combo.Draw(ImGui.GetContentRegionAvail().X);
|
||||
ImGui.TableNextColumn();
|
||||
string ttBefore, ttAfter;
|
||||
bool canAddBefore, canAddAfter;
|
||||
if (_combo.Design == null)
|
||||
{
|
||||
ttAfter = ttBefore = "Select a design first.";
|
||||
canAddBefore = canAddAfter = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
canAddBefore = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.Before, out var error);
|
||||
ttBefore = canAddBefore
|
||||
? $"Add a link at the top of the list to {_combo.Design.Name}."
|
||||
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
|
||||
canAddAfter = LinkContainer.CanAddLink(_selector.Selected!, _combo.Design, LinkOrder.After, out error);
|
||||
ttAfter = canAddAfter
|
||||
? $"Add a link at the bottom of the list to {_combo.Design.Name}."
|
||||
: $"Can not add a link to {_combo.Design.Name}:\n{error}";
|
||||
}
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleUp.ToIconString(), buttonSize, ttBefore, !canAddBefore, true))
|
||||
{
|
||||
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.Before);
|
||||
_linkManager.MoveDesignLink(_selector.Selected!, _selector.Selected!.Links.Before.Count - 1, LinkOrder.Before, 0, LinkOrder.Before);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.ArrowCircleDown.ToIconString(), buttonSize, ttAfter, !canAddAfter, true))
|
||||
_linkManager.AddDesignLink(_selector.Selected!, _combo.Design!, LinkOrder.After);
|
||||
}
|
||||
|
||||
private void DrawDragDrop(Design design, LinkOrder order, int index)
|
||||
{
|
||||
using (var source = ImRaii.DragDropSource())
|
||||
{
|
||||
if (source)
|
||||
{
|
||||
ImGui.SetDragDropPayload("DraggingLink", IntPtr.Zero, 0);
|
||||
ImGui.TextUnformatted($"Reordering {design.Name}...");
|
||||
_dragDropIndex = index;
|
||||
_dragDropOrder = order;
|
||||
}
|
||||
}
|
||||
|
||||
using var target = ImRaii.DragDropTarget();
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
if (!ImGuiUtil.IsDropping("DraggingLink"))
|
||||
return;
|
||||
|
||||
_dragDropTargetIndex = index;
|
||||
_dragDropTargetOrder = order;
|
||||
}
|
||||
|
||||
private void DrawApplicationBoxes(int idx, LinkOrder order, ApplicationType current)
|
||||
{
|
||||
var newType = current;
|
||||
var newTypeInt = (uint)newType;
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale))
|
||||
{
|
||||
using var _ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value());
|
||||
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)ApplicationType.All))
|
||||
newType = (ApplicationType)newTypeInt;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip("Toggle all application modes at once.");
|
||||
|
||||
ImGui.SameLine();
|
||||
Box(0);
|
||||
ImGui.SameLine();
|
||||
Box(1);
|
||||
ImGui.SameLine();
|
||||
|
||||
Box(2);
|
||||
ImGui.SameLine();
|
||||
Box(3);
|
||||
ImGui.SameLine();
|
||||
Box(4);
|
||||
if (newType != current)
|
||||
_linkManager.ChangeApplicationType(_selector.Selected!, idx, order, current);
|
||||
return;
|
||||
|
||||
void Box(int i)
|
||||
{
|
||||
var (applicationType, description) = ApplicationTypeExtensions.Types[i];
|
||||
var value = applicationType.HasFlag(applicationType);
|
||||
if (ImGui.Checkbox($"##{(byte)applicationType}", ref value))
|
||||
newType = value ? newType | applicationType : newType & ~applicationType;
|
||||
ImGuiUtil.HoverTooltip(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
|
|
@ -31,7 +30,8 @@ public class DesignPanel(
|
|||
DesignConverter _converter,
|
||||
ImportService _importService,
|
||||
MultiDesignPanel _multiDesignPanel,
|
||||
CustomizeParameterDrawer _parameterDrawer)
|
||||
CustomizeParameterDrawer _parameterDrawer,
|
||||
DesignLinkDrawer _designLinkDrawer)
|
||||
{
|
||||
private readonly FileDialogManager _fileDialog = new();
|
||||
|
||||
|
|
@ -119,21 +119,21 @@ public class DesignPanel(
|
|||
{
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.HatState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.HatState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Head, _manager, _selector.Selected!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.VisorState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.VisorState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.Body, _manager, _selector.Selected!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.WeaponState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.WeaponState, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromDesign(CrestFlag.OffHand, _manager, _selector.Selected!));
|
||||
}
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ public class DesignPanel(
|
|||
_manager.ChangeCustomize(_selector.Selected, idx, _customizationDrawer.Customize[idx]);
|
||||
}
|
||||
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(ActorState.MetaIndex.Wetness, _manager, _selector.Selected!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromDesign(MetaIndex.Wetness, _manager, _selector.Selected!));
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
|
@ -166,6 +166,7 @@ public class DesignPanel(
|
|||
{
|
||||
if (!_config.UseAdvancedParameters)
|
||||
return;
|
||||
|
||||
using var h = ImRaii.CollapsingHeader("Advanced Customizations");
|
||||
if (!h)
|
||||
return;
|
||||
|
|
@ -255,24 +256,24 @@ public class DesignPanel(
|
|||
{
|
||||
var apply = bigChange ? ((EquipFlag)flags).HasFlag(slot.ToFlag()) : _selector.Selected!.DoApplyEquip(slot);
|
||||
if (ImGui.Checkbox($"Apply {slot.ToName()}", ref apply) || bigChange)
|
||||
_manager.ChangeApplyEquip(_selector.Selected!, slot, apply);
|
||||
_manager.ChangeApplyItem(_selector.Selected!, slot, apply);
|
||||
}
|
||||
}
|
||||
|
||||
ApplyEquip("Weapons", AutoDesign.WeaponFlags, false, new[]
|
||||
ApplyEquip("Weapons", ApplicationTypeExtensions.WeaponFlags, false, new[]
|
||||
{
|
||||
EquipSlot.MainHand,
|
||||
EquipSlot.OffHand,
|
||||
});
|
||||
|
||||
ImGui.NewLine();
|
||||
ApplyEquip("Armor", AutoDesign.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
|
||||
ApplyEquip("Armor", ApplicationTypeExtensions.ArmorFlags, false, EquipSlotExtensions.EquipmentSlots);
|
||||
|
||||
ImGui.NewLine();
|
||||
ApplyEquip("Accessories", AutoDesign.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
|
||||
ApplyEquip("Accessories", ApplicationTypeExtensions.AccessoryFlags, false, EquipSlotExtensions.AccessorySlots);
|
||||
|
||||
ImGui.NewLine();
|
||||
ApplyEquip("Dyes", AutoDesign.StainFlags, true,
|
||||
ApplyEquip("Dyes", ApplicationTypeExtensions.StainFlags, true,
|
||||
EquipSlotExtensions.FullSlots);
|
||||
|
||||
ImGui.NewLine();
|
||||
|
|
@ -285,28 +286,25 @@ public class DesignPanel(
|
|||
|
||||
private void DrawMetaApplication()
|
||||
{
|
||||
using var id = ImRaii.PushId("Meta");
|
||||
const uint all = 0x0Fu;
|
||||
var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00)
|
||||
| (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00)
|
||||
| (_selector.Selected!.DoApplyWeaponVisible() ? 0x04u : 0x00)
|
||||
| (_selector.Selected!.DoApplyWetness() ? 0x08u : 0x00);
|
||||
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
|
||||
var apply = bigChange ? (flags & 0x01) == 0x01 : _selector.Selected!.DoApplyHatVisible();
|
||||
if (ImGui.Checkbox("Apply Hat Visibility", ref apply) || bigChange)
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.HatState, apply);
|
||||
using var id = ImRaii.PushId("Meta");
|
||||
const uint all = (uint)MetaExtensions.All;
|
||||
var flags = (uint)_selector.Selected!.ApplyMeta;
|
||||
var bigChange = ImGui.CheckboxFlags("Apply All Meta Changes", ref flags, all);
|
||||
|
||||
apply = bigChange ? (flags & 0x02) == 0x02 : _selector.Selected!.DoApplyVisorToggle();
|
||||
if (ImGui.Checkbox("Apply Visor State", ref apply) || bigChange)
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.VisorState, apply);
|
||||
var labels = new[]
|
||||
{
|
||||
"Apply Hat Visibility",
|
||||
"Apply Visor State",
|
||||
"Apply Weapon Visibility",
|
||||
"Apply Wetness",
|
||||
};
|
||||
|
||||
apply = bigChange ? (flags & 0x04) == 0x04 : _selector.Selected!.DoApplyWeaponVisible();
|
||||
if (ImGui.Checkbox("Apply Weapon Visibility", ref apply) || bigChange)
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.WeaponState, apply);
|
||||
|
||||
apply = bigChange ? (flags & 0x08) == 0x08 : _selector.Selected!.DoApplyWetness();
|
||||
if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange)
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply);
|
||||
foreach (var (index, label) in MetaExtensions.AllRelevant.Zip(labels))
|
||||
{
|
||||
var apply = bigChange ? ((MetaFlag)flags).HasFlag(index.ToFlag()) : _selector.Selected!.DoApplyMeta(index);
|
||||
if (ImGui.Checkbox(label, ref apply) || bigChange)
|
||||
_manager.ChangeApplyMeta(_selector.Selected!, index, apply);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawParameterApplication()
|
||||
|
|
@ -370,6 +368,7 @@ public class DesignPanel(
|
|||
_designDetails.Draw();
|
||||
DrawApplicationRules();
|
||||
_modAssociations.Draw();
|
||||
_designLinkDrawer.Draw();
|
||||
}
|
||||
|
||||
private void DrawButtonRow()
|
||||
|
|
@ -439,7 +438,7 @@ public class DesignPanel(
|
|||
{
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
|
||||
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with { MergeLinks = true });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +457,7 @@ public class DesignPanel(
|
|||
{
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
|
||||
_state.ApplyDesign(state, _selector.Selected!, ApplySettings.Manual with {MergeLinks = true});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using Lumina.Data.Parsing.Scd;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -172,7 +170,7 @@ public class NpcPanel(
|
|||
_equipDrawer.DrawWeapons(mainhandData, offhandData, false);
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(MetaIndex.VisorState, _selector.Selection.VisorToggled));
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +200,7 @@ public class NpcPanel(
|
|||
{
|
||||
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
|
||||
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
|
||||
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -219,9 +217,9 @@ public class NpcPanel(
|
|||
|
||||
if (_state.GetOrCreate(id, data.Objects[0], out var state))
|
||||
{
|
||||
var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
|
||||
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters);
|
||||
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
var (applyGear, applyCustomize, _, _) = UiHelpers.ConvertKeysToFlags();
|
||||
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, 0, 0);
|
||||
_state.ApplyDesign(state, design, ApplySettings.Manual);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ public class SettingsTab(
|
|||
if (!ImGui.CollapsingHeader("Glamourer Behavior"))
|
||||
return;
|
||||
|
||||
Checkbox("Always Apply Entire Weapon for Mainhand",
|
||||
"When manually applying a mainhand item, will also apply a corresponding offhand and potentially gauntlets for certain fist weapons.",
|
||||
config.ChangeEntireItem, v => config.ChangeEntireItem = v);
|
||||
Checkbox("Use Replacement Gear for Gear Unavailable to Your Race or Gender",
|
||||
"Use different gender- and race-appropriate models as a substitute when detecting certain items not available for a characters current gender and race.",
|
||||
config.UseRestrictedGearProtection, v => config.UseRestrictedGearProtection = v);
|
||||
|
|
|
|||
|
|
@ -1,121 +1,115 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
public ref struct ToggleDrawData
|
||||
public struct ToggleDrawData
|
||||
{
|
||||
private IDesignEditor _editor = null!;
|
||||
private object _data = null!;
|
||||
private StateIndex _index;
|
||||
|
||||
public bool Locked;
|
||||
public bool DisplayApplication;
|
||||
|
||||
public bool CurrentValue;
|
||||
public bool CurrentApply;
|
||||
|
||||
public Action<bool> SetValue = null!;
|
||||
public Action<bool> SetApply = null!;
|
||||
|
||||
public string Label = string.Empty;
|
||||
public string Tooltip = string.Empty;
|
||||
|
||||
|
||||
public ToggleDrawData()
|
||||
{ }
|
||||
|
||||
public static ToggleDrawData FromDesign(ActorState.MetaIndex index, DesignManager manager, Design design)
|
||||
public readonly void SetValue(bool value)
|
||||
{
|
||||
var (label, value, apply, setValue, setApply) = index switch
|
||||
switch (_index.GetFlag())
|
||||
{
|
||||
ActorState.MetaIndex.HatState => ("Hat Visible", design.DesignData.IsHatVisible(), design.DoApplyHatVisible(),
|
||||
(Action<bool>)(b => manager.ChangeMeta(design, index, b)), (Action<bool>)(b => manager.ChangeApplyMeta(design, index, b))),
|
||||
ActorState.MetaIndex.VisorState => ("Visor Toggled", design.DesignData.IsVisorToggled(), design.DoApplyVisorToggle(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
ActorState.MetaIndex.WeaponState => ("Weapon Visible", design.DesignData.IsWeaponVisible(), design.DoApplyWeaponVisible(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
ActorState.MetaIndex.Wetness => ("Force Wetness", design.DesignData.IsWet(), design.DoApplyWetness(),
|
||||
b => manager.ChangeMeta(design, index, b), b => manager.ChangeApplyMeta(design, index, b)),
|
||||
_ => throw new Exception("Unsupported meta index."),
|
||||
};
|
||||
case MetaIndex index:
|
||||
_editor.ChangeMetaState(_data, index, value, ApplySettings.Manual);
|
||||
break;
|
||||
case CrestFlag flag:
|
||||
_editor.ChangeCrest(_data, flag, value, ApplySettings.Manual);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ToggleDrawData
|
||||
public readonly void SetApply(bool value)
|
||||
{
|
||||
var manager = (DesignManager)_editor;
|
||||
var design = (Design)_data;
|
||||
switch (_index.GetFlag())
|
||||
{
|
||||
Label = label,
|
||||
case MetaIndex index:
|
||||
manager.ChangeApplyMeta(design, index, value);
|
||||
break;
|
||||
case CrestFlag flag:
|
||||
manager.ChangeApplyCrest(design, flag, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static ToggleDrawData FromDesign(MetaIndex index, DesignManager manager, Design design)
|
||||
=> new()
|
||||
{
|
||||
_index = index,
|
||||
_editor = manager,
|
||||
_data = design,
|
||||
Label = index.ToName(),
|
||||
Tooltip = string.Empty,
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
CurrentValue = value,
|
||||
CurrentApply = apply,
|
||||
SetValue = setValue,
|
||||
SetApply = setApply,
|
||||
CurrentValue = design.DesignData.GetMeta(index),
|
||||
CurrentApply = design.DoApplyMeta(index),
|
||||
};
|
||||
|
||||
public static ToggleDrawData FromState(MetaIndex index, StateManager manager, ActorState state)
|
||||
=> new()
|
||||
{
|
||||
_index = index,
|
||||
_editor = manager,
|
||||
_data = state,
|
||||
Label = index.ToName(),
|
||||
Tooltip = index.ToTooltip(),
|
||||
Locked = state.IsLocked,
|
||||
CurrentValue = state.ModelData.GetMeta(index),
|
||||
};
|
||||
}
|
||||
|
||||
public static ToggleDrawData CrestFromDesign(CrestFlag slot, DesignManager manager, Design design)
|
||||
=> new()
|
||||
{
|
||||
_index = slot,
|
||||
_editor = manager,
|
||||
_data = design,
|
||||
Label = $"{slot.ToLabel()} Crest",
|
||||
Tooltip = string.Empty,
|
||||
Locked = design.WriteProtected(),
|
||||
DisplayApplication = true,
|
||||
CurrentValue = design.DesignData.Crest(slot),
|
||||
CurrentApply = design.DoApplyCrest(slot),
|
||||
SetValue = v => manager.ChangeCrest(design, slot, v),
|
||||
SetApply = v => manager.ChangeApplyCrest(design, slot, v),
|
||||
};
|
||||
|
||||
public static ToggleDrawData CrestFromState(CrestFlag slot, StateManager manager, ActorState state)
|
||||
=> new()
|
||||
{
|
||||
_index = slot,
|
||||
_editor = manager,
|
||||
_data = state,
|
||||
Label = $"{slot.ToLabel()} Crest",
|
||||
Tooltip = "Hide or show your free company crest on this piece of gear.",
|
||||
Locked = state.IsLocked,
|
||||
CurrentValue = state.ModelData.Crest(slot),
|
||||
SetValue = v => manager.ChangeCrest(state, slot, v, StateChanged.Source.Manual),
|
||||
};
|
||||
|
||||
public static ToggleDrawData FromState(ActorState.MetaIndex index, StateManager manager, ActorState state)
|
||||
{
|
||||
var (label, tooltip, value, setValue) = index switch
|
||||
public static ToggleDrawData FromValue(MetaIndex index, bool value)
|
||||
=> new()
|
||||
{
|
||||
ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear.", state.ModelData.IsHatVisible(),
|
||||
(Action<bool>)(b => manager.ChangeHatState(state, b, StateChanged.Source.Manual))),
|
||||
ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear.",
|
||||
state.ModelData.IsVisorToggled(),
|
||||
b => manager.ChangeVisorState(state, b, StateChanged.Source.Manual)),
|
||||
ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn.",
|
||||
state.ModelData.IsWeaponVisible(),
|
||||
b => manager.ChangeWeaponState(state, b, StateChanged.Source.Manual)),
|
||||
ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not.", state.ModelData.IsWet(),
|
||||
b => manager.ChangeWetness(state, b, StateChanged.Source.Manual)),
|
||||
_ => throw new Exception("Unsupported meta index."),
|
||||
};
|
||||
|
||||
return new ToggleDrawData
|
||||
{
|
||||
Label = label,
|
||||
Tooltip = tooltip,
|
||||
Locked = state.IsLocked,
|
||||
CurrentValue = value,
|
||||
SetValue = setValue,
|
||||
};
|
||||
}
|
||||
|
||||
public static ToggleDrawData FromValue(ActorState.MetaIndex index, bool value)
|
||||
{
|
||||
var (label, tooltip) = index switch
|
||||
{
|
||||
ActorState.MetaIndex.HatState => ("Hat Visible", "Hide or show the characters head gear."),
|
||||
ActorState.MetaIndex.VisorState => ("Visor Toggled", "Toggle the visor state of the characters head gear."),
|
||||
ActorState.MetaIndex.WeaponState => ("Weapon Visible", "Hide or show the characters weapons when not drawn."),
|
||||
ActorState.MetaIndex.Wetness => ("Force Wetness", "Force the character to be wet or not."),
|
||||
_ => throw new Exception("Unsupported meta index."),
|
||||
};
|
||||
return new ToggleDrawData
|
||||
{
|
||||
Label = label,
|
||||
Tooltip = tooltip,
|
||||
_index = index,
|
||||
Label = index.ToName(),
|
||||
Tooltip = index.ToTooltip(),
|
||||
Locked = true,
|
||||
CurrentValue = value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,24 +14,17 @@ public sealed class CharaFile
|
|||
public CustomizeFlag ApplyCustomize;
|
||||
public EquipFlag ApplyEquip;
|
||||
|
||||
public static CharaFile? ParseData(ItemManager items, string data, string? name = null)
|
||||
public static CharaFile ParseData(ItemManager items, string data, string? name = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jObj = JObject.Parse(data);
|
||||
SanityCheck(jObj);
|
||||
var ret = new CharaFile();
|
||||
ret.Data.SetDefaultEquipment(items);
|
||||
ret.Data.ModelId = ParseModelId(jObj);
|
||||
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
|
||||
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
|
||||
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
|
||||
return ret;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var jObj = JObject.Parse(data);
|
||||
SanityCheck(jObj);
|
||||
var ret = new CharaFile();
|
||||
ret.Data.SetDefaultEquipment(items);
|
||||
ret.Data.ModelId = ParseModelId(jObj);
|
||||
ret.Name = jObj["Nickname"]?.ToObject<string>() ?? name ?? "New Design";
|
||||
ret.ApplyCustomize = ParseCustomize(jObj, ref ret.Data.Customize);
|
||||
ret.ApplyEquip = ParseEquipment(items, jObj, ref ret.Data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static EquipFlag ParseEquipment(ItemManager items, JObject jObj, ref DesignData data)
|
||||
|
|
@ -282,9 +275,6 @@ public sealed class CharaFile
|
|||
|
||||
private static void SanityCheck(JObject jObj)
|
||||
{
|
||||
if (jObj["TypeName"]?.ToObject<string>() is not "Anamnesis Character File")
|
||||
throw new Exception("Wrong TypeName property set.");
|
||||
|
||||
var type = jObj["ObjectKind"]?.ToObject<string>();
|
||||
if (type is not "Player")
|
||||
throw new Exception($"ObjectKind {type} != Player is not supported.");
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using Dalamud.Game.Text;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -118,14 +118,14 @@ public class ContextMenuService : IDisposable
|
|||
return;
|
||||
|
||||
var slot = item.Type.ToSlot();
|
||||
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual);
|
||||
if (item.Type.ValidOffhand().IsOffhandType())
|
||||
{
|
||||
if (item.PrimaryId.Id is > 1600 and < 1651
|
||||
&& _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
|
||||
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual);
|
||||
if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
|
||||
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -142,14 +142,14 @@ public class ContextMenuService : IDisposable
|
|||
return;
|
||||
|
||||
var slot = item.Type.ToSlot();
|
||||
_state.ChangeEquip(state, slot, item, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, slot, item, 0, ApplySettings.Manual);
|
||||
if (item.Type.ValidOffhand().IsOffhandType())
|
||||
{
|
||||
if (item.PrimaryId.Id is > 1600 and < 1651
|
||||
&& _items.ItemData.TryGetValue(item.ItemId, EquipSlot.Hands, out var gauntlets))
|
||||
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, EquipSlot.Hands, gauntlets, 0, ApplySettings.Manual);
|
||||
if (_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand))
|
||||
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, StateChanged.Source.Manual);
|
||||
_state.ChangeEquip(state, EquipSlot.OffHand, offhand, 0, ApplySettings.Manual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ public class ImportService(CustomizeService _customizations, IDragDropManager _d
|
|||
{
|
||||
var text = File.ReadAllText(path);
|
||||
var file = CharaFile.CharaFile.ParseData(_items, text, Path.GetFileNameWithoutExtension(path));
|
||||
if (file == null)
|
||||
throw new Exception();
|
||||
|
||||
name = file.Name;
|
||||
design = new DesignBase(_customizations, file.Data, file.ApplyEquip, file.ApplyCustomize);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Plugin;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.State;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Services;
|
||||
|
||||
|
|
@ -41,9 +40,9 @@ public class PaletteImport(DalamudPluginInterface pluginInterface, DesignManager
|
|||
design.ApplyCustomize = 0;
|
||||
design.ApplyEquip = 0;
|
||||
design.ApplyCrest = 0;
|
||||
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.VisorState, false);
|
||||
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.HatState, false);
|
||||
designManager.ChangeApplyMeta(design, ActorState.MetaIndex.WeaponState, false);
|
||||
designManager.ChangeApplyMeta(design, MetaIndex.VisorState, false);
|
||||
designManager.ChangeApplyMeta(design, MetaIndex.HatState, false);
|
||||
designManager.ChangeApplyMeta(design, MetaIndex.WeaponState, false);
|
||||
foreach (var flag in flags.Iterate())
|
||||
{
|
||||
designManager.ChangeApplyParameter(design, flag, true);
|
||||
|
|
|
|||
|
|
@ -255,26 +255,26 @@ public class CommandService : IDisposable
|
|||
}
|
||||
|
||||
--designIdx;
|
||||
AutoDesign.Type applicationFlags = 0;
|
||||
ApplicationType applicationFlags = 0;
|
||||
if (split2.Length == 2)
|
||||
foreach (var character in split2[1])
|
||||
{
|
||||
switch (char.ToLowerInvariant(character))
|
||||
{
|
||||
case 'c':
|
||||
applicationFlags |= AutoDesign.Type.Customizations;
|
||||
applicationFlags |= ApplicationType.Customizations;
|
||||
break;
|
||||
case 'e':
|
||||
applicationFlags |= AutoDesign.Type.Armor;
|
||||
applicationFlags |= ApplicationType.Armor;
|
||||
break;
|
||||
case 'a':
|
||||
applicationFlags |= AutoDesign.Type.Accessories;
|
||||
applicationFlags |= ApplicationType.Accessories;
|
||||
break;
|
||||
case 'd':
|
||||
applicationFlags |= AutoDesign.Type.GearCustomization;
|
||||
applicationFlags |= ApplicationType.GearCustomization;
|
||||
break;
|
||||
case 'w':
|
||||
applicationFlags |= AutoDesign.Type.Weapons;
|
||||
applicationFlags |= ApplicationType.Weapons;
|
||||
break;
|
||||
default:
|
||||
_chat.Print(new SeStringBuilder().AddText("The value ").AddPurple(split2[1], true)
|
||||
|
|
@ -333,7 +333,7 @@ public class CommandService : IDisposable
|
|||
foreach (var identifier in identifiers)
|
||||
{
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
_stateManager.ResetState(state, StateChanged.Source.Manual);
|
||||
_stateManager.ResetState(state, StateSource.Manual);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -419,7 +419,7 @@ public class CommandService : IDisposable
|
|||
if (!_objects.TryGetValue(identifier, out var actors))
|
||||
{
|
||||
if (_stateManager.TryGetValue(identifier, out var state))
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -428,7 +428,7 @@ public class CommandService : IDisposable
|
|||
if (_stateManager.GetOrCreate(actor.GetIdentifier(_actors), actor, out var state))
|
||||
{
|
||||
ApplyModSettings(design, actor, applyMods);
|
||||
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
|
||||
_stateManager.ApplyDesign(state, design, ApplySettings.Manual with { MergeLinks = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -587,7 +587,7 @@ public class CommandService : IDisposable
|
|||
|
||||
if (Guid.TryParse(argument, out var guid))
|
||||
{
|
||||
design = _designManager.Designs.FirstOrDefault(d => d.Identifier == guid);
|
||||
design = _designManager.Designs.ByIdentifier(guid);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
namespace Glamourer.Services
|
||||
{
|
||||
internal interface IGamePathParser
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ public static class ServiceManagerA
|
|||
private static ServiceManager AddState(this ServiceManager services)
|
||||
=> services.AddSingleton<StateManager>()
|
||||
.AddSingleton<StateApplier>()
|
||||
.AddSingleton<StateEditor>()
|
||||
.AddSingleton<InternalStateEditor>()
|
||||
.AddSingleton<StateListener>()
|
||||
.AddSingleton<FunModule>();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
|
|
@ -11,15 +10,6 @@ namespace Glamourer.State;
|
|||
|
||||
public class ActorState
|
||||
{
|
||||
public enum MetaIndex
|
||||
{
|
||||
Wetness = EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices,
|
||||
HatState,
|
||||
VisorState,
|
||||
WeaponState,
|
||||
ModelId,
|
||||
}
|
||||
|
||||
public readonly ActorIdentifier Identifier;
|
||||
|
||||
public bool AllowsRedraw(ICondition condition)
|
||||
|
|
@ -77,47 +67,14 @@ public class ActorState
|
|||
=> Unlock(1337);
|
||||
|
||||
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
|
||||
private readonly StateChanged.Source[] _sources = Enumerable
|
||||
.Repeat(StateChanged.Source.Game,
|
||||
EquipFlagExtensions.NumEquipFlags
|
||||
+ CustomizationExtensions.NumIndices
|
||||
+ 5
|
||||
+ CrestExtensions.AllRelevantSet.Count
|
||||
+ CustomizeParameterExtensions.AllFlags.Count).ToArray();
|
||||
public StateSources Sources = new();
|
||||
|
||||
internal ActorState(ActorIdentifier identifier)
|
||||
=> Identifier = identifier.CreatePermanent();
|
||||
|
||||
public ref StateChanged.Source this[EquipSlot slot, bool stain]
|
||||
=> ref _sources[slot.ToIndex() + (stain ? EquipFlagExtensions.NumEquipFlags / 2 : 0)];
|
||||
|
||||
public ref StateChanged.Source this[CrestFlag slot]
|
||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + slot.ToInternalIndex()];
|
||||
|
||||
public ref StateChanged.Source this[CustomizeIndex type]
|
||||
=> ref _sources[EquipFlagExtensions.NumEquipFlags + (int)type];
|
||||
|
||||
public ref StateChanged.Source this[MetaIndex index]
|
||||
=> ref _sources[(int)index];
|
||||
|
||||
public ref StateChanged.Source this[CustomizeParameterFlag flag]
|
||||
=> ref _sources[
|
||||
EquipFlagExtensions.NumEquipFlags
|
||||
+ CustomizationExtensions.NumIndices + 5
|
||||
+ CrestExtensions.AllRelevantSet.Count
|
||||
+ flag.ToInternalIndex()];
|
||||
|
||||
public void RemoveFixedDesignSources()
|
||||
{
|
||||
for (var i = 0; i < _sources.Length; ++i)
|
||||
{
|
||||
if (_sources[i] is StateChanged.Source.Fixed)
|
||||
_sources[i] = StateChanged.Source.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
public CustomizeParameterFlag OnlyChangedParameters()
|
||||
=> CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b);
|
||||
=> CustomizeParameterExtensions.AllFlags.Where(f => Sources[f] is not StateSource.Game)
|
||||
.Aggregate((CustomizeParameterFlag)0, (a, b) => a | b);
|
||||
|
||||
public bool UpdateTerritory(ushort territory)
|
||||
{
|
||||
|
|
@ -127,4 +84,4 @@ public class ActorState
|
|||
LastTerritory = territory;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,8 +116,6 @@ public unsafe class FunModule : IDisposable
|
|||
SetRandomItem(slot, ref armor);
|
||||
break;
|
||||
case CodeService.CodeFlag.Elephants:
|
||||
SetElephant(slot, ref armor);
|
||||
break;
|
||||
case CodeService.CodeFlag.World when actor.Index != 0:
|
||||
KeepOldArmor(actor, slot, ref armor);
|
||||
break;
|
||||
|
|
@ -166,8 +164,9 @@ public unsafe class FunModule : IDisposable
|
|||
SetRandomItem(slot, ref armor[idx]);
|
||||
break;
|
||||
case CodeService.CodeFlag.Elephants:
|
||||
SetElephant(EquipSlot.Body, ref armor[1]);
|
||||
SetElephant(EquipSlot.Head, ref armor[0]);
|
||||
var stainId = ElephantStains[_rng.Next(0, ElephantStains.Length)];
|
||||
SetElephant(EquipSlot.Body, ref armor[1], stainId);
|
||||
SetElephant(EquipSlot.Head, ref armor[0], stainId);
|
||||
break;
|
||||
case CodeService.CodeFlag.World when actor.Index != 0:
|
||||
_worldSets.Apply(actor, _rng, armor);
|
||||
|
|
@ -216,12 +215,24 @@ public unsafe class FunModule : IDisposable
|
|||
armor.Variant = item.Variant;
|
||||
}
|
||||
|
||||
private static void SetElephant(EquipSlot slot, ref CharacterArmor armor)
|
||||
private static ReadOnlySpan<byte> ElephantStains
|
||||
=>
|
||||
[
|
||||
87, 87, 87, 87, 87, // Cherry Pink
|
||||
83, 83, 83, // Colibri Pink
|
||||
80, // Iris Purple
|
||||
85, // Regal Purple
|
||||
103, // Pastel Pink
|
||||
82, 82, 82, // Lotus Pink
|
||||
7, // Rose Pink
|
||||
];
|
||||
|
||||
private void SetElephant(EquipSlot slot, ref CharacterArmor armor, StainId stainId)
|
||||
{
|
||||
armor = slot switch
|
||||
{
|
||||
EquipSlot.Body => new CharacterArmor(6133, 1, 87),
|
||||
EquipSlot.Head => new CharacterArmor(6133, 1, 87),
|
||||
EquipSlot.Body => new CharacterArmor(6133, 1, stainId),
|
||||
EquipSlot.Head => new CharacterArmor(6133, 1, stainId),
|
||||
_ => armor,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
234
Glamourer/State/InternalStateEditor.cs
Normal file
234
Glamourer/State/InternalStateEditor.cs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class InternalStateEditor(
|
||||
CustomizeService customizations,
|
||||
HumanModelList humans,
|
||||
ItemManager items,
|
||||
GPoseService gPose,
|
||||
ICondition condition)
|
||||
{
|
||||
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
|
||||
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
|
||||
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateSource source,
|
||||
out uint oldModelId, uint key = 0)
|
||||
{
|
||||
oldModelId = state.ModelData.ModelId;
|
||||
|
||||
// TODO think about this.
|
||||
if (modelId != 0)
|
||||
return false;
|
||||
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
var oldIsHuman = state.ModelData.IsHuman;
|
||||
state.ModelData.IsHuman = humans.IsHuman(modelId);
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
if (oldModelId == modelId)
|
||||
return true;
|
||||
|
||||
state.ModelData.ModelId = modelId;
|
||||
if (oldIsHuman)
|
||||
return true;
|
||||
|
||||
if (!state.AllowsRedraw(condition))
|
||||
return false;
|
||||
|
||||
// Fix up everything else to make sure the result is a valid human.
|
||||
state.ModelData.Customize = CustomizeArray.Default;
|
||||
state.ModelData.SetDefaultEquipment(items);
|
||||
state.ModelData.SetHatVisible(true);
|
||||
state.ModelData.SetWeaponVisible(true);
|
||||
state.ModelData.SetVisor(false);
|
||||
state.Sources[MetaIndex.ModelId] = source;
|
||||
state.Sources[MetaIndex.HatState] = source;
|
||||
state.Sources[MetaIndex.WeaponState] = source;
|
||||
state.Sources[MetaIndex.VisorState] = source;
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
state.Sources[slot, true] = source;
|
||||
state.Sources[slot, false] = source;
|
||||
}
|
||||
|
||||
state.Sources[CustomizeIndex.Clan] = source;
|
||||
state.Sources[CustomizeIndex.Gender] = source;
|
||||
var set = customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
|
||||
state.Sources[index] = source;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!state.AllowsRedraw(condition))
|
||||
return false;
|
||||
|
||||
state.ModelData.LoadNonHuman(modelId, customize, equipData);
|
||||
state.Sources[MetaIndex.ModelId] = source;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateSource source,
|
||||
out CustomizeValue old, uint key = 0)
|
||||
{
|
||||
old = state.ModelData.Customize[idx];
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.Customize[idx] = value;
|
||||
state.Sources[idx] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to functions. </summary>
|
||||
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich,
|
||||
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
|
||||
{
|
||||
old = state.ModelData.Customize;
|
||||
changed = 0;
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
(var customize, var applied, changed) = customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
|
||||
if (changed == 0)
|
||||
return false;
|
||||
|
||||
state.ModelData.Customize = customize;
|
||||
applied |= changed;
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
if (applied.HasFlag(type.ToFlag()))
|
||||
state.Sources[type] = source(type);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to functions. </summary>
|
||||
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, Func<CustomizeIndex, bool> applyWhich,
|
||||
Func<CustomizeIndex, StateSource> source, out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
|
||||
{
|
||||
var apply = Enum.GetValues<CustomizeIndex>().Where(applyWhich).Aggregate((CustomizeFlag)0, (current, type) => current | type.ToFlag());
|
||||
return ChangeHumanCustomize(state, customizeInput, apply, source, out old, out changed, key);
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment without stain. </summary>
|
||||
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateSource source, out EquipItem oldItem, uint key = 0)
|
||||
{
|
||||
oldItem = state.ModelData.Item(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
// Can not change weapon type from expected type in state.
|
||||
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|
||||
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
|
||||
{
|
||||
if (!gPose.InGPose)
|
||||
return false;
|
||||
|
||||
var old = oldItem;
|
||||
gPose.AddActionOnLeave(() =>
|
||||
{
|
||||
if (old.Type == state.BaseData.Item(slot).Type)
|
||||
ChangeItem(state, slot, old, state.Sources[slot, false], out _, key);
|
||||
});
|
||||
}
|
||||
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state.Sources[slot, false] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment including stain. </summary>
|
||||
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateSource source, out EquipItem oldItem,
|
||||
out StainId oldStain, uint key = 0)
|
||||
{
|
||||
oldItem = state.ModelData.Item(slot);
|
||||
oldStain = state.ModelData.Stain(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
// Can not change weapon type from expected type in state.
|
||||
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|
||||
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
|
||||
{
|
||||
if (!gPose.InGPose)
|
||||
return false;
|
||||
|
||||
var old = oldItem;
|
||||
var oldS = oldStain;
|
||||
gPose.AddActionOnLeave(() =>
|
||||
{
|
||||
if (old.Type == state.BaseData.Item(slot).Type)
|
||||
ChangeEquip(state, slot, old, oldS, state.Sources[slot, false], out _, out _, key);
|
||||
});
|
||||
}
|
||||
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state.Sources[slot, false] = source;
|
||||
state.Sources[slot, true] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change only the stain of an equipment piece. </summary>
|
||||
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateSource source, out StainId oldStain, uint key = 0)
|
||||
{
|
||||
oldStain = state.ModelData.Stain(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state.Sources[slot, true] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateSource source, out bool oldCrest, uint key = 0)
|
||||
{
|
||||
oldCrest = state.ModelData.Crest(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetCrest(slot, crest);
|
||||
state.Sources[slot] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change the customize flags of a character. </summary>
|
||||
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateSource source,
|
||||
out CustomizeParameterValue oldValue, uint key = 0)
|
||||
{
|
||||
oldValue = state.ModelData.Parameters[flag];
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.Parameters.Set(flag, value);
|
||||
state.Sources[flag] = source;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeMetaState(ActorState state, MetaIndex index, bool value, StateSource source, out bool oldValue,
|
||||
uint key = 0)
|
||||
{
|
||||
oldValue = state.ModelData.GetMeta(index);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetMeta(index, value);
|
||||
state.Sources[index] = source;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
30
Glamourer/State/JobChangeState.cs
Normal file
30
Glamourer/State/JobChangeState.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed class JobChangeState : Dictionary<FullEquipType, (EquipItem, StateSource)>, IService
|
||||
{
|
||||
public ActorState? State { get; private set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
State = null;
|
||||
Clear();
|
||||
}
|
||||
|
||||
public bool HasState
|
||||
=> State != null;
|
||||
|
||||
public ActorIdentifier Identifier
|
||||
=> State?.Identifier ?? ActorIdentifier.Invalid;
|
||||
|
||||
public void Set(ActorState state, IEnumerable<(EquipItem, StateSource)> items)
|
||||
{
|
||||
foreach (var (item, source) in items.Where(p => p.Item1.Valid))
|
||||
TryAdd(item.Type, (item, source));
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using Glamourer.Events;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
|
|
@ -118,7 +118,7 @@ public class StateApplier(
|
|||
// If the source is not IPC we do not want to apply restrictions.
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeArmor(data, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc,
|
||||
ChangeArmor(data, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
|
||||
state.ModelData.IsHatVisible());
|
||||
|
||||
return data;
|
||||
|
|
@ -200,67 +200,44 @@ public class StateApplier(
|
|||
_weapon.LoadWeapon(actor, EquipSlot.OffHand, weapon.Weapon().With(stain));
|
||||
}
|
||||
|
||||
/// <summary> Change the visor state of actors only on the draw object. </summary>
|
||||
public void ChangeVisor(ActorData data, bool value)
|
||||
/// <summary> Change a meta state. </summary>
|
||||
public unsafe void ChangeMetaState(ActorData data, MetaIndex index, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
|
||||
_visor.SetVisorState(actor.Model, value);
|
||||
switch (index)
|
||||
{
|
||||
case MetaIndex.Wetness:
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
actor.AsCharacter->IsGPoseWet = value;
|
||||
return;
|
||||
}
|
||||
case MetaIndex.HatState:
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
_metaService.SetHatState(actor, value);
|
||||
return;
|
||||
}
|
||||
case MetaIndex.WeaponState:
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
_metaService.SetWeaponState(actor, value);
|
||||
return;
|
||||
}
|
||||
case MetaIndex.VisorState:
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.Model.IsHuman))
|
||||
_visor.SetVisorState(actor.Model, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeVisor(ActorData, bool)"/>
|
||||
public ActorData ChangeVisor(ActorState state, bool apply)
|
||||
/// <inheritdoc cref="ChangeMetaState(ActorData, MetaIndex, bool)"/>
|
||||
public ActorData ChangeMetaState(ActorState state, MetaIndex index, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeVisor(data, state.ModelData.IsVisorToggled());
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary> Change the forced wetness state on actors. </summary>
|
||||
public unsafe void ChangeWetness(ActorData data, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
actor.AsCharacter->IsGPoseWet = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeWetness(ActorData, bool)"/>
|
||||
public ActorData ChangeWetness(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeWetness(data, state.ModelData.IsWet());
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary> Change the hat-visibility state on actors. </summary>
|
||||
public void ChangeHatState(ActorData data, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
_metaService.SetHatState(actor, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeHatState(ActorData, bool)"/>
|
||||
public ActorData ChangeHatState(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeHatState(data, state.ModelData.IsHatVisible());
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary> Change the weapon-visibility state on actors. </summary>
|
||||
public void ChangeWeaponState(ActorData data, bool value)
|
||||
{
|
||||
foreach (var actor in data.Objects.Where(a => a.IsCharacter))
|
||||
_metaService.SetWeaponState(actor, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeWeaponState(ActorData, bool)"/>
|
||||
public ActorData ChangeWeaponState(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
if (apply)
|
||||
ChangeWeaponState(data, state.ModelData.IsWeaponVisible());
|
||||
ChangeMetaState(data, index, state.ModelData.GetMeta(index));
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -299,6 +276,47 @@ public class StateApplier(
|
|||
return data;
|
||||
}
|
||||
|
||||
/// <summary> Apply the entire state of an actor to all relevant actors, either via immediate redraw or piecewise. </summary>
|
||||
/// <param name="state"> The state to apply. </param>
|
||||
/// <param name="redraw"> Whether a redraw should be forced. </param>
|
||||
/// <param name="withLock"> Whether a temporary lock should be applied for the redraw. </param>
|
||||
/// <returns> The actor data for the actors who got changed. </returns>
|
||||
public ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
|
||||
{
|
||||
var actors = ChangeMetaState(state, MetaIndex.Wetness, true);
|
||||
if (redraw)
|
||||
{
|
||||
if (withLock)
|
||||
state.TempLock();
|
||||
ForceRedraw(actors);
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeCustomize(actors, state.ModelData.Customize);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
ChangeArmor(actors, slot, state.ModelData.Armor(slot), state.Sources[slot, false] is not StateSource.Ipc,
|
||||
state.ModelData.IsHatVisible());
|
||||
}
|
||||
|
||||
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
|
||||
ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
||||
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
|
||||
ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
|
||||
}
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
ChangeMetaState(actors, MetaIndex.HatState, state.ModelData.IsHatVisible());
|
||||
ChangeMetaState(actors, MetaIndex.WeaponState, state.ModelData.IsWeaponVisible());
|
||||
ChangeMetaState(actors, MetaIndex.VisorState, state.ModelData.IsVisorToggled());
|
||||
ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||
ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
||||
}
|
||||
|
||||
return actors;
|
||||
}
|
||||
|
||||
private ActorData GetData(ActorState state)
|
||||
{
|
||||
_objects.Update();
|
||||
|
|
|
|||
|
|
@ -1,246 +1,326 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Common.Math;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Vector3 = FFXIVClientStructs.FFXIV.Common.Math.Vector3;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class StateEditor
|
||||
public class StateEditor(
|
||||
InternalStateEditor editor,
|
||||
StateApplier applier,
|
||||
StateChanged stateChanged,
|
||||
JobChangeState jobChange,
|
||||
Configuration config,
|
||||
ItemManager items,
|
||||
DesignMerger merger) : IDesignEditor
|
||||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly GPoseService _gPose;
|
||||
private readonly ICondition _condition;
|
||||
protected readonly InternalStateEditor Editor = editor;
|
||||
protected readonly StateApplier Applier = applier;
|
||||
protected readonly StateChanged StateChanged = stateChanged;
|
||||
protected readonly Configuration Config = config;
|
||||
protected readonly ItemManager Items = items;
|
||||
|
||||
public StateEditor(CustomizeService customizations, HumanModelList humans, ItemManager items, GPoseService gPose, ICondition condition)
|
||||
{
|
||||
_customizations = customizations;
|
||||
_humans = humans;
|
||||
_items = items;
|
||||
_gPose = gPose;
|
||||
_condition = condition;
|
||||
}
|
||||
|
||||
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
|
||||
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
|
||||
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source,
|
||||
out uint oldModelId, uint key = 0)
|
||||
{
|
||||
oldModelId = state.ModelData.ModelId;
|
||||
|
||||
// TODO think about this.
|
||||
if (modelId != 0)
|
||||
return false;
|
||||
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
var oldIsHuman = state.ModelData.IsHuman;
|
||||
state.ModelData.IsHuman = _humans.IsHuman(modelId);
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
if (oldModelId == modelId)
|
||||
return true;
|
||||
|
||||
state.ModelData.ModelId = modelId;
|
||||
if (oldIsHuman)
|
||||
return true;
|
||||
|
||||
if (!state.AllowsRedraw(_condition))
|
||||
return false;
|
||||
|
||||
// Fix up everything else to make sure the result is a valid human.
|
||||
state.ModelData.Customize = CustomizeArray.Default;
|
||||
state.ModelData.SetDefaultEquipment(_items);
|
||||
state.ModelData.SetHatVisible(true);
|
||||
state.ModelData.SetWeaponVisible(true);
|
||||
state.ModelData.SetVisor(false);
|
||||
state[ActorState.MetaIndex.ModelId] = source;
|
||||
state[ActorState.MetaIndex.HatState] = source;
|
||||
state[ActorState.MetaIndex.WeaponState] = source;
|
||||
state[ActorState.MetaIndex.VisorState] = source;
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
state[slot, true] = source;
|
||||
state[slot, false] = source;
|
||||
}
|
||||
|
||||
state[CustomizeIndex.Clan] = source;
|
||||
state[CustomizeIndex.Gender] = source;
|
||||
var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(set.IsAvailable))
|
||||
state[index] = source;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!state.AllowsRedraw(_condition))
|
||||
return false;
|
||||
|
||||
state.ModelData.LoadNonHuman(modelId, customize, equipData);
|
||||
state[ActorState.MetaIndex.ModelId] = source;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public bool ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source,
|
||||
out CustomizeValue old, uint key = 0)
|
||||
{
|
||||
old = state.ModelData.Customize[idx];
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.Customize[idx] = value;
|
||||
state[idx] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to flags. </summary>
|
||||
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
|
||||
out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
|
||||
{
|
||||
old = state.ModelData.Customize;
|
||||
changed = 0;
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
(var customize, var applied, changed) = _customizations.Combine(state.ModelData.Customize, customizeInput, applyWhich, true);
|
||||
if (changed == 0)
|
||||
return false;
|
||||
|
||||
state.ModelData.Customize = customize;
|
||||
applied |= changed;
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
if (applied.HasFlag(type.ToFlag()))
|
||||
state[type] = source;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment without stain. </summary>
|
||||
public bool ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, out EquipItem oldItem, uint key = 0)
|
||||
{
|
||||
oldItem = state.ModelData.Item(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
// Can not change weapon type from expected type in state.
|
||||
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|
||||
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
|
||||
{
|
||||
if (!_gPose.InGPose)
|
||||
return false;
|
||||
|
||||
var old = oldItem;
|
||||
_gPose.AddActionOnLeave(() =>
|
||||
{
|
||||
if (old.Type == state.BaseData.Item(slot).Type)
|
||||
ChangeItem(state, slot, old, state[slot, false], out _, key);
|
||||
});
|
||||
}
|
||||
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state[slot, false] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment including stain. </summary>
|
||||
public bool ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, out EquipItem oldItem,
|
||||
out StainId oldStain, uint key = 0)
|
||||
{
|
||||
oldItem = state.ModelData.Item(slot);
|
||||
oldStain = state.ModelData.Stain(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
// Can not change weapon type from expected type in state.
|
||||
if (slot is EquipSlot.MainHand && item.Type != state.BaseData.MainhandType
|
||||
|| slot is EquipSlot.OffHand && item.Type != state.BaseData.OffhandType)
|
||||
{
|
||||
if (!_gPose.InGPose)
|
||||
return false;
|
||||
|
||||
var old = oldItem;
|
||||
var oldS = oldStain;
|
||||
_gPose.AddActionOnLeave(() =>
|
||||
{
|
||||
if (old.Type == state.BaseData.Item(slot).Type)
|
||||
ChangeEquip(state, slot, old, oldS, state[slot, false], out _, out _, key);
|
||||
});
|
||||
}
|
||||
|
||||
state.ModelData.SetItem(slot, item);
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, false] = source;
|
||||
state[slot, true] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change only the stain of an equipment piece. </summary>
|
||||
public bool ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, out StainId oldStain, uint key = 0)
|
||||
{
|
||||
oldStain = state.ModelData.Stain(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetStain(slot, stain);
|
||||
state[slot, true] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public bool ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, out bool oldCrest, uint key = 0)
|
||||
{
|
||||
oldCrest = state.ModelData.Crest(slot);
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.SetCrest(slot, crest);
|
||||
state[slot] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary> Change the customize flags of a character. </summary>
|
||||
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value, StateChanged.Source source,
|
||||
out CustomizeParameterValue oldValue, uint key = 0)
|
||||
{
|
||||
oldValue = state.ModelData.Parameters[flag];
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
|
||||
state.ModelData.Parameters.Set(flag, value);
|
||||
state[flag] = source;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
|
||||
/// <summary> Turn an actor to. </summary>
|
||||
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateSource source,
|
||||
uint key = 0)
|
||||
{
|
||||
(var setter, oldValue) = index switch
|
||||
if (!Editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ForceRedraw(state, source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeCustomize(state, idx, value, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Customize, settings.Source, state, actors, (old, value, idx));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeEntireCustomize(object data, in CustomizeArray customizeInput, CustomizeFlag apply, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeHumanCustomize(state, customizeInput, apply, _ => settings.Source, out var old, out var applied, settings.Key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ChangeCustomize(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.EntireCustomize, settings.Source, state, actors, (old, applied));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeItem(object data, EquipSlot slot, EquipItem item, ApplySettings settings = default)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||
var actors = type is StateChanged.Type.Equip
|
||||
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
|
||||
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
|
||||
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
|
||||
if (slot is EquipSlot.MainHand)
|
||||
ApplyMainhandPeriphery(state, item, settings);
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(type, settings.Source, state, actors, (old, item, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeEquip(object data, EquipSlot slot, EquipItem? item, StainId? stain, ApplySettings settings)
|
||||
{
|
||||
switch (item.HasValue, stain.HasValue)
|
||||
{
|
||||
ActorState.MetaIndex.Wetness => ((Func<bool, bool>)(v => state.ModelData.SetIsWet(v)), state.ModelData.IsWet()),
|
||||
ActorState.MetaIndex.HatState => ((Func<bool, bool>)(v => state.ModelData.SetHatVisible(v)), state.ModelData.IsHatVisible()),
|
||||
ActorState.MetaIndex.VisorState => ((Func<bool, bool>)(v => state.ModelData.SetVisor(v)), state.ModelData.IsVisorToggled()),
|
||||
ActorState.MetaIndex.WeaponState => ((Func<bool, bool>)(v => state.ModelData.SetWeaponVisible(v)),
|
||||
state.ModelData.IsWeaponVisible()),
|
||||
_ => throw new Exception("Invalid MetaIndex."),
|
||||
};
|
||||
case (false, false): return;
|
||||
case (true, false):
|
||||
ChangeItem(data, slot, item!.Value, settings);
|
||||
return;
|
||||
case (false, true):
|
||||
ChangeStain(data, slot, stain!.Value, settings);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.CanUnlock(key))
|
||||
return false;
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeEquip(state, slot, item ?? state.ModelData.Item(slot), stain ?? state.ModelData.Stain(slot), settings.Source,
|
||||
out var old, out var oldStain, settings.Key))
|
||||
return;
|
||||
|
||||
setter(value);
|
||||
state[index] = source;
|
||||
return true;
|
||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||
var actors = type is StateChanged.Type.Equip
|
||||
? Applier.ChangeArmor(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc)
|
||||
: Applier.ChangeWeapon(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc,
|
||||
item!.Value.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
|
||||
if (slot is EquipSlot.MainHand)
|
||||
ApplyMainhandPeriphery(state, item, settings);
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStain.Id} to {stain!.Value.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(type, settings.Source, state, actors, (old, item!.Value, slot));
|
||||
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (oldStain, stain!.Value, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeStain(object data, EquipSlot slot, StainId stain, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeStain(state, slot, stain, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ChangeStain(state, slot, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Stain, settings.Source, state, actors, (old, stain, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCrest(object data, CrestFlag slot, bool crest, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeCrest(state, slot, crest, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ChangeCrests(state, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Crest, settings.Source, state, actors, (old, crest, slot));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings settings)
|
||||
{
|
||||
if (data is not ActorState state)
|
||||
return;
|
||||
|
||||
// Also apply main color to highlights when highlights is off.
|
||||
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
|
||||
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, settings);
|
||||
|
||||
if (!Editor.ChangeParameter(state, flag, value, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var @new = state.ModelData.Parameters[flag];
|
||||
var actors = Applier.ChangeParameters(state, flag, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Parameter, settings.Source, state, actors, (old, @new, flag));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ChangeMetaState(object data, MetaIndex index, bool value, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeMetaState(state, index, value, settings.Source, out var old, settings.Key))
|
||||
return;
|
||||
|
||||
var actors = Applier.ChangeMetaState(state, index, settings.Source is StateSource.Manual or StateSource.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Other, settings.Source, state, actors, (old, value, MetaIndex.HatState));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ApplyDesign(object data, MergedDesign mergedDesign, ApplySettings settings)
|
||||
{
|
||||
var state = (ActorState)data;
|
||||
if (!Editor.ChangeModelId(state, mergedDesign.Design.DesignData.ModelId, mergedDesign.Design.DesignData.Customize,
|
||||
mergedDesign.Design.GetDesignDataRef().GetEquipmentPtr(), settings.Source, out var oldModelId, settings.Key))
|
||||
return;
|
||||
|
||||
var requiresRedraw = oldModelId != mergedDesign.Design.DesignData.ModelId || !mergedDesign.Design.DesignData.IsHuman;
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet.Where(mergedDesign.Design.DoApplyCrest))
|
||||
{
|
||||
if (!settings.RespectManual || state.Sources[slot] is not StateSource.Manual)
|
||||
Editor.ChangeCrest(state, slot, mergedDesign.Design.DesignData.Crest(slot), Source(slot),
|
||||
out _, settings.Key);
|
||||
}
|
||||
|
||||
var customizeFlags = mergedDesign.Design.ApplyCustomizeRaw;
|
||||
if (mergedDesign.Design.DoApplyCustomize(CustomizeIndex.Clan))
|
||||
customizeFlags |= CustomizeFlag.Race;
|
||||
|
||||
Func<CustomizeIndex, bool> applyWhich = settings.RespectManual
|
||||
? i => customizeFlags.HasFlag(i.ToFlag()) && state.Sources[i] is not StateSource.Manual
|
||||
: i => customizeFlags.HasFlag(i.ToFlag());
|
||||
|
||||
if (Editor.ChangeHumanCustomize(state, mergedDesign.Design.DesignData.Customize, applyWhich, i => Source(i), out _, out var changed,
|
||||
settings.Key))
|
||||
requiresRedraw |= changed.RequiresRedraw();
|
||||
|
||||
foreach (var parameter in mergedDesign.Design.ApplyParameters.Iterate())
|
||||
{
|
||||
if (settings.RespectManual && state.Sources[parameter] is StateSource.Manual or StateSource.Pending)
|
||||
continue;
|
||||
|
||||
var source = Source(parameter);
|
||||
if (source is StateSource.Manual)
|
||||
source = StateSource.Pending;
|
||||
Editor.ChangeParameter(state, parameter, mergedDesign.Design.DesignData.Parameters[parameter], source, out _, settings.Key);
|
||||
}
|
||||
|
||||
// Do not apply highlights from a design if highlights is unchecked.
|
||||
if (!state.ModelData.Customize.Highlights)
|
||||
Editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
|
||||
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
|
||||
state.Sources[CustomizeParameterFlag.HairDiffuse], out _, settings.Key);
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
if (mergedDesign.Design.DoApplyEquip(slot))
|
||||
if (!settings.RespectManual || state.Sources[slot, false] is not StateSource.Manual)
|
||||
Editor.ChangeItem(state, slot, mergedDesign.Design.DesignData.Item(slot),
|
||||
Source(slot.ToState()), out _, settings.Key);
|
||||
|
||||
if (mergedDesign.Design.DoApplyStain(slot))
|
||||
if (!settings.RespectManual || state.Sources[slot, true] is not StateSource.Manual)
|
||||
Editor.ChangeStain(state, slot, mergedDesign.Design.DesignData.Stain(slot),
|
||||
Source(slot.ToState(true)), out _, settings.Key);
|
||||
}
|
||||
|
||||
foreach (var weaponSlot in EquipSlotExtensions.WeaponSlots)
|
||||
{
|
||||
if (mergedDesign.Design.DoApplyStain(weaponSlot))
|
||||
if (!settings.RespectManual || state.Sources[weaponSlot, true] is not StateSource.Manual)
|
||||
Editor.ChangeStain(state, weaponSlot, mergedDesign.Design.DesignData.Stain(weaponSlot),
|
||||
Source(weaponSlot.ToState(true)), out _, settings.Key);
|
||||
|
||||
if (!mergedDesign.Design.DoApplyEquip(weaponSlot))
|
||||
continue;
|
||||
|
||||
if (settings.RespectManual && state.Sources[weaponSlot, false] is StateSource.Manual)
|
||||
continue;
|
||||
|
||||
var currentType = state.ModelData.Item(weaponSlot).Type;
|
||||
if (!settings.FromJobChange && mergedDesign.Weapons.TryGetValue(currentType, out var weapon))
|
||||
{
|
||||
var source = settings.UseSingleSource ? settings.Source :
|
||||
weapon.Item2 is StateSource.Game ? StateSource.Game : weapon.Item2;
|
||||
Editor.ChangeItem(state, weaponSlot, weapon.Item1, source, out _,
|
||||
settings.Key);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.FromJobChange)
|
||||
jobChange.Set(state, mergedDesign.Weapons.Values.Select(m =>
|
||||
(m.Item1, settings.UseSingleSource ? settings.Source :
|
||||
m.Item2 is StateSource.Game ? StateSource.Game : m.Item2)));
|
||||
|
||||
foreach (var meta in MetaExtensions.AllRelevant)
|
||||
{
|
||||
if (!settings.RespectManual || state.Sources[meta] is not StateSource.Manual)
|
||||
Editor.ChangeMetaState(state, meta, mergedDesign.Design.DesignData.GetMeta(meta), Source(meta), out _, settings.Key);
|
||||
}
|
||||
}
|
||||
|
||||
var actors = settings.Source is StateSource.Manual or StateSource.Ipc
|
||||
? Applier.ApplyAll(state, requiresRedraw, false)
|
||||
: ActorData.Invalid;
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
StateChanged.Invoke(StateChanged.Type.Design, state.Sources[MetaIndex.Wetness], state, actors, mergedDesign.Design);
|
||||
|
||||
return;
|
||||
|
||||
StateSource Source(StateIndex index)
|
||||
{
|
||||
if (settings.UseSingleSource)
|
||||
return settings.Source;
|
||||
|
||||
var source = mergedDesign.Sources[index];
|
||||
return source is StateSource.Game ? StateSource.Game : settings.Source;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
|
||||
{
|
||||
var merged = settings.MergeLinks && design is Design d
|
||||
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData, false, false)
|
||||
: new MergedDesign(design);
|
||||
|
||||
ApplyDesign(data, merged, settings with
|
||||
{
|
||||
FromJobChange = false,
|
||||
RespectManual = false,
|
||||
UseSingleSource = true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Apply offhand item and potentially gauntlets if configured. </summary>
|
||||
private void ApplyMainhandPeriphery(ActorState state, EquipItem? newMainhand, ApplySettings settings)
|
||||
{
|
||||
if (!Config.ChangeEntireItem || settings.Source is not StateSource.Manual)
|
||||
return;
|
||||
|
||||
var mh = newMainhand ?? state.ModelData.Item(EquipSlot.MainHand);
|
||||
var offhand = newMainhand != null ? Items.GetDefaultOffhand(mh) : state.ModelData.Item(EquipSlot.OffHand);
|
||||
if (offhand.Valid)
|
||||
ChangeEquip(state, EquipSlot.OffHand, offhand, state.ModelData.Stain(EquipSlot.OffHand), settings);
|
||||
|
||||
if (mh is { Type: FullEquipType.Fists } && Items.ItemData.Tertiary.TryGetValue(mh.ItemId, out var gauntlets))
|
||||
ChangeEquip(state, EquipSlot.Hands, newMainhand != null ? gauntlets : state.ModelData.Item(EquipSlot.Hands),
|
||||
state.ModelData.Stain(EquipSlot.Hands), settings);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
456
Glamourer/State/StateIndex.cs
Normal file
456
Glamourer/State/StateIndex.cs
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public readonly record struct StateIndex(int Value) : IEqualityOperators<StateIndex, StateIndex, bool>
|
||||
{
|
||||
public static readonly StateIndex Invalid = new(-1);
|
||||
|
||||
public static implicit operator StateIndex(EquipFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
EquipFlag.Head => new StateIndex(EquipHead),
|
||||
EquipFlag.Body => new StateIndex(EquipBody),
|
||||
EquipFlag.Hands => new StateIndex(EquipHands),
|
||||
EquipFlag.Legs => new StateIndex(EquipLegs),
|
||||
EquipFlag.Feet => new StateIndex(EquipFeet),
|
||||
EquipFlag.Ears => new StateIndex(EquipEars),
|
||||
EquipFlag.Neck => new StateIndex(EquipNeck),
|
||||
EquipFlag.Wrist => new StateIndex(EquipWrist),
|
||||
EquipFlag.RFinger => new StateIndex(EquipRFinger),
|
||||
EquipFlag.LFinger => new StateIndex(EquipLFinger),
|
||||
EquipFlag.Mainhand => new StateIndex(EquipMainhand),
|
||||
EquipFlag.Offhand => new StateIndex(EquipOffhand),
|
||||
EquipFlag.HeadStain => new StateIndex(StainHead),
|
||||
EquipFlag.BodyStain => new StateIndex(StainBody),
|
||||
EquipFlag.HandsStain => new StateIndex(StainHands),
|
||||
EquipFlag.LegsStain => new StateIndex(StainLegs),
|
||||
EquipFlag.FeetStain => new StateIndex(StainFeet),
|
||||
EquipFlag.EarsStain => new StateIndex(StainEars),
|
||||
EquipFlag.NeckStain => new StateIndex(StainNeck),
|
||||
EquipFlag.WristStain => new StateIndex(StainWrist),
|
||||
EquipFlag.RFingerStain => new StateIndex(StainRFinger),
|
||||
EquipFlag.LFingerStain => new StateIndex(StainLFinger),
|
||||
EquipFlag.MainhandStain => new StateIndex(StainMainhand),
|
||||
EquipFlag.OffhandStain => new StateIndex(StainOffhand),
|
||||
_ => Invalid,
|
||||
};
|
||||
|
||||
public static implicit operator StateIndex(CustomizeIndex index)
|
||||
=> index switch
|
||||
{
|
||||
CustomizeIndex.Race => new StateIndex(CustomizeRace),
|
||||
CustomizeIndex.Gender => new StateIndex(CustomizeGender),
|
||||
CustomizeIndex.BodyType => new StateIndex(CustomizeBodyType),
|
||||
CustomizeIndex.Height => new StateIndex(CustomizeHeight),
|
||||
CustomizeIndex.Clan => new StateIndex(CustomizeClan),
|
||||
CustomizeIndex.Face => new StateIndex(CustomizeFace),
|
||||
CustomizeIndex.Hairstyle => new StateIndex(CustomizeHairstyle),
|
||||
CustomizeIndex.Highlights => new StateIndex(CustomizeHighlights),
|
||||
CustomizeIndex.SkinColor => new StateIndex(CustomizeSkinColor),
|
||||
CustomizeIndex.EyeColorRight => new StateIndex(CustomizeEyeColorRight),
|
||||
CustomizeIndex.HairColor => new StateIndex(CustomizeHairColor),
|
||||
CustomizeIndex.HighlightsColor => new StateIndex(CustomizeHighlightsColor),
|
||||
CustomizeIndex.FacialFeature1 => new StateIndex(CustomizeFacialFeature1),
|
||||
CustomizeIndex.FacialFeature2 => new StateIndex(CustomizeFacialFeature2),
|
||||
CustomizeIndex.FacialFeature3 => new StateIndex(CustomizeFacialFeature3),
|
||||
CustomizeIndex.FacialFeature4 => new StateIndex(CustomizeFacialFeature4),
|
||||
CustomizeIndex.FacialFeature5 => new StateIndex(CustomizeFacialFeature5),
|
||||
CustomizeIndex.FacialFeature6 => new StateIndex(CustomizeFacialFeature6),
|
||||
CustomizeIndex.FacialFeature7 => new StateIndex(CustomizeFacialFeature7),
|
||||
CustomizeIndex.LegacyTattoo => new StateIndex(CustomizeLegacyTattoo),
|
||||
CustomizeIndex.TattooColor => new StateIndex(CustomizeTattooColor),
|
||||
CustomizeIndex.Eyebrows => new StateIndex(CustomizeEyebrows),
|
||||
CustomizeIndex.EyeColorLeft => new StateIndex(CustomizeEyeColorLeft),
|
||||
CustomizeIndex.EyeShape => new StateIndex(CustomizeEyeShape),
|
||||
CustomizeIndex.SmallIris => new StateIndex(CustomizeSmallIris),
|
||||
CustomizeIndex.Nose => new StateIndex(CustomizeNose),
|
||||
CustomizeIndex.Jaw => new StateIndex(CustomizeJaw),
|
||||
CustomizeIndex.Mouth => new StateIndex(CustomizeMouth),
|
||||
CustomizeIndex.Lipstick => new StateIndex(CustomizeLipstick),
|
||||
CustomizeIndex.LipColor => new StateIndex(CustomizeLipColor),
|
||||
CustomizeIndex.MuscleMass => new StateIndex(CustomizeMuscleMass),
|
||||
CustomizeIndex.TailShape => new StateIndex(CustomizeTailShape),
|
||||
CustomizeIndex.BustSize => new StateIndex(CustomizeBustSize),
|
||||
CustomizeIndex.FacePaint => new StateIndex(CustomizeFacePaint),
|
||||
CustomizeIndex.FacePaintReversed => new StateIndex(CustomizeFacePaintReversed),
|
||||
CustomizeIndex.FacePaintColor => new StateIndex(CustomizeFacePaintColor),
|
||||
_ => Invalid,
|
||||
};
|
||||
|
||||
public static implicit operator StateIndex(MetaIndex meta)
|
||||
=> new((int)meta);
|
||||
|
||||
public static implicit operator StateIndex(CrestFlag crest)
|
||||
=> crest switch
|
||||
{
|
||||
CrestFlag.OffHand => new StateIndex(CrestOffhand),
|
||||
CrestFlag.Head => new StateIndex(CrestHead),
|
||||
CrestFlag.Body => new StateIndex(CrestBody),
|
||||
_ => Invalid,
|
||||
};
|
||||
|
||||
public static implicit operator StateIndex(CustomizeParameterFlag param)
|
||||
=> param switch
|
||||
{
|
||||
CustomizeParameterFlag.SkinDiffuse => new StateIndex(ParamSkinDiffuse),
|
||||
CustomizeParameterFlag.MuscleTone => new StateIndex(ParamMuscleTone),
|
||||
CustomizeParameterFlag.SkinSpecular => new StateIndex(ParamSkinSpecular),
|
||||
CustomizeParameterFlag.LipDiffuse => new StateIndex(ParamLipDiffuse),
|
||||
CustomizeParameterFlag.HairDiffuse => new StateIndex(ParamHairDiffuse),
|
||||
CustomizeParameterFlag.HairSpecular => new StateIndex(ParamHairSpecular),
|
||||
CustomizeParameterFlag.HairHighlight => new StateIndex(ParamHairHighlight),
|
||||
CustomizeParameterFlag.LeftEye => new StateIndex(ParamLeftEye),
|
||||
CustomizeParameterFlag.RightEye => new StateIndex(ParamRightEye),
|
||||
CustomizeParameterFlag.FeatureColor => new StateIndex(ParamFeatureColor),
|
||||
CustomizeParameterFlag.FacePaintUvMultiplier => new StateIndex(ParamFacePaintUvMultiplier),
|
||||
CustomizeParameterFlag.FacePaintUvOffset => new StateIndex(ParamFacePaintUvOffset),
|
||||
CustomizeParameterFlag.DecalColor => new StateIndex(ParamDecalColor),
|
||||
_ => Invalid,
|
||||
};
|
||||
|
||||
public const int EquipHead = 0;
|
||||
public const int EquipBody = EquipHead + 1;
|
||||
public const int EquipHands = EquipBody + 1;
|
||||
public const int EquipLegs = EquipHands + 1;
|
||||
public const int EquipFeet = EquipLegs + 1;
|
||||
public const int EquipEars = EquipFeet + 1;
|
||||
public const int EquipNeck = EquipEars + 1;
|
||||
public const int EquipWrist = EquipNeck + 1;
|
||||
public const int EquipRFinger = EquipWrist + 1;
|
||||
public const int EquipLFinger = EquipRFinger + 1;
|
||||
public const int EquipMainhand = EquipLFinger + 1;
|
||||
public const int EquipOffhand = EquipMainhand + 1;
|
||||
|
||||
public const int StainHead = EquipOffhand + 1;
|
||||
public const int StainBody = StainHead + 1;
|
||||
public const int StainHands = StainBody + 1;
|
||||
public const int StainLegs = StainHands + 1;
|
||||
public const int StainFeet = StainLegs + 1;
|
||||
public const int StainEars = StainFeet + 1;
|
||||
public const int StainNeck = StainEars + 1;
|
||||
public const int StainWrist = StainNeck + 1;
|
||||
public const int StainRFinger = StainWrist + 1;
|
||||
public const int StainLFinger = StainRFinger + 1;
|
||||
public const int StainMainhand = StainLFinger + 1;
|
||||
public const int StainOffhand = StainMainhand + 1;
|
||||
|
||||
public const int CustomizeRace = StainOffhand + 1;
|
||||
public const int CustomizeGender = CustomizeRace + 1;
|
||||
public const int CustomizeBodyType = CustomizeGender + 1;
|
||||
public const int CustomizeHeight = CustomizeBodyType + 1;
|
||||
public const int CustomizeClan = CustomizeHeight + 1;
|
||||
public const int CustomizeFace = CustomizeClan + 1;
|
||||
public const int CustomizeHairstyle = CustomizeFace + 1;
|
||||
public const int CustomizeHighlights = CustomizeHairstyle + 1;
|
||||
public const int CustomizeSkinColor = CustomizeHighlights + 1;
|
||||
public const int CustomizeEyeColorRight = CustomizeSkinColor + 1;
|
||||
public const int CustomizeHairColor = CustomizeEyeColorRight + 1;
|
||||
public const int CustomizeHighlightsColor = CustomizeHairColor + 1;
|
||||
public const int CustomizeFacialFeature1 = CustomizeHighlightsColor + 1;
|
||||
public const int CustomizeFacialFeature2 = CustomizeFacialFeature1 + 1;
|
||||
public const int CustomizeFacialFeature3 = CustomizeFacialFeature2 + 1;
|
||||
public const int CustomizeFacialFeature4 = CustomizeFacialFeature3 + 1;
|
||||
public const int CustomizeFacialFeature5 = CustomizeFacialFeature4 + 1;
|
||||
public const int CustomizeFacialFeature6 = CustomizeFacialFeature5 + 1;
|
||||
public const int CustomizeFacialFeature7 = CustomizeFacialFeature6 + 1;
|
||||
public const int CustomizeLegacyTattoo = CustomizeFacialFeature7 + 1;
|
||||
public const int CustomizeTattooColor = CustomizeLegacyTattoo + 1;
|
||||
public const int CustomizeEyebrows = CustomizeTattooColor + 1;
|
||||
public const int CustomizeEyeColorLeft = CustomizeEyebrows + 1;
|
||||
public const int CustomizeEyeShape = CustomizeEyeColorLeft + 1;
|
||||
public const int CustomizeSmallIris = CustomizeEyeShape + 1;
|
||||
public const int CustomizeNose = CustomizeSmallIris + 1;
|
||||
public const int CustomizeJaw = CustomizeNose + 1;
|
||||
public const int CustomizeMouth = CustomizeJaw + 1;
|
||||
public const int CustomizeLipstick = CustomizeMouth + 1;
|
||||
public const int CustomizeLipColor = CustomizeLipstick + 1;
|
||||
public const int CustomizeMuscleMass = CustomizeLipColor + 1;
|
||||
public const int CustomizeTailShape = CustomizeMuscleMass + 1;
|
||||
public const int CustomizeBustSize = CustomizeTailShape + 1;
|
||||
public const int CustomizeFacePaint = CustomizeBustSize + 1;
|
||||
public const int CustomizeFacePaintReversed = CustomizeFacePaint + 1;
|
||||
public const int CustomizeFacePaintColor = CustomizeFacePaintReversed + 1;
|
||||
|
||||
public const int MetaWetness = CustomizeFacePaintColor + 1;
|
||||
public const int MetaHatState = MetaWetness + 1;
|
||||
public const int MetaVisorState = MetaHatState + 1;
|
||||
public const int MetaWeaponState = MetaVisorState + 1;
|
||||
public const int MetaModelId = MetaWeaponState + 1;
|
||||
|
||||
public const int CrestHead = MetaModelId + 1;
|
||||
public const int CrestBody = CrestHead + 1;
|
||||
public const int CrestOffhand = CrestBody + 1;
|
||||
|
||||
public const int ParamSkinDiffuse = CrestOffhand + 1;
|
||||
public const int ParamMuscleTone = ParamSkinDiffuse + 1;
|
||||
public const int ParamSkinSpecular = ParamMuscleTone + 1;
|
||||
public const int ParamLipDiffuse = ParamSkinSpecular + 1;
|
||||
public const int ParamHairDiffuse = ParamLipDiffuse + 1;
|
||||
public const int ParamHairSpecular = ParamHairDiffuse + 1;
|
||||
public const int ParamHairHighlight = ParamHairSpecular + 1;
|
||||
public const int ParamLeftEye = ParamHairHighlight + 1;
|
||||
public const int ParamRightEye = ParamLeftEye + 1;
|
||||
public const int ParamFeatureColor = ParamRightEye + 1;
|
||||
public const int ParamFacePaintUvMultiplier = ParamFeatureColor + 1;
|
||||
public const int ParamFacePaintUvOffset = ParamFacePaintUvMultiplier + 1;
|
||||
public const int ParamDecalColor = ParamFacePaintUvOffset + 1;
|
||||
|
||||
public const int Size = ParamDecalColor + 1;
|
||||
|
||||
public IEnumerable<StateIndex> All
|
||||
=> Enumerable.Range(0, Size - 1).Select(i => new StateIndex(i));
|
||||
|
||||
public bool GetApply(DesignBase data)
|
||||
=> GetFlag() switch
|
||||
{
|
||||
EquipFlag e => data.ApplyEquip.HasFlag(e),
|
||||
CustomizeFlag c => data.ApplyCustomize.HasFlag(c),
|
||||
MetaFlag m => data.ApplyMeta.HasFlag(m),
|
||||
CrestFlag c => data.ApplyCrest.HasFlag(c),
|
||||
CustomizeParameterFlag c => data.ApplyParameters.HasFlag(c),
|
||||
bool v => v,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public string ToName()
|
||||
=> GetFlag() switch
|
||||
{
|
||||
EquipFlag e => GetName(e),
|
||||
CustomizeFlag c => c.ToIndex().ToDefaultName(),
|
||||
MetaFlag m => m.ToIndex().ToName(),
|
||||
CrestFlag c => c.ToLabel(),
|
||||
CustomizeParameterFlag c => c.ToName(),
|
||||
bool v => "Model ID",
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
public object GetFlag()
|
||||
=> Value switch
|
||||
{
|
||||
EquipHead => EquipFlag.Head,
|
||||
EquipBody => EquipFlag.Body,
|
||||
EquipHands => EquipFlag.Hands,
|
||||
EquipLegs => EquipFlag.Legs,
|
||||
EquipFeet => EquipFlag.Feet,
|
||||
EquipEars => EquipFlag.Ears,
|
||||
EquipNeck => EquipFlag.Neck,
|
||||
EquipWrist => EquipFlag.Wrist,
|
||||
EquipRFinger => EquipFlag.RFinger,
|
||||
EquipLFinger => EquipFlag.LFinger,
|
||||
EquipMainhand => EquipFlag.Mainhand,
|
||||
EquipOffhand => EquipFlag.Offhand,
|
||||
|
||||
StainHead => EquipFlag.HeadStain,
|
||||
StainBody => EquipFlag.BodyStain,
|
||||
StainHands => EquipFlag.HandsStain,
|
||||
StainLegs => EquipFlag.LegsStain,
|
||||
StainFeet => EquipFlag.FeetStain,
|
||||
StainEars => EquipFlag.EarsStain,
|
||||
StainNeck => EquipFlag.NeckStain,
|
||||
StainWrist => EquipFlag.WristStain,
|
||||
StainRFinger => EquipFlag.RFingerStain,
|
||||
StainLFinger => EquipFlag.LFingerStain,
|
||||
StainMainhand => EquipFlag.MainhandStain,
|
||||
StainOffhand => EquipFlag.OffhandStain,
|
||||
|
||||
CustomizeRace => CustomizeFlag.Race,
|
||||
CustomizeGender => CustomizeFlag.Gender,
|
||||
CustomizeBodyType => CustomizeFlag.BodyType,
|
||||
CustomizeHeight => CustomizeFlag.Height,
|
||||
CustomizeClan => CustomizeFlag.Clan,
|
||||
CustomizeFace => CustomizeFlag.Face,
|
||||
CustomizeHairstyle => CustomizeFlag.Hairstyle,
|
||||
CustomizeHighlights => CustomizeFlag.Highlights,
|
||||
CustomizeSkinColor => CustomizeFlag.SkinColor,
|
||||
CustomizeEyeColorRight => CustomizeFlag.EyeColorRight,
|
||||
CustomizeHairColor => CustomizeFlag.HairColor,
|
||||
CustomizeHighlightsColor => CustomizeFlag.HighlightsColor,
|
||||
CustomizeFacialFeature1 => CustomizeFlag.FacialFeature1,
|
||||
CustomizeFacialFeature2 => CustomizeFlag.FacialFeature2,
|
||||
CustomizeFacialFeature3 => CustomizeFlag.FacialFeature3,
|
||||
CustomizeFacialFeature4 => CustomizeFlag.FacialFeature4,
|
||||
CustomizeFacialFeature5 => CustomizeFlag.FacialFeature5,
|
||||
CustomizeFacialFeature6 => CustomizeFlag.FacialFeature6,
|
||||
CustomizeFacialFeature7 => CustomizeFlag.FacialFeature7,
|
||||
CustomizeLegacyTattoo => CustomizeFlag.LegacyTattoo,
|
||||
CustomizeTattooColor => CustomizeFlag.TattooColor,
|
||||
CustomizeEyebrows => CustomizeFlag.Eyebrows,
|
||||
CustomizeEyeColorLeft => CustomizeFlag.EyeColorLeft,
|
||||
CustomizeEyeShape => CustomizeFlag.EyeShape,
|
||||
CustomizeSmallIris => CustomizeFlag.SmallIris,
|
||||
CustomizeNose => CustomizeFlag.Nose,
|
||||
CustomizeJaw => CustomizeFlag.Jaw,
|
||||
CustomizeMouth => CustomizeFlag.Mouth,
|
||||
CustomizeLipstick => CustomizeFlag.Lipstick,
|
||||
CustomizeLipColor => CustomizeFlag.LipColor,
|
||||
CustomizeMuscleMass => CustomizeFlag.MuscleMass,
|
||||
CustomizeTailShape => CustomizeFlag.TailShape,
|
||||
CustomizeBustSize => CustomizeFlag.BustSize,
|
||||
CustomizeFacePaint => CustomizeFlag.FacePaint,
|
||||
CustomizeFacePaintReversed => CustomizeFlag.FacePaintReversed,
|
||||
CustomizeFacePaintColor => CustomizeFlag.FacePaintColor,
|
||||
|
||||
MetaWetness => MetaFlag.Wetness,
|
||||
MetaHatState => MetaFlag.HatState,
|
||||
MetaVisorState => MetaFlag.VisorState,
|
||||
MetaWeaponState => MetaFlag.WeaponState,
|
||||
MetaModelId => true,
|
||||
|
||||
CrestHead => CrestFlag.Head,
|
||||
CrestBody => CrestFlag.Body,
|
||||
CrestOffhand => CrestFlag.OffHand,
|
||||
|
||||
ParamSkinDiffuse => CustomizeParameterFlag.SkinDiffuse,
|
||||
ParamMuscleTone => CustomizeParameterFlag.MuscleTone,
|
||||
ParamSkinSpecular => CustomizeParameterFlag.SkinSpecular,
|
||||
ParamLipDiffuse => CustomizeParameterFlag.LipDiffuse,
|
||||
ParamHairDiffuse => CustomizeParameterFlag.HairDiffuse,
|
||||
ParamHairSpecular => CustomizeParameterFlag.HairSpecular,
|
||||
ParamHairHighlight => CustomizeParameterFlag.HairHighlight,
|
||||
ParamLeftEye => CustomizeParameterFlag.LeftEye,
|
||||
ParamRightEye => CustomizeParameterFlag.RightEye,
|
||||
ParamFeatureColor => CustomizeParameterFlag.FeatureColor,
|
||||
ParamFacePaintUvMultiplier => CustomizeParameterFlag.FacePaintUvMultiplier,
|
||||
ParamFacePaintUvOffset => CustomizeParameterFlag.FacePaintUvOffset,
|
||||
ParamDecalColor => CustomizeParameterFlag.DecalColor,
|
||||
|
||||
_ => -1,
|
||||
};
|
||||
|
||||
public object? GetValue(in DesignData data)
|
||||
{
|
||||
return Value switch
|
||||
{
|
||||
EquipHead => data.Item(EquipSlot.Head),
|
||||
EquipBody => data.Item(EquipSlot.Body),
|
||||
EquipHands => data.Item(EquipSlot.Hands),
|
||||
EquipLegs => data.Item(EquipSlot.Legs),
|
||||
EquipFeet => data.Item(EquipSlot.Feet),
|
||||
EquipEars => data.Item(EquipSlot.Ears),
|
||||
EquipNeck => data.Item(EquipSlot.Neck),
|
||||
EquipWrist => data.Item(EquipSlot.Wrists),
|
||||
EquipRFinger => data.Item(EquipSlot.RFinger),
|
||||
EquipLFinger => data.Item(EquipSlot.LFinger),
|
||||
EquipMainhand => data.Item(EquipSlot.MainHand),
|
||||
EquipOffhand => data.Item(EquipSlot.OffHand),
|
||||
|
||||
StainHead => data.Stain(EquipSlot.Head),
|
||||
StainBody => data.Stain(EquipSlot.Body),
|
||||
StainHands => data.Stain(EquipSlot.Hands),
|
||||
StainLegs => data.Stain(EquipSlot.Legs),
|
||||
StainFeet => data.Stain(EquipSlot.Feet),
|
||||
StainEars => data.Stain(EquipSlot.Ears),
|
||||
StainNeck => data.Stain(EquipSlot.Neck),
|
||||
StainWrist => data.Stain(EquipSlot.Wrists),
|
||||
StainRFinger => data.Stain(EquipSlot.RFinger),
|
||||
StainLFinger => data.Stain(EquipSlot.LFinger),
|
||||
StainMainhand => data.Stain(EquipSlot.MainHand),
|
||||
StainOffhand => data.Stain(EquipSlot.OffHand),
|
||||
|
||||
CustomizeRace => data.Customize[CustomizeIndex.Race],
|
||||
CustomizeGender => data.Customize[CustomizeIndex.Gender],
|
||||
CustomizeBodyType => data.Customize[CustomizeIndex.BodyType],
|
||||
CustomizeHeight => data.Customize[CustomizeIndex.Height],
|
||||
CustomizeClan => data.Customize[CustomizeIndex.Clan],
|
||||
CustomizeFace => data.Customize[CustomizeIndex.Face],
|
||||
CustomizeHairstyle => data.Customize[CustomizeIndex.Hairstyle],
|
||||
CustomizeHighlights => data.Customize[CustomizeIndex.Highlights],
|
||||
CustomizeSkinColor => data.Customize[CustomizeIndex.SkinColor],
|
||||
CustomizeEyeColorRight => data.Customize[CustomizeIndex.EyeColorRight],
|
||||
CustomizeHairColor => data.Customize[CustomizeIndex.HairColor],
|
||||
CustomizeHighlightsColor => data.Customize[CustomizeIndex.HighlightsColor],
|
||||
CustomizeFacialFeature1 => data.Customize[CustomizeIndex.FacialFeature1],
|
||||
CustomizeFacialFeature2 => data.Customize[CustomizeIndex.FacialFeature2],
|
||||
CustomizeFacialFeature3 => data.Customize[CustomizeIndex.FacialFeature3],
|
||||
CustomizeFacialFeature4 => data.Customize[CustomizeIndex.FacialFeature4],
|
||||
CustomizeFacialFeature5 => data.Customize[CustomizeIndex.FacialFeature5],
|
||||
CustomizeFacialFeature6 => data.Customize[CustomizeIndex.FacialFeature6],
|
||||
CustomizeFacialFeature7 => data.Customize[CustomizeIndex.FacialFeature7],
|
||||
CustomizeLegacyTattoo => data.Customize[CustomizeIndex.LegacyTattoo],
|
||||
CustomizeTattooColor => data.Customize[CustomizeIndex.TattooColor],
|
||||
CustomizeEyebrows => data.Customize[CustomizeIndex.Eyebrows],
|
||||
CustomizeEyeColorLeft => data.Customize[CustomizeIndex.EyeColorLeft],
|
||||
CustomizeEyeShape => data.Customize[CustomizeIndex.EyeShape],
|
||||
CustomizeSmallIris => data.Customize[CustomizeIndex.SmallIris],
|
||||
CustomizeNose => data.Customize[CustomizeIndex.Nose],
|
||||
CustomizeJaw => data.Customize[CustomizeIndex.Jaw],
|
||||
CustomizeMouth => data.Customize[CustomizeIndex.Mouth],
|
||||
CustomizeLipstick => data.Customize[CustomizeIndex.Lipstick],
|
||||
CustomizeLipColor => data.Customize[CustomizeIndex.LipColor],
|
||||
CustomizeMuscleMass => data.Customize[CustomizeIndex.MuscleMass],
|
||||
CustomizeTailShape => data.Customize[CustomizeIndex.TailShape],
|
||||
CustomizeBustSize => data.Customize[CustomizeIndex.BustSize],
|
||||
CustomizeFacePaint => data.Customize[CustomizeIndex.FacePaint],
|
||||
CustomizeFacePaintReversed => data.Customize[CustomizeIndex.FacePaintReversed],
|
||||
CustomizeFacePaintColor => data.Customize[CustomizeIndex.FacePaintColor],
|
||||
|
||||
MetaWetness => data.GetMeta(MetaIndex.Wetness),
|
||||
MetaHatState => data.GetMeta(MetaIndex.HatState),
|
||||
MetaVisorState => data.GetMeta(MetaIndex.VisorState),
|
||||
MetaWeaponState => data.GetMeta(MetaIndex.WeaponState),
|
||||
MetaModelId => data.ModelId,
|
||||
|
||||
CrestHead => data.Crest(CrestFlag.Head),
|
||||
CrestBody => data.Crest(CrestFlag.Body),
|
||||
CrestOffhand => data.Crest(CrestFlag.OffHand),
|
||||
|
||||
ParamSkinDiffuse => data.Parameters[CustomizeParameterFlag.SkinDiffuse],
|
||||
ParamMuscleTone => data.Parameters[CustomizeParameterFlag.MuscleTone],
|
||||
ParamSkinSpecular => data.Parameters[CustomizeParameterFlag.SkinSpecular],
|
||||
ParamLipDiffuse => data.Parameters[CustomizeParameterFlag.LipDiffuse],
|
||||
ParamHairDiffuse => data.Parameters[CustomizeParameterFlag.HairDiffuse],
|
||||
ParamHairSpecular => data.Parameters[CustomizeParameterFlag.HairSpecular],
|
||||
ParamHairHighlight => data.Parameters[CustomizeParameterFlag.HairHighlight],
|
||||
ParamLeftEye => data.Parameters[CustomizeParameterFlag.LeftEye],
|
||||
ParamRightEye => data.Parameters[CustomizeParameterFlag.RightEye],
|
||||
ParamFeatureColor => data.Parameters[CustomizeParameterFlag.FeatureColor],
|
||||
ParamFacePaintUvMultiplier => data.Parameters[CustomizeParameterFlag.FacePaintUvMultiplier],
|
||||
ParamFacePaintUvOffset => data.Parameters[CustomizeParameterFlag.FacePaintUvOffset],
|
||||
ParamDecalColor => data.Parameters[CustomizeParameterFlag.DecalColor],
|
||||
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetName(EquipFlag flag)
|
||||
{
|
||||
var slot = flag.ToSlot(out var stain);
|
||||
return stain ? $"{slot.ToName()} Stain" : slot.ToName();
|
||||
}
|
||||
}
|
||||
|
||||
public static class StateExtensions
|
||||
{
|
||||
public static StateIndex ToState(this EquipSlot slot, bool stain = false)
|
||||
=> (slot, stain) switch
|
||||
{
|
||||
(EquipSlot.Head, true) => new StateIndex(StateIndex.EquipHead),
|
||||
(EquipSlot.Body, true) => new StateIndex(StateIndex.EquipBody),
|
||||
(EquipSlot.Hands, true) => new StateIndex(StateIndex.EquipHands),
|
||||
(EquipSlot.Legs, true) => new StateIndex(StateIndex.EquipLegs),
|
||||
(EquipSlot.Feet, true) => new StateIndex(StateIndex.EquipFeet),
|
||||
(EquipSlot.Ears, true) => new StateIndex(StateIndex.EquipEars),
|
||||
(EquipSlot.Neck, true) => new StateIndex(StateIndex.EquipNeck),
|
||||
(EquipSlot.Wrists, true) => new StateIndex(StateIndex.EquipWrist),
|
||||
(EquipSlot.RFinger, true) => new StateIndex(StateIndex.EquipRFinger),
|
||||
(EquipSlot.LFinger, true) => new StateIndex(StateIndex.EquipLFinger),
|
||||
(EquipSlot.MainHand, true) => new StateIndex(StateIndex.EquipMainhand),
|
||||
(EquipSlot.OffHand, true) => new StateIndex(StateIndex.EquipOffhand),
|
||||
(EquipSlot.Head, false) => new StateIndex(StateIndex.StainHead),
|
||||
(EquipSlot.Body, false) => new StateIndex(StateIndex.StainBody),
|
||||
(EquipSlot.Hands, false) => new StateIndex(StateIndex.StainHands),
|
||||
(EquipSlot.Legs, false) => new StateIndex(StateIndex.StainLegs),
|
||||
(EquipSlot.Feet, false) => new StateIndex(StateIndex.StainFeet),
|
||||
(EquipSlot.Ears, false) => new StateIndex(StateIndex.StainEars),
|
||||
(EquipSlot.Neck, false) => new StateIndex(StateIndex.StainNeck),
|
||||
(EquipSlot.Wrists, false) => new StateIndex(StateIndex.StainWrist),
|
||||
(EquipSlot.RFinger, false) => new StateIndex(StateIndex.StainRFinger),
|
||||
(EquipSlot.LFinger, false) => new StateIndex(StateIndex.StainLFinger),
|
||||
(EquipSlot.MainHand, false) => new StateIndex(StateIndex.StainMainhand),
|
||||
(EquipSlot.OffHand, false) => new StateIndex(StateIndex.StainOffhand),
|
||||
_ => StateIndex.Invalid,
|
||||
};
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ using Dalamud.Game.ClientState.Conditions;
|
|||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.GameData;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Glamourer.Designs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
@ -138,7 +139,7 @@ public class StateListener : IDisposable
|
|||
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||
}
|
||||
|
||||
private unsafe void OnCustomizeChange(Model model, ref CustomizeArray customize)
|
||||
private void OnCustomizeChange(Model model, ref CustomizeArray customize)
|
||||
{
|
||||
if (!model.IsHuman)
|
||||
return;
|
||||
|
|
@ -163,21 +164,21 @@ public class StateListener : IDisposable
|
|||
var model = state.ModelData.Customize;
|
||||
if (customize.Gender != model.Gender || customize.Clan != model.Clan)
|
||||
{
|
||||
_manager.ChangeCustomize(state, in customize, CustomizeFlagExtensions.AllRelevant, StateChanged.Source.Game);
|
||||
_manager.ChangeEntireCustomize(state, in customize, CustomizeFlagExtensions.All, ApplySettings.Game);
|
||||
return;
|
||||
}
|
||||
|
||||
var set = _customizations.Manager.GetSet(model.Clan, model.Gender);
|
||||
foreach (var index in CustomizationExtensions.AllBasic)
|
||||
{
|
||||
if (state[index] is not StateChanged.Source.Fixed)
|
||||
if (state.Sources[index] is not StateSource.Fixed)
|
||||
{
|
||||
var newValue = customize[index];
|
||||
var oldValue = model[index];
|
||||
if (newValue != oldValue)
|
||||
{
|
||||
if (set.Validate(index, newValue, out _, model.Face))
|
||||
_manager.ChangeCustomize(state, index, newValue, StateChanged.Source.Game);
|
||||
_manager.ChangeCustomize(state, index, newValue, ApplySettings.Game);
|
||||
else
|
||||
customize[index] = oldValue;
|
||||
}
|
||||
|
|
@ -213,7 +214,7 @@ public class StateListener : IDisposable
|
|||
&& _manager.TryGetValue(identifier, out var state))
|
||||
{
|
||||
HandleEquipSlot(actor, state, slot, ref armor);
|
||||
locked = state[slot, false] is StateChanged.Source.Ipc;
|
||||
locked = state.Sources[slot, false] is StateSource.Ipc;
|
||||
}
|
||||
|
||||
_funModule.ApplyFunToSlot(actor, ref armor, slot);
|
||||
|
|
@ -240,10 +241,10 @@ public class StateListener : IDisposable
|
|||
continue;
|
||||
|
||||
var changed = changedItem.Weapon(stain);
|
||||
if (current.Value == changed.Value && state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
if (current.Value == changed.Value && state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
{
|
||||
_manager.ChangeItem(state, slot, currentItem, StateChanged.Source.Game);
|
||||
_manager.ChangeStain(state, slot, current.Stain, StateChanged.Source.Game);
|
||||
_manager.ChangeItem(state, slot, currentItem, ApplySettings.Game);
|
||||
_manager.ChangeStain(state, slot, current.Stain, ApplySettings.Game);
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
|
|
@ -251,7 +252,7 @@ public class StateListener : IDisposable
|
|||
_applier.ChangeWeapon(objects, slot, currentItem, stain);
|
||||
break;
|
||||
default:
|
||||
_applier.ChangeArmor(objects, slot, current.ToArmor(), state[slot, false] is not StateChanged.Source.Ipc,
|
||||
_applier.ChangeArmor(objects, slot, current.ToArmor(), state.Sources[slot, false] is not StateSource.Ipc,
|
||||
state.ModelData.IsHatVisible());
|
||||
break;
|
||||
}
|
||||
|
|
@ -285,13 +286,13 @@ public class StateListener : IDisposable
|
|||
// Do nothing. But this usually can not happen because the hooked function also writes to game objects later.
|
||||
case UpdateState.Transformed: break;
|
||||
case UpdateState.Change:
|
||||
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
|
||||
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
||||
else
|
||||
apply = true;
|
||||
|
||||
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
||||
else
|
||||
apply = true;
|
||||
break;
|
||||
|
|
@ -384,13 +385,13 @@ public class StateListener : IDisposable
|
|||
// Update model state if not on fixed design.
|
||||
case UpdateState.Change:
|
||||
var apply = false;
|
||||
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
|
||||
if (state.Sources[slot, false] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), ApplySettings.Game);
|
||||
else
|
||||
apply = true;
|
||||
|
||||
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||
if (state.Sources[slot, true] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), ApplySettings.Game);
|
||||
else
|
||||
apply = true;
|
||||
|
||||
|
|
@ -418,8 +419,8 @@ public class StateListener : IDisposable
|
|||
switch (UpdateBaseCrest(actor, state, slot, value))
|
||||
{
|
||||
case UpdateState.Change:
|
||||
if (state[slot] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), StateChanged.Source.Game);
|
||||
if (state.Sources[slot] is not StateSource.Fixed and not StateSource.Ipc)
|
||||
_manager.ChangeCrest(state, slot, state.BaseData.Crest(slot), ApplySettings.Game);
|
||||
else
|
||||
value = state.ModelData.Crest(slot);
|
||||
break;
|
||||
|
|
@ -564,10 +565,10 @@ public class StateListener : IDisposable
|
|||
{
|
||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||
// or overwrite the stored model state with the new one.
|
||||
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed or StateSource.Ipc)
|
||||
value = state.ModelData.IsVisorToggled();
|
||||
else
|
||||
_manager.ChangeVisorState(state, value, StateChanged.Source.Game);
|
||||
_manager.ChangeMetaState(state, MetaIndex.VisorState, value, ApplySettings.Game);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -597,10 +598,10 @@ public class StateListener : IDisposable
|
|||
{
|
||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||
// or overwrite the stored model state with the new one.
|
||||
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed or StateSource.Ipc)
|
||||
value = state.ModelData.IsHatVisible();
|
||||
else
|
||||
_manager.ChangeHatState(state, value, StateChanged.Source.Game);
|
||||
_manager.ChangeMetaState(state, MetaIndex.HatState, value, ApplySettings.Game);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -630,10 +631,10 @@ public class StateListener : IDisposable
|
|||
{
|
||||
// if base state changed, either overwrite the actual value if we have fixed values,
|
||||
// or overwrite the stored model state with the new one.
|
||||
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed or StateChanged.Source.Ipc)
|
||||
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed or StateSource.Ipc)
|
||||
value = state.ModelData.IsWeaponVisible();
|
||||
else
|
||||
_manager.ChangeWeaponState(state, value, StateChanged.Source.Game);
|
||||
_manager.ChangeMetaState(state, MetaIndex.WeaponState, value, ApplySettings.Game);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -699,9 +700,9 @@ public class StateListener : IDisposable
|
|||
return;
|
||||
|
||||
var data = new ActorData(gameObject, _creatingIdentifier.ToName());
|
||||
_applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible());
|
||||
_applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible());
|
||||
_applier.ChangeWetness(data, _creatingState.ModelData.IsWet());
|
||||
_applier.ChangeMetaState(data, MetaIndex.HatState, _creatingState.ModelData.IsHatVisible());
|
||||
_applier.ChangeMetaState(data, MetaIndex.Wetness, _creatingState.ModelData.IsWet());
|
||||
_applier.ChangeMetaState(data, MetaIndex.WeaponState, _creatingState.ModelData.IsWeaponVisible());
|
||||
|
||||
ApplyParameters(_creatingState, drawObject);
|
||||
}
|
||||
|
|
@ -723,7 +724,7 @@ public class StateListener : IDisposable
|
|||
_customizeState = null;
|
||||
}
|
||||
|
||||
private unsafe void ApplyParameters(ActorState state, Model model)
|
||||
private void ApplyParameters(ActorState state, Model model)
|
||||
{
|
||||
if (!model.IsHuman)
|
||||
return;
|
||||
|
|
@ -732,30 +733,30 @@ public class StateListener : IDisposable
|
|||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
{
|
||||
var newValue = data[flag];
|
||||
switch (state[flag])
|
||||
switch (state.Sources[flag])
|
||||
{
|
||||
case StateChanged.Source.Game:
|
||||
case StateSource.Game:
|
||||
if (state.BaseData.Parameters.Set(flag, newValue))
|
||||
_manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game);
|
||||
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
|
||||
break;
|
||||
case StateChanged.Source.Manual:
|
||||
case StateSource.Manual:
|
||||
if (state.BaseData.Parameters.Set(flag, newValue))
|
||||
_manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game);
|
||||
_manager.ChangeCustomizeParameter(state, flag, newValue, ApplySettings.Game);
|
||||
else if (_config.UseAdvancedParameters)
|
||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||
break;
|
||||
case StateChanged.Source.Fixed:
|
||||
case StateSource.Fixed:
|
||||
state.BaseData.Parameters.Set(flag, newValue);
|
||||
if (_config.UseAdvancedParameters)
|
||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||
break;
|
||||
case StateChanged.Source.Ipc:
|
||||
case StateSource.Ipc:
|
||||
state.BaseData.Parameters.Set(flag, newValue);
|
||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||
break;
|
||||
case StateChanged.Source.Pending:
|
||||
case StateSource.Pending:
|
||||
state.BaseData.Parameters.Set(flag, newValue);
|
||||
state[flag] = StateChanged.Source.Manual;
|
||||
state.Sources[flag] = StateSource.Manual;
|
||||
if (_config.UseAdvancedParameters)
|
||||
model.ApplySingleParameterData(flag, state.ModelData.Parameters);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Shader;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs.Links;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -13,16 +13,18 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class StateManager(
|
||||
public sealed class StateManager(
|
||||
ActorManager _actors,
|
||||
ItemManager _items,
|
||||
StateChanged _event,
|
||||
StateApplier _applier,
|
||||
StateEditor _editor,
|
||||
ItemManager items,
|
||||
StateChanged @event,
|
||||
StateApplier applier,
|
||||
InternalStateEditor editor,
|
||||
HumanModelList _humans,
|
||||
ICondition _condition,
|
||||
IClientState _clientState)
|
||||
: IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||
IClientState _clientState,
|
||||
Configuration config,
|
||||
JobChangeState jobChange,
|
||||
DesignMerger merger)
|
||||
: StateEditor(editor, applier, @event, jobChange, config, items, merger), IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||
{
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
|
||||
|
||||
|
|
@ -93,7 +95,7 @@ public class StateManager(
|
|||
// If the given actor is not a character, just return a default character.
|
||||
if (!actor.IsCharacter)
|
||||
{
|
||||
ret.SetDefaultEquipment(_items);
|
||||
ret.SetDefaultEquipment(Items);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +129,7 @@ public class StateManager(
|
|||
|
||||
// We can not use the head slot data from the draw object if the hat is hidden.
|
||||
var head = ret.IsHatVisible() || ignoreHatState ? model.GetArmor(EquipSlot.Head) : actor.GetArmor(EquipSlot.Head);
|
||||
var headItem = _items.Identify(EquipSlot.Head, head.Set, head.Variant);
|
||||
var headItem = Items.Identify(EquipSlot.Head, head.Set, head.Variant);
|
||||
ret.SetItem(EquipSlot.Head, headItem);
|
||||
ret.SetStain(EquipSlot.Head, head.Stain);
|
||||
|
||||
|
|
@ -135,7 +137,7 @@ public class StateManager(
|
|||
foreach (var slot in EquipSlotExtensions.EqdpSlots.Skip(1))
|
||||
{
|
||||
var armor = model.GetArmor(slot);
|
||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||
var item = Items.Identify(slot, armor.Set, armor.Variant);
|
||||
ret.SetItem(slot, item);
|
||||
ret.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
|
@ -157,7 +159,7 @@ public class StateManager(
|
|||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var armor = actor.GetArmor(slot);
|
||||
var item = _items.Identify(slot, armor.Set, armor.Variant);
|
||||
var item = Items.Identify(slot, armor.Set, armor.Variant);
|
||||
ret.SetItem(slot, item);
|
||||
ret.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
|
@ -172,8 +174,8 @@ public class StateManager(
|
|||
}
|
||||
|
||||
// Set the weapons regardless of source.
|
||||
var mainItem = _items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
|
||||
var offItem = _items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
|
||||
var mainItem = Items.Identify(EquipSlot.MainHand, main.Skeleton, main.Weapon, main.Variant);
|
||||
var offItem = Items.Identify(EquipSlot.OffHand, off.Skeleton, off.Weapon, off.Variant, mainItem.Type);
|
||||
ret.SetItem(EquipSlot.MainHand, mainItem);
|
||||
ret.SetStain(EquipSlot.MainHand, main.Stain);
|
||||
ret.SetItem(EquipSlot.OffHand, offItem);
|
||||
|
|
@ -197,7 +199,7 @@ public class StateManager(
|
|||
if (mainhand.Skeleton.Id is < 1601 or >= 1651)
|
||||
return;
|
||||
|
||||
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
|
||||
var gauntlets = Items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
|
||||
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
|
||||
offhand.Variant = mainhand.Variant;
|
||||
offhand.Weapon = mainhand.Weapon;
|
||||
|
|
@ -205,279 +207,11 @@ public class StateManager(
|
|||
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
|
||||
}
|
||||
|
||||
#region Change Values
|
||||
|
||||
/// <summary> Turn an actor human. </summary>
|
||||
public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0)
|
||||
public void TurnHuman(ActorState state, StateSource source, uint key = 0)
|
||||
=> ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key);
|
||||
|
||||
/// <summary> Turn an actor to. </summary>
|
||||
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source,
|
||||
uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ForceRedraw(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set model id in state {state.Identifier.Incognito(null)} from {old} to {modelId}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Model, source, state, actors, (old, modelId));
|
||||
}
|
||||
|
||||
/// <summary> Change a customization value. </summary>
|
||||
public void ChangeCustomize(ActorState state, CustomizeIndex idx, CustomizeValue value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeCustomize(state, idx, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Customize, source, state, actors, (old, value, idx));
|
||||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to flags. </summary>
|
||||
public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source,
|
||||
uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeCustomize(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.EntireCustomize, source, state, actors, (old, applied));
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment without stain. </summary>
|
||||
/// <remarks> Do not use this in the same frame as ChangeStain, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
|
||||
public void ChangeItem(ActorState state, EquipSlot slot, EquipItem item, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeItem(state, slot, item, source, out var old, key))
|
||||
return;
|
||||
|
||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||
var actors = type is StateChanged.Type.Equip
|
||||
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
|
||||
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc,
|
||||
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(type, source, state, actors, (old, item, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change a single piece of equipment including stain. </summary>
|
||||
public void ChangeEquip(ActorState state, EquipSlot slot, EquipItem item, StainId stain, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeEquip(state, slot, item, stain, source, out var old, out var oldStain, key))
|
||||
return;
|
||||
|
||||
var type = slot.ToIndex() < 10 ? StateChanged.Type.Equip : StateChanged.Type.Weapon;
|
||||
var actors = type is StateChanged.Type.Equip
|
||||
? _applier.ChangeArmor(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
|
||||
: _applier.ChangeWeapon(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc,
|
||||
item.Type != (slot is EquipSlot.MainHand ? state.BaseData.MainhandType : state.BaseData.OffhandType));
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}) and its stain from {oldStain.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(type, source, state, actors, (old, item, slot));
|
||||
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (oldStain, stain, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change only the stain of an equipment piece. </summary>
|
||||
/// <remarks> Do not use this in the same frame as ChangeEquip, use <see cref="ChangeEquip(ActorState,EquipSlot,EquipItem,StainId,StateChanged.Source,uint)"/> instead. </remarks>
|
||||
public void ChangeStain(ActorState state, EquipSlot slot, StainId stain, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeStain(state, slot, stain, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeStain(state, slot, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old.Id} to {stain.Id}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Stain, source, state, actors, (old, stain, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public void ChangeCrest(ActorState state, CrestFlag slot, bool crest, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeCrest(state, slot, crest, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeCrests(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {slot.ToLabel()} crest in state {state.Identifier.Incognito(null)} from {old} to {crest}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot));
|
||||
}
|
||||
|
||||
/// <summary> Change the crest of an equipment piece. </summary>
|
||||
public void ChangeCustomizeParameter(ActorState state, CustomizeParameterFlag flag, CustomizeParameterValue value,
|
||||
StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
// Also apply main color to highlights when highlights is off.
|
||||
if (!state.ModelData.Customize.Highlights && flag is CustomizeParameterFlag.HairDiffuse)
|
||||
ChangeCustomizeParameter(state, CustomizeParameterFlag.HairHighlight, value, source, key);
|
||||
|
||||
if (!_editor.ChangeParameter(state, flag, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var @new = state.ModelData.Parameters[flag];
|
||||
var actors = _applier.ChangeParameters(state, flag, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set {flag} crest in state {state.Identifier.Incognito(null)} from {old} to {@new}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Parameter, source, state, actors, (old, @new, flag));
|
||||
}
|
||||
|
||||
/// <summary> Change hat visibility. </summary>
|
||||
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeHatState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Head Gear Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.HatState));
|
||||
}
|
||||
|
||||
/// <summary> Change weapon visibility. </summary>
|
||||
public void ChangeWeaponState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeWeaponState(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Weapon Visibility in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.WeaponState));
|
||||
}
|
||||
|
||||
/// <summary> Change visor state. </summary>
|
||||
public void ChangeVisorState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeVisor(state, source is StateChanged.Source.Manual or StateChanged.Source.Ipc);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Visor State in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, source, state, actors, (old, value, ActorState.MetaIndex.VisorState));
|
||||
}
|
||||
|
||||
/// <summary> Set GPose Wetness. </summary>
|
||||
public void ChangeWetness(ActorState state, bool value, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, value, source, out var old, key))
|
||||
return;
|
||||
|
||||
var actors = _applier.ChangeWetness(state, true);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Set Wetness in state {state.Identifier.Incognito(null)} from {old} to {value}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Other, state[ActorState.MetaIndex.Wetness], state, actors, (old, value, ActorState.MetaIndex.Wetness));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ApplyDesign(DesignBase design, ActorState state, StateChanged.Source source, uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeModelId(state, design.DesignData.ModelId, design.DesignData.Customize, design.GetDesignDataRef().GetEquipmentPtr(),
|
||||
source,
|
||||
out var oldModelId, key))
|
||||
return;
|
||||
|
||||
var redraw = oldModelId != design.DesignData.ModelId || !design.DesignData.IsHuman;
|
||||
if (design.DoApplyWetness())
|
||||
_editor.ChangeMetaState(state, ActorState.MetaIndex.Wetness, design.DesignData.IsWet(), source, out _, key);
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
if (design.DoApplyHatVisible())
|
||||
_editor.ChangeMetaState(state, ActorState.MetaIndex.HatState, design.DesignData.IsHatVisible(), source, out _, key);
|
||||
if (design.DoApplyWeaponVisible())
|
||||
_editor.ChangeMetaState(state, ActorState.MetaIndex.WeaponState, design.DesignData.IsWeaponVisible(), source, out _, key);
|
||||
if (design.DoApplyVisorToggle())
|
||||
_editor.ChangeMetaState(state, ActorState.MetaIndex.VisorState, design.DesignData.IsVisorToggled(), source, out _, key);
|
||||
|
||||
var flags = state.AllowsRedraw(_condition)
|
||||
? design.ApplyCustomize
|
||||
: design.ApplyCustomize & ~CustomizeFlagExtensions.RedrawRequired;
|
||||
_editor.ChangeHumanCustomize(state, design.DesignData.Customize, flags, source, out _, out var applied, key);
|
||||
redraw |= applied.RequiresRedraw();
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
HandleEquip(slot, design.DoApplyEquip(slot), design.DoApplyStain(slot));
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
|
||||
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
|
||||
|
||||
var paramSource = source is StateChanged.Source.Manual
|
||||
? StateChanged.Source.Pending
|
||||
: source;
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter))
|
||||
_editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], paramSource, out _, key);
|
||||
|
||||
// Do not apply highlights from a design if highlights is unchecked.
|
||||
if (!state.ModelData.Customize.Highlights)
|
||||
_editor.ChangeParameter(state, CustomizeParameterFlag.HairHighlight,
|
||||
state.ModelData.Parameters[CustomizeParameterFlag.HairDiffuse],
|
||||
state[CustomizeParameterFlag.HairDiffuse], out _, key);
|
||||
}
|
||||
|
||||
var actors = ApplyAll(state, redraw, false);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Design, state[ActorState.MetaIndex.Wetness], state, actors, design);
|
||||
return;
|
||||
|
||||
void HandleEquip(EquipSlot slot, bool applyPiece, bool applyStain)
|
||||
{
|
||||
var unused = (applyPiece, applyStain) switch
|
||||
{
|
||||
(false, false) => false,
|
||||
(true, false) => _editor.ChangeItem(state, slot, design.DesignData.Item(slot), source, out _, key),
|
||||
(false, true) => _editor.ChangeStain(state, slot, design.DesignData.Stain(slot), source, out _, key),
|
||||
(true, true) => _editor.ChangeEquip(state, slot, design.DesignData.Item(slot), design.DesignData.Stain(slot), source, out _,
|
||||
out _, key),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private ActorData ApplyAll(ActorState state, bool redraw, bool withLock)
|
||||
{
|
||||
var actors = _applier.ChangeWetness(state, true);
|
||||
if (redraw)
|
||||
{
|
||||
if (withLock)
|
||||
state.TempLock();
|
||||
_applier.ForceRedraw(actors);
|
||||
}
|
||||
else
|
||||
{
|
||||
_applier.ChangeCustomize(actors, state.ModelData.Customize);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
_applier.ChangeArmor(actors, slot, state.ModelData.Armor(slot), state[slot, false] is not StateChanged.Source.Ipc,
|
||||
state.ModelData.IsHatVisible());
|
||||
}
|
||||
|
||||
var mainhandActors = state.ModelData.MainhandType != state.BaseData.MainhandType ? actors.OnlyGPose() : actors;
|
||||
_applier.ChangeMainhand(mainhandActors, state.ModelData.Item(EquipSlot.MainHand), state.ModelData.Stain(EquipSlot.MainHand));
|
||||
var offhandActors = state.ModelData.OffhandType != state.BaseData.OffhandType ? actors.OnlyGPose() : actors;
|
||||
_applier.ChangeOffhand(offhandActors, state.ModelData.Item(EquipSlot.OffHand), state.ModelData.Stain(EquipSlot.OffHand));
|
||||
}
|
||||
|
||||
if (state.ModelData.IsHuman)
|
||||
{
|
||||
_applier.ChangeHatState(actors, state.ModelData.IsHatVisible());
|
||||
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
|
||||
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
|
||||
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
|
||||
_applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters, state.IsLocked);
|
||||
}
|
||||
|
||||
return actors;
|
||||
}
|
||||
|
||||
public void ResetState(ActorState state, StateChanged.Source source, uint key = 0)
|
||||
public void ResetState(ActorState state, StateSource source, uint key = 0)
|
||||
{
|
||||
if (!state.Unlock(key))
|
||||
return;
|
||||
|
|
@ -489,33 +223,33 @@ public class StateManager(
|
|||
state.ModelData = state.BaseData;
|
||||
state.ModelData.SetIsWet(false);
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
state[index] = StateChanged.Source.Game;
|
||||
state.Sources[index] = StateSource.Game;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
state[slot, true] = StateChanged.Source.Game;
|
||||
state[slot, false] = StateChanged.Source.Game;
|
||||
state.Sources[slot, true] = StateSource.Game;
|
||||
state.Sources[slot, false] = StateSource.Game;
|
||||
}
|
||||
|
||||
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
|
||||
state[type] = StateChanged.Source.Game;
|
||||
foreach (var type in Enum.GetValues<MetaIndex>())
|
||||
state.Sources[type] = StateSource.Game;
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
state[slot] = StateChanged.Source.Game;
|
||||
state.Sources[slot] = StateSource.Game;
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
state[flag] = StateChanged.Source.Game;
|
||||
state.Sources[flag] = StateSource.Game;
|
||||
|
||||
var actors = ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
|
||||
actors = ApplyAll(state, redraw, true);
|
||||
if (source is StateSource.Manual or StateSource.Ipc)
|
||||
actors = Applier.ApplyAll(state, redraw, true);
|
||||
|
||||
Glamourer.Log.Verbose(
|
||||
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
|
||||
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
|
||||
}
|
||||
|
||||
public void ResetAdvancedState(ActorState state, StateChanged.Source source, uint key = 0)
|
||||
public void ResetAdvancedState(ActorState state, StateSource source, uint key = 0)
|
||||
{
|
||||
if (!state.Unlock(key) || !state.ModelData.IsHuman)
|
||||
return;
|
||||
|
|
@ -523,14 +257,14 @@ public class StateManager(
|
|||
state.ModelData.Parameters = state.BaseData.Parameters;
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
state[flag] = StateChanged.Source.Game;
|
||||
state.Sources[flag] = StateSource.Game;
|
||||
|
||||
var actors = ActorData.Invalid;
|
||||
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
|
||||
actors = _applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
|
||||
if (source is StateSource.Manual or StateSource.Ipc)
|
||||
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Reset, source, state, actors, null);
|
||||
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
|
||||
}
|
||||
|
||||
public void ResetStateFixed(ActorState state, bool respectManualPalettes, uint key = 0)
|
||||
|
|
@ -538,69 +272,69 @@ public class StateManager(
|
|||
if (!state.Unlock(key))
|
||||
return;
|
||||
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state[i] is StateChanged.Source.Fixed))
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>().Where(i => state.Sources[i] is StateSource.Fixed))
|
||||
{
|
||||
state[index] = StateChanged.Source.Game;
|
||||
state.Sources[index] = StateSource.Game;
|
||||
state.ModelData.Customize[index] = state.BaseData.Customize[index];
|
||||
}
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.FullSlots)
|
||||
{
|
||||
if (state[slot, true] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[slot, true] is StateSource.Fixed)
|
||||
{
|
||||
state[slot, true] = StateChanged.Source.Game;
|
||||
state.Sources[slot, true] = StateSource.Game;
|
||||
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
||||
}
|
||||
|
||||
if (state[slot, false] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[slot, false] is StateSource.Fixed)
|
||||
{
|
||||
state[slot, false] = StateChanged.Source.Game;
|
||||
state.Sources[slot, false] = StateSource.Game;
|
||||
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var slot in CrestExtensions.AllRelevantSet)
|
||||
{
|
||||
if (state[slot] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[slot] is StateSource.Fixed)
|
||||
{
|
||||
state[slot] = StateChanged.Source.Game;
|
||||
state.Sources[slot] = StateSource.Game;
|
||||
state.ModelData.SetCrest(slot, state.BaseData.Crest(slot));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var flag in CustomizeParameterExtensions.AllFlags)
|
||||
{
|
||||
switch (state[flag])
|
||||
switch (state.Sources[flag])
|
||||
{
|
||||
case StateChanged.Source.Fixed:
|
||||
case StateChanged.Source.Manual when !respectManualPalettes:
|
||||
state[flag] = StateChanged.Source.Game;
|
||||
case StateSource.Fixed:
|
||||
case StateSource.Manual when !respectManualPalettes:
|
||||
state.Sources[flag] = StateSource.Game;
|
||||
state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[MetaIndex.HatState] is StateSource.Fixed)
|
||||
{
|
||||
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game;
|
||||
state.Sources[MetaIndex.HatState] = StateSource.Game;
|
||||
state.ModelData.SetHatVisible(state.BaseData.IsHatVisible());
|
||||
}
|
||||
|
||||
if (state[ActorState.MetaIndex.VisorState] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[MetaIndex.VisorState] is StateSource.Fixed)
|
||||
{
|
||||
state[ActorState.MetaIndex.VisorState] = StateChanged.Source.Game;
|
||||
state.Sources[MetaIndex.VisorState] = StateSource.Game;
|
||||
state.ModelData.SetVisor(state.BaseData.IsVisorToggled());
|
||||
}
|
||||
|
||||
if (state[ActorState.MetaIndex.WeaponState] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[MetaIndex.WeaponState] is StateSource.Fixed)
|
||||
{
|
||||
state[ActorState.MetaIndex.WeaponState] = StateChanged.Source.Game;
|
||||
state.Sources[MetaIndex.WeaponState] = StateSource.Game;
|
||||
state.ModelData.SetWeaponVisible(state.BaseData.IsWeaponVisible());
|
||||
}
|
||||
|
||||
if (state[ActorState.MetaIndex.Wetness] is StateChanged.Source.Fixed)
|
||||
if (state.Sources[MetaIndex.Wetness] is StateSource.Fixed)
|
||||
{
|
||||
state[ActorState.MetaIndex.Wetness] = StateChanged.Source.Game;
|
||||
state.Sources[MetaIndex.Wetness] = StateSource.Game;
|
||||
state.ModelData.SetIsWet(state.BaseData.IsWet());
|
||||
}
|
||||
}
|
||||
|
|
@ -610,7 +344,8 @@ public class StateManager(
|
|||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
|
||||
Applier.ApplyAll(state,
|
||||
!actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
|
||||
false);
|
||||
}
|
||||
|
||||
|
|
|
|||
75
Glamourer/State/StateSource.cs
Normal file
75
Glamourer/State/StateSource.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public enum StateSource : byte
|
||||
{
|
||||
Game,
|
||||
Manual,
|
||||
Fixed,
|
||||
Ipc,
|
||||
|
||||
// Only used for CustomizeParameters.
|
||||
Pending,
|
||||
}
|
||||
|
||||
public unsafe struct StateSources
|
||||
{
|
||||
public const int Size = (StateIndex.Size + 1) / 2;
|
||||
private fixed byte _data[Size];
|
||||
|
||||
|
||||
public StateSources()
|
||||
{ }
|
||||
|
||||
public StateSource this[StateIndex index]
|
||||
{
|
||||
get
|
||||
{
|
||||
var val = _data[index.Value / 2];
|
||||
return (StateSource)((index.Value & 1) == 1 ? val >> 4 : val & 0x0F);
|
||||
}
|
||||
set
|
||||
{
|
||||
var val = _data[index.Value / 2];
|
||||
if ((index.Value & 1) == 1)
|
||||
val = (byte)((val & 0x0F) | ((byte)value << 4));
|
||||
else
|
||||
val = (byte)((val & 0xF0) | (byte)value);
|
||||
_data[index.Value / 2] = val;
|
||||
}
|
||||
}
|
||||
|
||||
public StateSource this[EquipSlot slot, bool stain]
|
||||
{
|
||||
get => this[slot.ToState(stain)];
|
||||
set => this[slot.ToState(stain)] = value;
|
||||
}
|
||||
|
||||
public void RemoveFixedDesignSources()
|
||||
{
|
||||
for (var i = 0; i < Size; ++i)
|
||||
{
|
||||
var value = _data[i];
|
||||
switch (value)
|
||||
{
|
||||
case (byte)StateSource.Fixed | ((byte)StateSource.Fixed << 4):
|
||||
_data[i] = (byte)StateSource.Manual | ((byte)StateSource.Manual << 4);
|
||||
break;
|
||||
|
||||
case (byte)StateSource.Game | ((byte)StateSource.Fixed << 4):
|
||||
case (byte)StateSource.Manual | ((byte)StateSource.Fixed << 4):
|
||||
case (byte)StateSource.Ipc | ((byte)StateSource.Fixed << 4):
|
||||
case (byte)StateSource.Pending | ((byte)StateSource.Fixed << 4):
|
||||
_data[i] = (byte)((value & 0x0F) | ((byte)StateSource.Manual << 4));
|
||||
break;
|
||||
case (byte)StateSource.Fixed:
|
||||
case ((byte)StateSource.Manual << 4) | (byte)StateSource.Fixed:
|
||||
case ((byte)StateSource.Ipc << 4) | (byte)StateSource.Fixed:
|
||||
case ((byte)StateSource.Pending << 4) | (byte)StateSource.Fixed:
|
||||
_data[i] = (byte)((value & 0xF0) | (byte)StateSource.Manual);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit d734d5d2b0686db0f5f4270dc379360d31f72e59
|
||||
Subproject commit 04eb0b5ed3930e9cb87ad00dffa9c8be90b58bb3
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 1ebaf1d209b7edc783896b3a6af4907c91bb302e
|
||||
Subproject commit 260ac69cd6f17050eaf9b7e0b5ce9a8843edfee4
|
||||
Loading…
Add table
Add a link
Reference in a new issue