Add parameter handling.

This commit is contained in:
Ottermandias 2024-01-08 23:00:02 +01:00
parent 9361560350
commit 1a0a0f681f
27 changed files with 633 additions and 155 deletions

View file

@ -1,5 +1,6 @@
using System; using System;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.State; using Glamourer.State;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -80,20 +81,24 @@ public class AutoDesign
return ret; return ret;
} }
public (EquipFlag Equip, CustomizeFlag Customize, CrestFlag Crest, bool ApplyHat, bool ApplyVisor, bool ApplyWeapon, bool ApplyWet) ApplyWhat() 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) var equipFlags = (ApplicationType.HasFlag(Type.Weapons) ? WeaponFlags : 0)
| (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0) | (ApplicationType.HasFlag(Type.Armor) ? ArmorFlags : 0)
| (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0) | (ApplicationType.HasFlag(Type.Accessories) ? AccessoryFlags : 0)
| (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0); | (ApplicationType.HasFlag(Type.GearCustomization) ? StainFlags : 0);
var customizeFlags = ApplicationType.HasFlag(Type.Customizations) ? CustomizeFlagExtensions.All : 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; var crestFlag = ApplicationType.HasFlag(Type.GearCustomization) ? CrestExtensions.AllRelevant : 0;
if (Revert) if (Revert)
return (equipFlags, customizeFlags, crestFlag, ApplicationType.HasFlag(Type.Armor), ApplicationType.HasFlag(Type.Armor), return (equipFlags, customizeFlags, crestFlag, parameterFlags, ApplicationType.HasFlag(Type.Armor),
ApplicationType.HasFlag(Type.Armor),
ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations)); ApplicationType.HasFlag(Type.Weapons), ApplicationType.HasFlag(Type.Customizations));
return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest, return (equipFlags & Design!.ApplyEquip, customizeFlags & Design.ApplyCustomize, crestFlag & Design.ApplyCrest,
parameterFlags & Design.ApplyParameters,
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyHatVisible(),
ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(), ApplicationType.HasFlag(Type.Armor) && Design.DoApplyVisorToggle(),
ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(), ApplicationType.HasFlag(Type.Weapons) && Design.DoApplyWeaponVisible(),

View file

@ -6,6 +6,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
@ -274,6 +275,7 @@ public class AutoDesignApplier : IDisposable
EquipFlag totalEquipFlags = 0; EquipFlag totalEquipFlags = 0;
CustomizeFlag totalCustomizeFlags = 0; CustomizeFlag totalCustomizeFlags = 0;
CrestFlag totalCrestFlags = 0; CrestFlag totalCrestFlags = 0;
CustomizeParameterFlag totalParameterFlags = 0;
byte totalMetaFlags = 0; byte totalMetaFlags = 0;
if (set.BaseState == AutoDesignSet.Base.Game) if (set.BaseState == AutoDesignSet.Base.Game)
_state.ResetStateFixed(state); _state.ResetStateFixed(state);
@ -297,18 +299,19 @@ public class AutoDesignApplier : IDisposable
if (!data.IsHuman) if (!data.IsHuman)
continue; continue;
var (equipFlags, customizeFlags, crestFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat(); var (equipFlags, customizeFlags, crestFlags, parameterFlags, applyHat, applyVisor, applyWeapon, applyWet) = design.ApplyWhat();
ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source); ReduceMeta(state, data, applyHat, applyVisor, applyWeapon, applyWet, ref totalMetaFlags, respectManual, source);
ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source); ReduceCustomize(state, data, customizeFlags, ref totalCustomizeFlags, respectManual, source);
ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange); ReduceEquip(state, data, equipFlags, ref totalEquipFlags, respectManual, source, fromJobChange);
ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source); ReduceCrests(state, data, crestFlags, ref totalCrestFlags, respectManual, source);
ReduceParameters(state, data, parameterFlags, ref totalParameterFlags, respectManual, source);
} }
if (totalCustomizeFlags != 0) if (totalCustomizeFlags != 0)
state.ModelData.ModelId = 0; state.ModelData.ModelId = 0;
} }
/// <summary> Get world-specific first and all-world afterwards. </summary> /// <summary> Get world-specific first and all-world afterward. </summary>
private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set) private bool GetPlayerSet(ActorIdentifier identifier, [NotNullWhen(true)] out AutoDesignSet? set)
{ {
switch (identifier.Type) switch (identifier.Type)
@ -349,6 +352,24 @@ public class AutoDesignApplier : IDisposable
} }
} }
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)
_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, private void ReduceEquip(ActorState state, in DesignData design, EquipFlag equipFlags, ref EquipFlag totalEquipFlags, bool respectManual,
StateChanged.Source source, bool fromJobChange) StateChanged.Source source, bool fromJobChange)
{ {

View file

@ -38,6 +38,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool RevertManualChangesOnZoneChange { get; set; } = false; public bool RevertManualChangesOnZoneChange { get; set; } = false;
public bool ShowQuickBarInTabs { get; set; } = true; public bool ShowQuickBarInTabs { get; set; } = true;
public bool OpenWindowAtStart { get; set; } = false; public bool OpenWindowAtStart { get; set; } = false;
public bool UseAdvancedParameters { get; set; } = false;
public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY); public ModifiableHotkey ToggleQuickDesignBar { get; set; } = new(VirtualKey.NO_KEY);
public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift); public DoubleModifier DeleteDesignModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New; public ChangeLogDisplayType ChangeLogDisplayType { get; set; } = ChangeLogDisplayType.New;

View file

@ -66,6 +66,7 @@ public sealed class Design : DesignBase, ISavable
["WriteProtected"] = WriteProtected(), ["WriteProtected"] = WriteProtected(),
["Equipment"] = SerializeEquipment(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
["Mods"] = SerializeMods(), ["Mods"] = SerializeMods(),
}; };
return ret; return ret;
@ -133,6 +134,7 @@ public sealed class Design : DesignBase, ISavable
LoadCustomize(customizations, json["Customize"], design, design.Name, true, false); LoadCustomize(customizations, json["Customize"], design, design.Name, true, false);
LoadEquip(items, json["Equipment"], design, design.Name, true); LoadEquip(items, json["Equipment"], design, design.Name, true);
LoadMods(json["Mods"], design); LoadMods(json["Mods"], design);
LoadParameters(json["Parameters"], design, design.Name);
design.Color = json["Color"]?.ToObject<string>() ?? string.Empty; design.Color = json["Color"]?.ToObject<string>() ?? string.Empty;
return design; return design;
} }

View file

@ -6,7 +6,9 @@ using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -71,6 +73,8 @@ public class DesignBase
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
public CustomizeSet CustomizeSet { get; private set; } public CustomizeSet CustomizeSet { get; private set; }
public CustomizeParameterFlag ApplyParameters { get; private set; }
internal CustomizeFlag ApplyCustomize internal CustomizeFlag ApplyCustomize
{ {
get => _applyCustomize.FixApplication(CustomizeSet); get => _applyCustomize.FixApplication(CustomizeSet);
@ -174,6 +178,9 @@ public class DesignBase
public bool DoApplyCrest(CrestFlag slot) public bool DoApplyCrest(CrestFlag slot)
=> ApplyCrest.HasFlag(slot); => ApplyCrest.HasFlag(slot);
public bool DoApplyParameter(CustomizeParameterFlag flag)
=> ApplyParameters.HasFlag(flag);
internal bool SetApplyEquip(EquipSlot slot, bool value) internal bool SetApplyEquip(EquipSlot slot, bool value)
{ {
var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag(); var newValue = value ? ApplyEquip | slot.ToFlag() : ApplyEquip & ~slot.ToFlag();
@ -214,8 +221,19 @@ public class DesignBase
return true; return true;
} }
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) internal bool SetApplyParameter(CustomizeParameterFlag flag, bool value)
=> new(this, equipFlags, customizeFlags, crestFlags); {
var newValue = value ? ApplyParameters | flag : ApplyParameters & ~flag;
if (newValue == ApplyParameters)
return false;
ApplyParameters = newValue;
return true;
}
internal FlagRestrictionResetter TemporarilyRestrictApplication(EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
CustomizeParameterFlag parameterFlags)
=> new(this, equipFlags, customizeFlags, crestFlags, parameterFlags);
internal readonly struct FlagRestrictionResetter : IDisposable internal readonly struct FlagRestrictionResetter : IDisposable
{ {
@ -223,16 +241,20 @@ public class DesignBase
private readonly EquipFlag _oldEquipFlags; private readonly EquipFlag _oldEquipFlags;
private readonly CustomizeFlag _oldCustomizeFlags; private readonly CustomizeFlag _oldCustomizeFlags;
private readonly CrestFlag _oldCrestFlags; private readonly CrestFlag _oldCrestFlags;
private readonly CustomizeParameterFlag _oldParameterFlags;
public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public FlagRestrictionResetter(DesignBase d, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags,
CustomizeParameterFlag parameterFlags)
{ {
_design = d; _design = d;
_oldEquipFlags = d.ApplyEquip; _oldEquipFlags = d.ApplyEquip;
_oldCustomizeFlags = d.ApplyCustomizeRaw; _oldCustomizeFlags = d.ApplyCustomizeRaw;
_oldCrestFlags = d.ApplyCrest; _oldCrestFlags = d.ApplyCrest;
_oldParameterFlags = d.ApplyParameters;
d.ApplyEquip &= equipFlags; d.ApplyEquip &= equipFlags;
d.ApplyCustomize &= customizeFlags; d.ApplyCustomize &= customizeFlags;
d.ApplyCrest &= crestFlags; d.ApplyCrest &= crestFlags;
d.ApplyParameters &= parameterFlags;
} }
public void Dispose() public void Dispose()
@ -240,6 +262,7 @@ public class DesignBase
_design.ApplyEquip = _oldEquipFlags; _design.ApplyEquip = _oldEquipFlags;
_design.ApplyCustomize = _oldCustomizeFlags; _design.ApplyCustomize = _oldCustomizeFlags;
_design.ApplyCrest = _oldCrestFlags; _design.ApplyCrest = _oldCrestFlags;
_design.ApplyParameters = _oldParameterFlags;
} }
} }
@ -259,23 +282,13 @@ public class DesignBase
["FileVersion"] = FileVersion, ["FileVersion"] = FileVersion,
["Equipment"] = SerializeEquipment(), ["Equipment"] = SerializeEquipment(),
["Customize"] = SerializeCustomize(), ["Customize"] = SerializeCustomize(),
["Parameters"] = SerializeParameters(),
}; };
return ret; return ret;
} }
protected JObject SerializeEquipment() protected JObject SerializeEquipment()
{ {
static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest)
=> new()
{
["ItemId"] = id.Id,
["Stain"] = stain.Id,
["Crest"] = crest,
["Apply"] = apply,
["ApplyStain"] = applyStain,
["ApplyCrest"] = applyCrest,
};
var ret = new JObject(); var ret = new JObject();
if (_designData.IsHuman) if (_designData.IsHuman)
{ {
@ -298,6 +311,17 @@ public class DesignBase
} }
return ret; return ret;
static JObject Serialize(CustomItemId id, StainId stain, bool crest, bool apply, bool applyStain, bool applyCrest)
=> new()
{
["ItemId"] = id.Id,
["Stain"] = stain.Id,
["Crest"] = crest,
["Apply"] = apply,
["ApplyStain"] = applyStain,
["ApplyCrest"] = applyCrest,
};
} }
protected JObject SerializeCustomize() protected JObject SerializeCustomize()
@ -329,6 +353,42 @@ public class DesignBase
return ret; return ret;
} }
protected JObject SerializeParameters()
{
var ret = new JObject();
foreach (var flag in CustomizeParameterExtensions.ValueFlags)
{
ret[flag.ToString()] = new JObject()
{
["Value"] = DesignData.Parameters[flag][0],
["Apply"] = DoApplyParameter(flag),
};
}
foreach (var flag in CustomizeParameterExtensions.PercentageFlags)
{
ret[flag.ToString()] = new JObject()
{
["Percentage"] = DesignData.Parameters[flag][0],
["Apply"] = DoApplyParameter(flag),
};
}
foreach (var flag in CustomizeParameterExtensions.TripleFlags)
{
ret[flag.ToString()] = new JObject()
{
["Red"] = DesignData.Parameters[flag][0],
["Green"] = DesignData.Parameters[flag][1],
["Blue"] = DesignData.Parameters[flag][2],
["Apply"] = DoApplyParameter(flag),
};
}
return ret;
}
#endregion #endregion
#region Deserialization #region Deserialization
@ -348,9 +408,68 @@ public class DesignBase
var ret = new DesignBase(customizations, items); var ret = new DesignBase(customizations, items);
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
LoadEquip(items, json["Equipment"], ret, "Temporary Design", true); LoadEquip(items, json["Equipment"], ret, "Temporary Design", true);
LoadParameters(json["Parameters"], ret, "Temporary Design");
return ret; return ret;
} }
protected static void LoadParameters(JToken? parameters, DesignBase design, string name)
{
if (parameters == null)
{
design.ApplyParameters = 0;
design.GetDesignDataRef().Parameters = default;
return;
}
foreach (var flag in CustomizeParameterExtensions.ValueFlags)
{
if (!TryGetToken(flag, out var token))
continue;
var value = token["Value"]?.ToObject<float>() ?? 0f;
design.GetDesignDataRef().Parameters[flag] = new Vector3(value);
}
foreach (var flag in CustomizeParameterExtensions.PercentageFlags)
{
if (!TryGetToken(flag, out var token))
continue;
var value = Math.Clamp(token["Percentage"]?.ToObject<float>() ?? 0f, 0f, 1f);
design.GetDesignDataRef().Parameters[flag] = new Vector3(value);
}
foreach (var flag in CustomizeParameterExtensions.TripleFlags)
{
if (!TryGetToken(flag, out var token))
continue;
var r = Math.Clamp(token["Red"]?.ToObject<float>() ?? 0f, 0, 1);
var g = Math.Clamp(token["Green"]?.ToObject<float>() ?? 0f, 0, 1);
var b = Math.Clamp(token["Blue"]?.ToObject<float>() ?? 0f, 0, 1);
design.GetDesignDataRef().Parameters[flag] = new Vector3(r, g, b);
}
return;
// Load the token and set application.
bool TryGetToken(CustomizeParameterFlag flag, [NotNullWhen(true)] out JToken? token)
{
token = parameters![flag.ToString()];
if (token != null)
{
var apply = token["Apply"]?.ToObject<bool>() ?? false;
design.SetApplyParameter(flag, apply);
return true;
}
design.ApplyParameters &= ~flag;
design.GetDesignDataRef().Parameters[flag] = Vector3.Zero;
return false;
}
}
protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name, bool allowUnknown) protected static void LoadEquip(ItemManager items, JToken? equip, DesignBase design, string name, bool allowUnknown)
{ {
if (equip == null) if (equip == null)
@ -516,6 +635,8 @@ public class DesignBase
out var applyHat, out var applyVisor, out var applyWeapon); out var applyHat, out var applyVisor, out var applyWeapon);
ApplyEquip = equipFlags; ApplyEquip = equipFlags;
ApplyCustomize = customizeFlags; ApplyCustomize = customizeFlags;
ApplyParameters = 0;
ApplyCrest = 0;
SetWriteProtected(writeProtected); SetWriteProtected(writeProtected);
SetApplyHatVisible(applyHat); SetApplyHatVisible(applyHat);
SetApplyVisorToggle(applyVisor); SetApplyVisorToggle(applyVisor);

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Utility; using Glamourer.Utility;
@ -23,9 +24,9 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
public JObject ShareJObject(Design design) public JObject ShareJObject(Design design)
=> design.JsonSerialize(); => design.JsonSerialize();
public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public JObject ShareJObject(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
{ {
var design = Convert(state, equipFlags, customizeFlags, crestFlags); var design = Convert(state, equipFlags, customizeFlags, crestFlags, parameterFlags);
return ShareJObject(design); return ShareJObject(design);
} }
@ -36,21 +37,21 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
=> ShareBase64(ShareJObject(design)); => ShareBase64(ShareJObject(design));
public string ShareBase64(ActorState state) public string ShareBase64(ActorState state)
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All, CustomizeParameterExtensions.All);
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
=> ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags); => ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags);
public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
{ {
var design = Convert(data, equipFlags, customizeFlags, crestFlags); var design = Convert(data, equipFlags, customizeFlags, crestFlags, parameterFlags);
return ShareBase64(ShareJObject(design)); return ShareBase64(ShareJObject(design));
} }
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
=> Convert(state.ModelData, equipFlags, customizeFlags, crestFlags); => Convert(state.ModelData, equipFlags, customizeFlags, crestFlags, parameterFlags);
public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags, CustomizeParameterFlag parameterFlags)
{ {
var design = _designs.CreateTemporary(); var design = _designs.CreateTemporary();
design.ApplyEquip = equipFlags & EquipFlagExtensions.All; design.ApplyEquip = equipFlags & EquipFlagExtensions.All;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -25,6 +26,7 @@ public unsafe struct DesignData
private fixed uint _itemIds[12]; private fixed uint _itemIds[12];
private fixed ushort _iconIds[12]; private fixed ushort _iconIds[12];
private fixed byte _equipmentBytes[48]; private fixed byte _equipmentBytes[48];
public CustomizeParameterData Parameters;
public CustomizeArray Customize = CustomizeArray.Default; public CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId; public uint ModelId;
public CrestFlag CrestVisibility; public CrestFlag CrestVisibility;

View file

@ -4,10 +4,13 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
@ -17,6 +20,7 @@ using OtterGui;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using static Glamourer.State.ActorState;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -424,6 +428,20 @@ public class DesignManager
} }
} }
/// <summary> Change a customize parameter. </summary>
public void ChangeCustomizeParameter(Design design, CustomizeParameterFlag flag, Vector3 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));
}
/// <summary> Change whether to apply a specific equipment piece. </summary> /// <summary> Change whether to apply a specific equipment piece. </summary>
public void ChangeApplyEquip(Design design, EquipSlot slot, bool value) public void ChangeApplyEquip(Design design, EquipSlot slot, bool value)
{ {
@ -529,6 +547,18 @@ public class DesignManager
_event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value)); _event.Invoke(DesignChanged.Type.Other, design, (metaIndex, true, value));
} }
/// <summary> Change the application value of a customize parameter. </summary>
public void ChangeApplyParameter(Design design, CustomizeParameterFlag flag, bool value)
{
if (!design.SetApplyParameter(flag, value))
return;
design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design);
Glamourer.Log.Debug($"Set applying of parameter {flag} to {value}.");
_event.Invoke(DesignChanged.Type.ApplyParameter, design, flag);
}
/// <summary> Apply an entire design based on its appliance rules piece by piece. </summary> /// <summary> Apply an entire design based on its appliance rules piece by piece. </summary>
public void ApplyDesign(Design design, DesignBase other) public void ApplyDesign(Design design, DesignBase other)
{ {

View file

@ -65,6 +65,9 @@ public sealed class DesignChanged()
/// <summary> An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary> /// <summary> An existing design had a crest visibility changed. Data is the old crest visibility, the new crest visibility and the slot [(bool, bool, EquipSlot)]. </summary>
Crest, Crest,
/// <summary> An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(Vector3, Vector3, CustomizeParameterFlag)]. </summary>
Parameter,
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary> /// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
ApplyCustomize, ApplyCustomize,
@ -77,6 +80,9 @@ public sealed class DesignChanged()
/// <summary> An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. </summary> /// <summary> An existing design changed whether a specific crest visibility is applied. Data is the slot of the equipment [EquipSlot]. </summary>
ApplyCrest, ApplyCrest,
/// <summary> An existing design changed whether a specific customize parameter is applied. Data is the flag for the parameter [CustomizeParameterFlag]. </summary>
ApplyParameter,
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary> /// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
WriteProtection, WriteProtection,

View file

@ -39,6 +39,9 @@ public sealed class StateChanged()
/// <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> /// <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, Crest,
/// <summary> A characters saved state had its customize parameter changed. Data is the old value, the new value and the type [(Vector3, Vector3, 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> /// <summary> A characters saved state had a design applied. This means everything may have changed. Data is the applied design. [DesignBase] </summary>
Design, Design,

View file

@ -159,8 +159,8 @@ public class DesignQuickBar : Window, IDisposable
return; return;
} }
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); using var _ = design!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_stateManager.ApplyDesign(design, state, StateChanged.Source.Manual); _stateManager.ApplyDesign(design, state, StateChanged.Source.Manual);
} }

View file

@ -35,7 +35,8 @@ public class ActorPanel(
DesignManager _designManager, DesignManager _designManager,
ImportService _importService, ImportService _importService,
ICondition _conditions, ICondition _conditions,
DictModelChara _modelChara) DictModelChara _modelChara,
CustomizeParameterDrawer _parameterDrawer)
{ {
private ActorIdentifier _identifier; private ActorIdentifier _identifier;
private string _actorName = string.Empty; private string _actorName = string.Empty;
@ -127,6 +128,7 @@ public class ActorPanel(
{ {
DrawCustomizationsHeader(); DrawCustomizationsHeader();
DrawEquipmentHeader(); DrawEquipmentHeader();
DrawParameterHeader();
} }
private void DrawCustomizationsHeader() private void DrawCustomizationsHeader()
@ -169,6 +171,14 @@ public class ActorPanel(
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
} }
private void DrawParameterHeader()
{
if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customizations"))
return;
_parameterDrawer.Draw(_stateManager, _state!);
}
private void DrawEquipmentMetaToggles() private void DrawEquipmentMetaToggles()
{ {
using (_ = ImRaii.Group()) using (_ = ImRaii.Group())
@ -303,8 +313,8 @@ public class ActorPanel(
{ {
ImGui.OpenPopup("Save as Design"); ImGui.OpenPopup("Save as Design");
_newName = _state!.Identifier.ToName(); _newName = _state!.Identifier.ToName();
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest, applyParameters);
} }
private void SaveDesignDrawPopup() private void SaveDesignDrawPopup()
@ -339,8 +349,8 @@ public class ActorPanel(
{ {
try try
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest); var text = _converter.ShareBase64(_state!, applyGear, applyCustomize, applyCrest, applyParameters);
ImGui.SetClipboardText(text); ImGui.SetClipboardText(text);
} }
catch (Exception ex) catch (Exception ex)
@ -379,9 +389,9 @@ public class ActorPanel(
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
return; return;
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
StateChanged.Source.Manual); StateChanged.Source.Manual);
} }
@ -397,9 +407,9 @@ public class ActorPanel(
!data.Valid || id == _identifier || _state!.ModelData.ModelId != 0)) !data.Valid || id == _identifier || _state!.ModelData.ModelId != 0))
return; return;
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
if (_stateManager.GetOrCreate(id, data.Objects[0], out var state)) if (_stateManager.GetOrCreate(id, data.Objects[0], out var state))
_stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest), state, _stateManager.ApplyDesign(_converter.Convert(_state!, applyGear, applyCustomize, applyCrest, applyParameters), state,
StateChanged.Source.Manual); StateChanged.Source.Manual);
} }
} }

View file

@ -269,7 +269,7 @@ public class SetPanel(
var size = new Vector2(ImGui.GetFrameHeight()); var size = new Vector2(ImGui.GetFrameHeight());
size.X += ImGuiHelpers.GlobalScale; size.X += ImGuiHelpers.GlobalScale;
var (equipFlags, customizeFlags, _, _, _, _, _) = design.ApplyWhat(); var (equipFlags, customizeFlags, _, _, _, _, _, _) = design.ApplyWhat();
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand)) foreach (var slot in EquipSlotExtensions.EqdpSlots.Append(EquipSlot.MainHand).Append(EquipSlot.OffHand))
{ {

View file

@ -101,6 +101,18 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
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[type]);
ImGui.TableNextRow(); ImGui.TableNextRow();
} }
foreach (var crest in CrestExtensions.AllRelevantSet)
{
PrintRow(crest.ToLabel(), state.BaseData.Crest(crest), state.ModelData.Crest(crest), state[crest]);
ImGui.TableNextRow();
}
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
PrintRow(flag.ToString(), state.BaseData.Parameters[flag], state.ModelData.Parameters[flag], state[flag]);
ImGui.TableNextRow();
}
} }
else else
{ {

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Numerics; using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Shader;
using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using ImGuiNET; using ImGuiNET;
@ -78,6 +80,28 @@ public unsafe class ModelEvaluationPanel(
DrawEquip(actor, model); DrawEquip(actor, model);
DrawCustomize(actor, model); DrawCustomize(actor, model);
DrawCrests(actor, model); DrawCrests(actor, model);
DrawParameters(actor, model);
}
private void DrawParameters(Actor actor, Model model)
{
if (!model.IsHuman)
return;
if (model.AsHuman->CustomizeParameterCBuffer == null)
return;
var ptr = (CustomizeParameter*)model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer;
if (ptr == null)
return;
var convert = CustomizeParameterData.FromParameters(*ptr);
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
ImGuiUtil.DrawTableColumn(flag.ToString());
ImGuiUtil.DrawTableColumn(string.Empty);
ImGuiUtil.DrawTableColumn(convert[flag].ToString());
ImGui.TableNextColumn();
}
} }
private void DrawVisor(Actor actor, Model model) private void DrawVisor(Actor actor, Model model)

View file

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
using Glamourer.Interop; using Glamourer.Interop;
@ -21,9 +22,20 @@ using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.DesignTab; namespace Glamourer.Gui.Tabs.DesignTab;
public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer _customizationDrawer, DesignManager _manager, public class DesignPanel(
ObjectManager _objects, StateManager _state, EquipmentDrawer _equipmentDrawer, ModAssociationsTab _modAssociations, DesignFileSystemSelector _selector,
DesignDetailTab _designDetails, DesignConverter _converter, ImportService _importService, MultiDesignPanel _multiDesignPanel) CustomizationDrawer _customizationDrawer,
DesignManager _manager,
ObjectManager _objects,
StateManager _state,
EquipmentDrawer _equipmentDrawer,
ModAssociationsTab _modAssociations,
Configuration _config,
DesignDetailTab _designDetails,
DesignConverter _converter,
ImportService _importService,
MultiDesignPanel _multiDesignPanel,
CustomizeParameterDrawer _parameterDrawer)
{ {
private readonly FileDialogManager _fileDialog = new(); private readonly FileDialogManager _fileDialog = new();
@ -152,11 +164,20 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
} }
private void DrawCustomizeParameters()
{
if (!_config.UseAdvancedParameters || !ImGui.CollapsingHeader("Advanced Customization"))
return;
_parameterDrawer.Draw(_manager, _selector.Selected!);
}
private void DrawCustomizeApplication() private void DrawCustomizeApplication()
{ {
var set = _selector.Selected!.CustomizeSet; var set = _selector.Selected!.CustomizeSet;
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType;
var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 :
(_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
{ {
var newFlags = flags == 3; var newFlags = flags == 3;
@ -205,6 +226,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
DrawCustomizeApplication(); DrawCustomizeApplication();
ImGui.NewLine(); ImGui.NewLine();
DrawCrestApplication(); DrawCrestApplication();
ImGui.NewLine();
if (_config.UseAdvancedParameters)
DrawMetaApplication();
} }
ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2); ImGui.SameLine(ImGui.GetContentRegionAvail().X / 2);
@ -248,6 +272,15 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
EquipSlotExtensions.FullSlots); EquipSlotExtensions.FullSlots);
ImGui.NewLine(); ImGui.NewLine();
if (_config.UseAdvancedParameters)
DrawParameterApplication();
else
DrawMetaApplication();
}
}
private void DrawMetaApplication()
{
const uint all = 0x0Fu; const uint all = 0x0Fu;
var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00) var flags = (_selector.Selected!.DoApplyHatVisible() ? 0x01u : 0x00)
| (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00) | (_selector.Selected!.DoApplyVisorToggle() ? 0x02u : 0x00)
@ -270,6 +303,17 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange) if (ImGui.Checkbox("Apply Wetness", ref apply) || bigChange)
_manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply); _manager.ChangeApplyMeta(_selector.Selected!, ActorState.MetaIndex.Wetness, apply);
} }
private void DrawParameterApplication()
{
var flags = (uint)_selector.Selected!.ApplyParameters;
var bigChange = ImGui.CheckboxFlags("Apply All Customize Parameters", ref flags, (uint)CustomizeParameterExtensions.All);
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
var apply = bigChange ? ((CustomizeParameterFlag)flags).HasFlag(flag) : _selector.Selected!.DoApplyParameter(flag);
if (ImGui.Checkbox($"Apply {flag}", ref apply) || bigChange)
_manager.ChangeApplyParameter(_selector.Selected!, flag, apply);
}
} }
public void Draw() public void Draw()
@ -316,6 +360,7 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
DrawButtonRow(); DrawButtonRow();
DrawCustomize(); DrawCustomize();
DrawEquipment(); DrawEquipment();
DrawCustomizeParameters();
_designDetails.Draw(); _designDetails.Draw();
DrawApplicationRules(); DrawApplicationRules();
_modAssociations.Draw(); _modAssociations.Draw();
@ -386,8 +431,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
} }
} }
@ -405,8 +450,8 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest); using var _ = _selector.Selected!.TemporarilyRestrictApplication(applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual); _state.ApplyDesign(_selector.Selected!, state, StateChanged.Source.Manual);
} }
} }

View file

@ -89,9 +89,9 @@ public class NpcPanel(
{ {
try try
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var data = ToDesignData(); var data = ToDesignData();
var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest); var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest, applyParameters);
ImGui.SetClipboardText(text); ImGui.SetClipboardText(text);
} }
catch (Exception ex) catch (Exception ex)
@ -106,10 +106,10 @@ public class NpcPanel(
{ {
ImGui.OpenPopup("Save as Design"); ImGui.OpenPopup("Save as Design");
_newName = _selector.Selection.Name; _newName = _selector.Selection.Name;
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var data = ToDesignData(); var data = ToDesignData();
_newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest); _newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest, applyParameters);
} }
private void SaveDesignDrawPopup() private void SaveDesignDrawPopup()
@ -198,8 +198,8 @@ public class NpcPanel(
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(design, state, StateChanged.Source.Manual); _state.ApplyDesign(design, state, StateChanged.Source.Manual);
} }
} }
@ -217,8 +217,8 @@ public class NpcPanel(
if (_state.GetOrCreate(id, data.Objects[0], out var state)) if (_state.GetOrCreate(id, data.Objects[0], out var state))
{ {
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest, applyParameters) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest); var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest, applyParameters);
_state.ApplyDesign(design, state, StateChanged.Source.Manual); _state.ApplyDesign(design, state, StateChanged.Source.Manual);
} }
} }

View file

@ -60,7 +60,8 @@ public class SettingsTab : ITab
if (!child) if (!child)
return; return;
Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.", Checkbox("Enable Auto Designs",
"Enable the application of designs associated to characters in the Automation tab to be applied automatically.",
_config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v);
ImGui.NewLine(); ImGui.NewLine();
ImGui.NewLine(); ImGui.NewLine();
@ -97,6 +98,8 @@ public class SettingsTab : ITab
Checkbox("Revert Manual Changes on Zone Change", Checkbox("Revert Manual Changes on Zone Change",
"Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.", "Restores the old behaviour of reverting your character to its game or automation base whenever you change the zone.",
_config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v); _config.RevertManualChangesOnZoneChange, v => _config.RevertManualChangesOnZoneChange = v);
Checkbox("Enable Advanced Customization Options", "Enable the display and editing of advanced customization options like arbitrary colors.",
_config.UseAdvancedParameters, v => _config.UseAdvancedParameters = v);
ImGui.NewLine(); ImGui.NewLine();
} }
@ -108,7 +111,8 @@ public class SettingsTab : ITab
EphemeralCheckbox("Show Quick Design Bar", EphemeralCheckbox("Show Quick Design Bar",
"Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.", "Show a bar separate from the main window that allows you to quickly apply designs or revert your character and target.",
_config.Ephemeral.ShowDesignQuickBar, v => _config.Ephemeral.ShowDesignQuickBar = v); _config.Ephemeral.ShowDesignQuickBar, v => _config.Ephemeral.ShowDesignQuickBar = v);
EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.", _config.Ephemeral.LockDesignQuickBar, EphemeralCheckbox("Lock Quick Design Bar", "Prevent the quick design bar from being moved and lock it in place.",
_config.Ephemeral.LockDesignQuickBar,
v => _config.Ephemeral.LockDesignQuickBar = v); v => _config.Ephemeral.LockDesignQuickBar = v);
if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.", if (Widget.ModifiableKeySelector("Hotkey to Toggle Quick Design Bar", "Set a hotkey that opens or closes the quick design bar.",
100 * ImGuiHelpers.GlobalScale, 100 * ImGuiHelpers.GlobalScale,
@ -138,7 +142,8 @@ public class SettingsTab : ITab
_config.HideWindowInCutscene = v; _config.HideWindowInCutscene = v;
_uiBuilder.DisableCutsceneUiHide = !v; _uiBuilder.DisableCutsceneUiHide = !v;
}); });
EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.", _config.Ephemeral.LockMainWindow, EphemeralCheckbox("Lock Main Window", "Prevent the main window from being moved and lock it in place.",
_config.Ephemeral.LockMainWindow,
v => _config.Ephemeral.LockMainWindow = v); v => _config.Ephemeral.LockMainWindow = v);
Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.", Checkbox("Open Main Window at Game Start", "Whether the main Glamourer window should be open or closed after launching the game.",
_config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v); _config.OpenWindowAtStart, v => _config.OpenWindowAtStart = v);

View file

@ -1,6 +1,7 @@
using System.Numerics; using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
@ -98,13 +99,13 @@ public static class UiHelpers
return (currentValue != newValue, currentApply != newApply); return (currentValue != newValue, currentApply != newApply);
} }
public static (EquipFlag, CustomizeFlag, CrestFlag) ConvertKeysToFlags() public static (EquipFlag, CustomizeFlag, CrestFlag, CustomizeParameterFlag) ConvertKeysToFlags()
=> (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch => (ImGui.GetIO().KeyCtrl, ImGui.GetIO().KeyShift) switch
{ {
(false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), (false, false) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All),
(true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All), (true, true) => (EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All),
(true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All), (true, false) => (EquipFlagExtensions.All, (CustomizeFlag)0, CrestExtensions.All, 0),
(false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0), (false, true) => ((EquipFlag)0, CustomizeFlagExtensions.AllRelevant, 0, CustomizeParameterExtensions.All),
}; };
public static (bool, bool) ConvertKeysToBool() public static (bool, bool) ConvertKeysToBool()

View file

@ -19,6 +19,8 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeAr
private readonly PenumbraReloaded _penumbraReloaded; private readonly PenumbraReloaded _penumbraReloaded;
private readonly IGameInteropProvider _interop; private readonly IGameInteropProvider _interop;
private readonly delegate* unmanaged[Stdcall]<Human*, byte*, bool, bool> _original; private readonly delegate* unmanaged[Stdcall]<Human*, byte*, bool, bool> _original;
private readonly Post _postEvent = new();
/// <summary> Check whether we in a manual customize update, in which case we need to not toggle certain flags. </summary> /// <summary> Check whether we in a manual customize update, in which case we need to not toggle certain flags. </summary>
public static readonly InMethodChecker InUpdate = new(); public static readonly InMethodChecker InUpdate = new();
@ -36,6 +38,7 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeAr
_interop = interop; _interop = interop;
_changeCustomizeHook = Create(); _changeCustomizeHook = Create();
_original = Human.MemberFunctionPointers.UpdateDrawData; _original = Human.MemberFunctionPointers.UpdateDrawData;
interop.InitializeFromAttributes(this);
_penumbraReloaded.Subscribe(Restore, PenumbraReloaded.Priority.ChangeCustomizeService); _penumbraReloaded.Subscribe(Restore, PenumbraReloaded.Priority.ChangeCustomizeService);
} }
@ -81,6 +84,23 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2<Model, CustomizeAr
if (!InUpdate.InMethod) if (!InUpdate.InMethod)
Invoke(human, ref *(CustomizeArray*)data); Invoke(human, ref *(CustomizeArray*)data);
return _changeCustomizeHook.Original(human, data, skipEquipment); var ret = _changeCustomizeHook.Original(human, data, skipEquipment);
_postEvent.Invoke(human);
return ret;
}
public void Subscribe(Action<Model> action, Post.Priority priority)
=> _postEvent.Subscribe(action, priority);
public void Unsubscribe(Action<Model> action)
=> _postEvent.Unsubscribe(action);
public sealed class Post() : EventWrapper<Model, Post.Priority>(nameof(ChangeCustomizeService) + '.' + nameof(Post))
{
public enum Priority
{
/// <seealso cref="State.StateListener.OnCustomizeChanged"/>
StateListener = 0,
}
} }
} }

View file

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OtterGui.Log; using OtterGui.Log;
using Penumbra.GameData.Actors;
namespace Glamourer.Interop.Structs; namespace Glamourer.Interop.Structs;

View file

@ -7,6 +7,7 @@ using Dalamud.Plugin.Services;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Gui; using Glamourer.Gui;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.State; using Glamourer.State;
@ -500,7 +501,7 @@ public class CommandService : IDisposable
&& _stateManager.GetOrCreate(identifier, data.Objects[0], out state))) && _stateManager.GetOrCreate(identifier, data.Objects[0], out state)))
continue; continue;
var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All); var design = _converter.Convert(state, EquipFlagExtensions.All, CustomizeFlagExtensions.AllRelevant, CrestExtensions.All, CustomizeParameterExtensions.All);
_designManager.CreateClone(design, split[0], true); _designManager.CreateClone(design, split[0], true);
return true; return true;
} }

View file

@ -5,6 +5,7 @@ using Penumbra.GameData.Enums;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Glamourer.GameData;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.State; namespace Glamourer.State;
@ -79,7 +80,11 @@ public class ActorState
/// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary> /// <summary> This contains whether a change to the base data was made by the game, the user via manual input or through automatic application. </summary>
private readonly StateChanged.Source[] _sources = Enumerable private readonly StateChanged.Source[] _sources = Enumerable
.Repeat(StateChanged.Source.Game, .Repeat(StateChanged.Source.Game,
EquipFlagExtensions.NumEquipFlags + CustomizationExtensions.NumIndices + 5 + CrestExtensions.AllRelevantSet.Count).ToArray(); EquipFlagExtensions.NumEquipFlags
+ CustomizationExtensions.NumIndices
+ 5
+ CrestExtensions.AllRelevantSet.Count
+ CustomizeParameterExtensions.AllFlags.Count).ToArray();
internal ActorState(ActorIdentifier identifier) internal ActorState(ActorIdentifier identifier)
=> Identifier = identifier.CreatePermanent(); => Identifier = identifier.CreatePermanent();
@ -96,6 +101,13 @@ public class ActorState
public ref StateChanged.Source this[MetaIndex index] public ref StateChanged.Source this[MetaIndex index]
=> ref _sources[(int)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() public void RemoveFixedDesignSources()
{ {
for (var i = 0; i < _sources.Length; ++i) for (var i = 0; i < _sources.Length; ++i)
@ -105,6 +117,9 @@ public class ActorState
} }
} }
public CustomizeParameterFlag OnlyChangedParameters()
=> CustomizeParameterExtensions.AllFlags.Where(f => this[f] is not StateChanged.Source.Game).Aggregate((CustomizeParameterFlag) 0, (a, b) => a | b);
public bool UpdateTerritory(ushort territory) public bool UpdateTerritory(ushort territory)
{ {
if (territory == LastTerritory) if (territory == LastTerritory)

View file

@ -1,5 +1,7 @@
using System.Linq; using System.Linq;
using FFXIVClientStructs.FFXIV.Shader;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
@ -14,8 +16,17 @@ namespace Glamourer.State;
/// This class applies changes made to state to actual objects in the game. /// This class applies changes made to state to actual objects in the game.
/// It handles applying those changes as well as redrawing the actor if necessary. /// It handles applying those changes as well as redrawing the actor if necessary.
/// </summary> /// </summary>
public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, WeaponService _weapon, ChangeCustomizeService _changeCustomize, public class StateApplier(
ItemManager _items, PenumbraService _penumbra, MetaService _metaService, ObjectManager _objects, CrestService _crests) UpdateSlotService _updateSlot,
VisorService _visor,
WeaponService _weapon,
ChangeCustomizeService _changeCustomize,
ItemManager _items,
PenumbraService _penumbra,
MetaService _metaService,
ObjectManager _objects,
CrestService _crests,
Configuration _config)
{ {
/// <summary> Simply force a redraw regardless of conditions. </summary> /// <summary> Simply force a redraw regardless of conditions. </summary>
public void ForceRedraw(ActorData data) public void ForceRedraw(ActorData data)
@ -271,6 +282,35 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
return data; return data;
} }
/// <summary> Change the customize parameters on models. Can change multiple at once. </summary>
public unsafe void ChangeParameters(ActorData data, CustomizeParameterFlag flags, in CustomizeParameterData values)
{
if (!_config.UseAdvancedParameters)
return;
foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true }))
{
var buffer = actor.Model.AsHuman->CustomizeParameterCBuffer;
if (buffer == null)
continue;
var ptr = (CustomizeParameter*)buffer->UnsafeSourcePointer;
if (ptr == null)
continue;
values.Apply(ref *ptr, flags);
}
}
/// <inheritdoc cref="ChangeParameters(ActorData,CustomizeParameterFlag,in CustomizeParameterData)"/>
public ActorData ChangeParameters(ActorState state, CustomizeParameterFlag flags, bool apply)
{
var data = GetData(state);
if (apply)
ChangeParameters(data, flags, state.ModelData.Parameters);
return data;
}
private ActorData GetData(ActorState state) private ActorData GetData(ActorState state)
{ {
_objects.Update(); _objects.Update();

View file

@ -1,7 +1,9 @@
using System; using System;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Common.Math;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -208,6 +210,19 @@ public class StateEditor
return true; return true;
} }
/// <summary> Change the customize flags of a character. </summary>
public bool ChangeParameter(ActorState state, CustomizeParameterFlag flag, Vector3 value, StateChanged.Source source, out Vector3 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, public bool ChangeMetaState(ActorState state, ActorState.MetaIndex index, bool value, StateChanged.Source source, out bool oldValue,
uint key = 0) uint key = 0)
{ {

View file

@ -4,13 +4,14 @@ using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using OtterGui.Classes;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using System; using System;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Shader;
using Glamourer.GameData;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
namespace Glamourer.State; namespace Glamourer.State;
@ -46,6 +47,7 @@ public class StateListener : IDisposable
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid; private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
private ActorState? _creatingState; private ActorState? _creatingState;
private ActorState? _customizeState;
private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty; private CharacterWeapon _lastFistOffhand = CharacterWeapon.Empty;
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config, public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorManager actors, Configuration config,
@ -148,10 +150,10 @@ public class StateListener : IDisposable
return; return;
if (!actor.Identifier(_actors, out var identifier) if (!actor.Identifier(_actors, out var identifier)
|| !_manager.TryGetValue(identifier, out var state)) || !_manager.TryGetValue(identifier, out _customizeState))
return; return;
UpdateCustomize(actor, state, ref customize, false); UpdateCustomize(actor, _customizeState, ref customize, false);
} }
private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform) private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform)
@ -671,6 +673,7 @@ public class StateListener : IDisposable
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener); _changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
_crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener); _crestService.Subscribe(OnCrestChange, CrestService.Priority.StateListener);
_crestService.ModelCrestSetup += OnModelCrestSetup; _crestService.ModelCrestSetup += OnModelCrestSetup;
_changeCustomizeService.Subscribe(OnCustomizeChanged, ChangeCustomizeService.Post.Priority.StateListener);
} }
private void Unsubscribe() private void Unsubscribe()
@ -686,6 +689,7 @@ public class StateListener : IDisposable
_changeCustomizeService.Unsubscribe(OnCustomizeChange); _changeCustomizeService.Unsubscribe(OnCustomizeChange);
_crestService.Unsubscribe(OnCrestChange); _crestService.Unsubscribe(OnCrestChange);
_crestService.ModelCrestSetup -= OnModelCrestSetup; _crestService.ModelCrestSetup -= OnModelCrestSetup;
_changeCustomizeService.Unsubscribe(OnCustomizeChanged);
} }
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject) private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
@ -700,5 +704,58 @@ public class StateListener : IDisposable
_applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible()); _applier.ChangeHatState(data, _creatingState.ModelData.IsHatVisible());
_applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible()); _applier.ChangeWeaponState(data, _creatingState.ModelData.IsWeaponVisible());
_applier.ChangeWetness(data, _creatingState.ModelData.IsWet()); _applier.ChangeWetness(data, _creatingState.ModelData.IsWet());
ApplyParameters(_creatingState, drawObject);
}
private void OnCustomizeChanged(Model model)
{
if (_customizeState == null)
{
var actor = _penumbra.GameObjectFromDrawObject(model);
if (_condition[ConditionFlag.CreatingCharacter] && actor.Index >= ObjectIndex.CutsceneStart)
return;
if (!actor.Identifier(_actors, out var identifier)
|| !_manager.TryGetValue(identifier, out _customizeState))
return;
}
ApplyParameters(_customizeState, model);
_customizeState = null;
}
private unsafe void ApplyParameters(ActorState state, Model model)
{
if (!model.IsHuman)
return;
var cBuffer = model.AsHuman->CustomizeParameterCBuffer;
if (cBuffer == null)
return;
var ptr = (CustomizeParameter*)cBuffer->UnsafeSourcePointer;
if (ptr == null)
return;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
var newValue = CustomizeParameterData.FromParameter(*ptr, flag);
switch (state[flag])
{
case StateChanged.Source.Game:
case StateChanged.Source.Manual:
if (state.BaseData.Parameters.Set(flag, newValue))
_manager.ChangeCustomizeParameter(state, flag, newValue, StateChanged.Source.Game);
break;
case StateChanged.Source.Fixed:
case StateChanged.Source.Ipc:
state.BaseData.Parameters.Set(flag, newValue);
if (_config.UseAdvancedParameters)
state.ModelData.Parameters.ApplySingle(ref *ptr, flag);
break;
}
}
} }
} }

View file

@ -3,9 +3,12 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Numerics;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Shader;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
@ -189,6 +192,14 @@ public class StateManager(
// Weapon visibility could technically be inferred from the weapon draw objects, // Weapon visibility could technically be inferred from the weapon draw objects,
// but since we use hat visibility from the game object we can also use weapon visibility from it. // but since we use hat visibility from the game object we can also use weapon visibility from it.
ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden); ret.SetWeaponVisible(!actor.AsCharacter->DrawData.IsWeaponHidden);
if (model.IsHuman && model.AsHuman->CustomizeParameterCBuffer != null)
{
var ptr = model.AsHuman->CustomizeParameterCBuffer->UnsafeSourcePointer;
if (ptr != null)
ret.Parameters = CustomizeParameterData.FromParameters(*(CustomizeParameter*)ptr);
}
return ret; return ret;
} }
@ -309,6 +320,19 @@ public class StateManager(
_event.Invoke(StateChanged.Type.Crest, source, state, actors, (old, crest, slot)); _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, Vector3 value, StateChanged.Source source, uint key = 0)
{
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> /// <summary> Change hat visibility. </summary>
public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0) public void ChangeHatState(ActorState state, bool value, StateChanged.Source source, uint key = 0)
{ {
@ -390,6 +414,9 @@ public class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest)) foreach (var slot in CrestExtensions.AllRelevantSet.Where(design.DoApplyCrest))
_editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key); _editor.ChangeCrest(state, slot, design.DesignData.Crest(slot), source, out _, key);
foreach (var flag in CustomizeParameterExtensions.AllFlags.Where(design.DoApplyParameter))
_editor.ChangeParameter(state, flag, design.DesignData.Parameters[flag], source, out _, key);
} }
var actors = ApplyAll(state, redraw, false); var actors = ApplyAll(state, redraw, false);
@ -441,6 +468,7 @@ public class StateManager(
_applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible()); _applier.ChangeWeaponState(actors, state.ModelData.IsWeaponVisible());
_applier.ChangeVisor(actors, state.ModelData.IsVisorToggled()); _applier.ChangeVisor(actors, state.ModelData.IsVisorToggled());
_applier.ChangeCrests(actors, state.ModelData.CrestVisibility); _applier.ChangeCrests(actors, state.ModelData.CrestVisibility);
_applier.ChangeParameters(actors, state.OnlyChangedParameters(), state.ModelData.Parameters);
} }
return actors; return actors;
@ -454,6 +482,7 @@ public class StateManager(
var redraw = state.ModelData.ModelId != state.BaseData.ModelId var redraw = state.ModelData.ModelId != state.BaseData.ModelId
|| !state.ModelData.IsHuman || !state.ModelData.IsHuman
|| CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw(); || CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
state.ModelData = state.BaseData; state.ModelData = state.BaseData;
state.ModelData.SetIsWet(false); state.ModelData.SetIsWet(false);
foreach (var index in Enum.GetValues<CustomizeIndex>()) foreach (var index in Enum.GetValues<CustomizeIndex>())
@ -471,9 +500,13 @@ public class StateManager(
foreach (var slot in CrestExtensions.AllRelevantSet) foreach (var slot in CrestExtensions.AllRelevantSet)
state[slot] = StateChanged.Source.Game; state[slot] = StateChanged.Source.Game;
foreach (var flag in CustomizeParameterExtensions.AllFlags)
state[flag] = StateChanged.Source.Game;
var actors = ActorData.Invalid; var actors = ActorData.Invalid;
if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc) if (source is StateChanged.Source.Manual or StateChanged.Source.Ipc)
actors = ApplyAll(state, redraw, true); actors = ApplyAll(state, redraw, true);
Glamourer.Log.Verbose( Glamourer.Log.Verbose(
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null); _event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null);
@ -514,6 +547,15 @@ public class StateManager(
} }
} }
foreach (var flag in CustomizeParameterExtensions.AllFlags)
{
if (state[flag] is StateChanged.Source.Fixed)
{
state[flag] = StateChanged.Source.Game;
state.ModelData.Parameters[flag] = state.BaseData.Parameters[flag];
}
}
if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed) if (state[ActorState.MetaIndex.HatState] is StateChanged.Source.Fixed)
{ {
state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game; state[ActorState.MetaIndex.HatState] = StateChanged.Source.Game;