Improve advanced dye stuff.

This commit is contained in:
Ottermandias 2024-02-14 18:51:48 +01:00
parent a194f88903
commit 10962cac6c
16 changed files with 342 additions and 113 deletions

View file

@ -35,6 +35,7 @@ public class Configuration : IPluginConfiguration, ISavable
public bool ShowQuickBarInTabs { get; set; } = true;
public bool OpenWindowAtStart { get; set; } = false;
public bool UseAdvancedParameters { get; set; } = true;
public bool UseAdvancedDyes { get; set; } = false;
public bool ShowRevertAdvancedParametersButton { get; set; } = true;
public bool ShowPalettePlusImport { get; set; } = true;
public bool UseFloatForColors { get; set; } = true;

View file

@ -33,7 +33,7 @@ public class DesignEditor(
/// <inheritdoc/>
public void ChangeCustomize(object data, CustomizeIndex idx, CustomizeValue value, ApplySettings _ = default)
{
var design = (Design)data;
var design = (Design)data;
var oldValue = design.DesignData.Customize[idx];
switch (idx)
{
@ -96,7 +96,7 @@ public class DesignEditor(
public void ChangeCustomizeParameter(object data, CustomizeParameterFlag flag, CustomizeParameterValue value, ApplySettings _ = default)
{
var design = (Design)data;
var old = design.DesignData.Parameters[flag];
var old = design.DesignData.Parameters[flag];
if (!design.GetDesignDataRef().Parameters.Set(flag, value))
return;
@ -219,6 +219,68 @@ public class DesignEditor(
DesignChanged.Invoke(DesignChanged.Type.Other, design, (metaIndex, false, value));
}
public void ChangeMaterialRevert(Design design, MaterialValueIndex index, bool revert)
{
var materials = design.GetMaterialDataRef();
if (!materials.TryGetValue(index, out var oldValue))
return;
materials.AddOrUpdateValue(index, oldValue with { Revert = revert });
Glamourer.Log.Debug($"Changed advanced dye value for {index} to {(revert ? "Revert." : "no longer Revert.")}");
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, index);
}
public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row)
{
var materials = design.GetMaterialDataRef();
if (materials.TryGetValue(index, out var oldValue))
{
if (!row.HasValue)
{
materials.RemoveValue(index);
Glamourer.Log.Debug($"Removed advanced dye value for {index}.");
}
else if (!row.Value.NearEqual(oldValue.Value))
{
materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert), out _);
Glamourer.Log.Debug($"Updated advanced dye value for {index} to new value.");
}
else
{
return;
}
}
else
{
if (!row.HasValue)
return;
if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false)))
return;
Glamourer.Log.Debug($"Added new advanced dye value for {index}.");
}
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.DelaySave(design);
DesignChanged.Invoke(DesignChanged.Type.Material, design, (oldValue, row, index));
}
public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value)
{
var materials = design.GetMaterialDataRef();
if (!materials.TryGetValue(index, out var oldValue) || oldValue.Enabled == value)
return;
materials.AddOrUpdateValue(index, oldValue with { Enabled = value });
Glamourer.Log.Debug($"Changed application of advanced dye for {index} to {value}.");
design.LastEdit = DateTimeOffset.UtcNow;
SaveService.QueueSave(design);
DesignChanged.Invoke(DesignChanged.Type.ApplyMaterial, design, index);
}
/// <inheritdoc/>
public void ApplyDesign(object data, MergedDesign other, ApplySettings _ = default)
=> ApplyDesign(data, other.Design);
@ -262,7 +324,8 @@ public class DesignEditor(
}
/// <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,
private bool ChangeMainhandPeriphery(DesignBase design, EquipItem currentMain, EquipItem currentOff, EquipItem newMain,
out EquipItem? newOff,
out EquipItem? newGauntlets)
{
newOff = null;

View file

@ -1,5 +1,6 @@
using Glamourer.Automation;
using Glamourer.GameData;
using Glamourer.Interop.Material;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Unlocks;
@ -46,6 +47,7 @@ public class DesignMerger(
ReduceCrests(data, crestFlags, ret, source);
ReduceParameters(data, parameterFlags, ret, source);
ReduceMods(design as Design, ret, modAssociations);
ReduceMaterials(design, ret);
}
ApplyFixFlags(ret, fixFlags);
@ -53,6 +55,15 @@ public class DesignMerger(
}
private static void ReduceMaterials(DesignBase? design, MergedDesign ret)
{
if (design == null)
return;
var materials = ret.Design.GetMaterialDataRef();
foreach (var (key, value) in design.Materials.Where(p => p.Item2.Enabled))
materials.TryAddValue(MaterialValueIndex.FromKey(key), value);
}
private static void ReduceMods(Design? design, MergedDesign ret, bool modAssociations)
{
if (design == null || !modAssociations)

View file

@ -74,10 +74,16 @@ public sealed class DesignChanged()
/// <summary> An existing design had a customize parameter changed. Data is the old value, the new value and the flag [(CustomizeParameterValue, CustomizeParameterValue, CustomizeParameterFlag)]. </summary>
Parameter,
/// <summary> An existing design had an advanced dye row added, changed, or deleted. Data is the old value, the new value and the index [(ColorRow?, ColorRow?, MaterialValueIndex)]. </summary>
Material,
/// <summary> An existing design had an advanced dye rows Revert state changed. Data is the index [MaterialValueIndex]. </summary>
MaterialRevert,
/// <summary> An existing design changed whether a specific customization is applied. Data is the type of customization [CustomizeIndex]. </summary>
ApplyCustomize,
/// <summary> An existing design changed whether a specific equipment is applied. Data is the slot of the equipment [EquipSlot]. </summary>
/// <summary> An existing design changed whether a specific equipment piece is applied. Data is the slot of the equipment [EquipSlot]. </summary>
ApplyEquip,
/// <summary> An existing design changed whether a specific stain is applied. Data is the slot of the equipment [EquipSlot]. </summary>
@ -89,6 +95,9 @@ public sealed class DesignChanged()
/// <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 whether an advanced dye row is applied. Data is the index [MaterialValueIndex]. </summary>
ApplyMaterial,
/// <summary> An existing design changed its write protection status. Data is the new value [bool]. </summary>
WriteProtection,

View file

@ -80,7 +80,7 @@ public partial class CustomizationDrawer
{
var size = ImGui.GetItemRectSize();
ImGui.GetWindowDrawList()
.AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBW(custom.Color));
.AddCircleFilled(ImGui.GetItemRectMin() + size / 2, size.X / 4, ImGuiUtil.ContrastColorBw(custom.Color));
}
if (i % 8 != 7)

View file

@ -240,7 +240,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (_playerIdentifier.IsValid && _playerState is { IsLocked: false } && _playerData.Valid)
{
available |= 1;
tooltip = "Left-Click: Revert the advanced customizations of the player character to their game state.";
tooltip = "Left-Click: Revert the advanced customizations and dyes of the player character to their game state.";
}
if (_targetIdentifier.IsValid && _targetState is { IsLocked: false } && _targetData.Valid)
@ -248,7 +248,7 @@ public sealed class DesignQuickBar : Window, IDisposable
if (available != 0)
tooltip += '\n';
available |= 2;
tooltip += $"Right-Click: Revert the advanced customizations of {_targetIdentifier} to their game state.";
tooltip += $"Right-Click: Revert the advanced customizations and dyes of {_targetIdentifier} to their game state.";
}
if (available == 0)

View file

@ -10,20 +10,135 @@ using OtterGui;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Gui;
using Penumbra.GameData.Structs;
using Vortice.DXGI;
namespace Glamourer.Gui.Materials;
public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager) : IService
public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _designManager, Configuration _config) : IService
{
private static readonly IReadOnlyList<MaterialValueIndex.DrawObjectType> Types =
[
MaterialValueIndex.DrawObjectType.Human,
MaterialValueIndex.DrawObjectType.Mainhand,
MaterialValueIndex.DrawObjectType.Offhand,
];
private ActorState? _state;
private EquipSlot _newSlot = EquipSlot.Head;
private int _newMaterialIdx;
private int _newRowIdx;
private MaterialValueIndex _newKey = MaterialValueIndex.Min();
private ActorState? _state;
public void DrawDesignPanel(Design design)
{
var buttonSize = new Vector2(ImGui.GetFrameHeight());
using (var table = ImRaii.Table("table", 5, ImGuiTableFlags.RowBg))
{
if (!table)
return;
ImGui.TableSetupColumn("button", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("enabled", ImGuiTableColumnFlags.WidthFixed, buttonSize.X);
ImGui.TableSetupColumn("values", ImGuiTableColumnFlags.WidthFixed, ImGui.GetStyle().ItemInnerSpacing.X * 4 + 3 * buttonSize.X + 220 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("revert", ImGuiTableColumnFlags.WidthFixed, buttonSize.X + ImGui.CalcTextSize("Revertm").X);
ImGui.TableSetupColumn("slot", ImGuiTableColumnFlags.WidthStretch);
for (var i = 0; i < design.Materials.Count; ++i)
{
var (idx, value) = design.Materials[i];
var key = MaterialValueIndex.FromKey(idx);
var name = key.DrawObject switch
{
MaterialValueIndex.DrawObjectType.Human => ((uint)key.SlotIndex).ToEquipSlot().ToName(),
MaterialValueIndex.DrawObjectType.Mainhand => EquipSlot.MainHand.ToName(),
MaterialValueIndex.DrawObjectType.Offhand => EquipSlot.OffHand.ToName(),
_ => string.Empty,
};
if (name.Length == 0)
continue;
name = $"{name} Material #{key.MaterialIndex + 1} Row #{key.RowIndex + 1}";
using var id = ImRaii.PushId((int)idx);
var deleteEnabled = _config.DeleteDesignModifier.IsActive();
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize,
$"Delete this color row.{(deleteEnabled ? string.Empty : $"\nHold {_config.DeleteDesignModifier} to delete.")}",
!deleteEnabled, true))
{
_designManager.ChangeMaterialValue(design, key, null);
--i;
}
ImGui.TableNextColumn();
var enabled = value.Enabled;
if (ImGui.Checkbox("Enabled", ref enabled))
_designManager.ChangeApplyMaterialValue(design, key, enabled);
ImGui.TableNextColumn();
var revert = value.Revert;
using (ImRaii.Disabled(revert))
{
var row = value.Value;
DrawRow(design, key, row);
}
ImGui.TableNextColumn();
if (ImGui.Checkbox("Revert", ref revert))
_designManager.ChangeMaterialRevert(design, key, revert);
ImGuiUtil.HoverTooltip(
"If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row.");
ImGui.TableNextColumn();
ImGui.TextUnformatted(name);
}
}
var exists = design.GetMaterialDataRef().TryGetValue(_newKey, out _);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), buttonSize,
exists ? "The selected advanced dye row already exists." : "Add the selected advanced dye row.", exists, true))
_designManager.ChangeMaterialValue(design, _newKey, ColorRow.Empty);
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
if (EquipSlotCombo.Draw("##slot", "Choose a slot for an advanced dye row.", ref _newSlot))
_newKey = _newSlot switch
{
EquipSlot.MainHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, (byte)_newMaterialIdx,
(byte)_newRowIdx),
EquipSlot.OffHand => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, (byte)_newMaterialIdx,
(byte)_newRowIdx),
_ => new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)_newSlot.ToIndex(), (byte)_newMaterialIdx,
(byte)_newRowIdx),
};
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
DrawMaterialIdxDrag();
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
DrawRowIdxDrag();
}
private void DrawMaterialIdxDrag()
{
_newMaterialIdx += 1;
ImGui.SetNextItemWidth(ImGui.CalcTextSize("Material #000").X);
if (ImGui.DragInt("##Material", ref _newMaterialIdx, 0.01f, 1, MaterialService.MaterialsPerModel, "Material #%i"))
{
_newMaterialIdx = Math.Clamp(_newMaterialIdx, 1, MaterialService.MaterialsPerModel);
_newKey = _newKey with { MaterialIndex = (byte)(_newMaterialIdx - 1) };
}
_newMaterialIdx -= 1;
}
private void DrawRowIdxDrag()
{
_newRowIdx += 1;
ImGui.SetNextItemWidth(ImGui.CalcTextSize("Row #0000").X);
if (ImGui.DragInt("##Row", ref _newRowIdx, 0.01f, 1, MtrlFile.ColorTable.NumRows, "Row #%i"))
{
_newRowIdx = Math.Clamp(_newRowIdx, 1, MtrlFile.ColorTable.NumRows);
_newKey = _newKey with { RowIndex = (byte)(_newRowIdx - 1) };
}
_newRowIdx -= 1;
}
public void DrawActorPanel(Actor actor)
{
@ -41,20 +156,21 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
{
var item = model.GetArmor(slot).ToWeapon(0);
DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte) idx, 0, 0));
DrawSlotMaterials(model, slot.ToName(), item, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Human, (byte)idx, 0, 0));
}
var (mainhand, offhand, mh, oh) = actor.Model.GetWeapons(actor);
if (mainhand.IsWeapon && mainhand.AsCharacterBase->SlotCount > 0)
DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0));
DrawSlotMaterials(mainhand, EquipSlot.MainHand.ToName(), mh,
new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Mainhand, 0, 0, 0));
if (offhand.IsWeapon && offhand.AsCharacterBase->SlotCount > 0)
DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh, new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0));
DrawSlotMaterials(offhand, EquipSlot.OffHand.ToName(), oh,
new MaterialValueIndex(MaterialValueIndex.DrawObjectType.Offhand, 0, 0, 0));
}
private void DrawSlotMaterials(Model model, string name, CharacterWeapon drawData, MaterialValueIndex index)
{
var drawnMaterial = 1;
for (byte materialIndex = 0; materialIndex < MaterialService.MaterialsPerModel; ++materialIndex)
{
var texture = model.AsCharacterBase->ColorTableTextures + index.SlotIndex * MaterialService.MaterialsPerModel + materialIndex;
@ -64,11 +180,11 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
if (!DirectXTextureHelper.TryGetColorTable(*texture, out var table))
continue;
using var tree = ImRaii.TreeNode($"{name} Material #{drawnMaterial++}###{name}{materialIndex}");
using var tree = ImRaii.TreeNode($"{name} Material #{materialIndex + 1}###{name}{materialIndex}");
if (!tree)
continue;
DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex} );
DrawMaterial(ref table, drawData, index with { MaterialIndex = materialIndex });
}
}
@ -82,6 +198,27 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
}
}
private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row)
{
var spacing = ImGui.GetStyle().ItemInnerSpacing;
var tmp = row;
var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", row.Diffuse, v => tmp.Diffuse = v, "D");
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", row.Specular, v => tmp.Specular = v, "S");
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", row.Emissive, v => tmp.Emissive = v, "E");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Gloss", ref tmp.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G");
ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(120 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("##Specular Strength", ref tmp.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS");
ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
if (applied)
_designManager.ChangeMaterialValue(design, index, tmp);
}
private void DrawRow(ref MtrlFile.ColorTable.Row row, CharacterWeapon drawData, MaterialValueIndex index)
{
using var id = ImRaii.PushId(index.RowIndex);
@ -92,22 +229,33 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
value = new MaterialValueState(internalRow, internalRow, drawData, StateSource.Manual);
}
var applied = ImGui.ColorEdit3("Diffuse", ref value.Model.Diffuse, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
applied |= ImGui.ColorEdit3("Specular", ref value.Model.Specular, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
applied |= ImGui.ColorEdit3("Emissive", ref value.Model.Emissive, ImGuiColorEditFlags.NoInputs);
ImGui.SameLine();
ImGui.AlignTextToFramePadding();
using (ImRaii.PushFont(UiBuilder.MonoFont))
{
ImGui.TextUnformatted($"Row {index.RowIndex + 1:D2}");
}
ImGui.SameLine(0, ImGui.GetStyle().ItemSpacing.X * 2);
var applied = ImGuiUtil.ColorPicker("##diffuse", "Change the diffuse value for this row.", value.Model.Diffuse, v => value.Model.Diffuse = v, "D");
var spacing = ImGui.GetStyle().ItemInnerSpacing;
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##specular", "Change the specular value for this row.", value.Model.Specular, v => value.Model.Specular = v, "S");
ImGui.SameLine(0, spacing.X);
applied |= ImGuiUtil.ColorPicker("##emissive", "Change the emissive value for this row.", value.Model.Emissive, v => value.Model.Emissive = v, "E");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("Gloss", ref value.Model.GlossStrength, 0.1f);
ImGui.SameLine();
applied |= ImGui.DragFloat("##Gloss", ref value.Model.GlossStrength, 0.01f, 0.001f, float.MaxValue, "%.3f G") && value.Model.GlossStrength > 0;
ImGuiUtil.HoverTooltip("Change the gloss strength for this row.");
ImGui.SameLine(0, spacing.X);
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
applied |= ImGui.DragFloat("Specular Strength", ref value.Model.SpecularStrength, 0.1f);
applied |= ImGui.DragFloat("##Specular Strength", ref value.Model.SpecularStrength, 0.01f, float.MinValue, float.MaxValue, "%.3f SS");
ImGuiUtil.HoverTooltip("Change the specular strength for this row.");
if (applied)
_stateManager.ChangeMaterialValue(_state!, index, value, ApplySettings.Manual);
if (changed)
{
ImGui.SameLine();
ImGui.SameLine(0, spacing.X);
using (ImRaii.PushFont(UiBuilder.IconFont))
{
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.FavoriteStarOn.Value());
@ -115,52 +263,4 @@ public unsafe class MaterialDrawer(StateManager _stateManager, DesignManager _de
}
}
}
private static readonly IReadOnlyList<string> SlotNames =
[
"Slot 1",
"Slot 2",
"Slot 3",
"Slot 4",
"Slot 5",
"Slot 6",
"Slot 7",
"Slot 8",
"Slot 9",
"Slot 10",
"Slot 11",
"Slot 12",
"Slot 13",
"Slot 14",
"Slot 15",
"Slot 16",
"Slot 17",
"Slot 18",
"Slot 19",
"Slot 20",
];
private static readonly IReadOnlyList<string> SlotNamesHuman =
[
"Head",
"Body",
"Hands",
"Legs",
"Feet",
"Earrings",
"Neck",
"Wrists",
"Right Finger",
"Left Finger",
"Slot 11",
"Slot 12",
"Slot 13",
"Slot 14",
"Slot 15",
"Slot 16",
"Slot 17",
"Slot 18",
"Slot 19",
"Slot 20",
];
}

View file

@ -117,9 +117,8 @@ public class ActorPanel(
RevertButtons();
// TODO Materials
//if (ImGui.CollapsingHeader("Material Shit"))
// _materialDrawer.DrawActorPanel(_actor);
if (_config.UseAdvancedDyes && ImGui.CollapsingHeader("Material Shit"))
_materialDrawer.DrawActorPanel(_actor);
using var disabled = ImRaii.Disabled(transformationId != 0);
if (_state.ModelData.IsHuman)
DrawHumanPanel();

View file

@ -7,6 +7,7 @@ using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Materials;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
@ -31,7 +32,8 @@ public class DesignPanel(
ImportService _importService,
MultiDesignPanel _multiDesignPanel,
CustomizeParameterDrawer _parameterDrawer,
DesignLinkDrawer _designLinkDrawer)
DesignLinkDrawer _designLinkDrawer,
MaterialDrawer _materials)
{
private readonly FileDialogManager _fileDialog = new();
@ -176,21 +178,14 @@ public class DesignPanel(
private void DrawMaterialValues()
{
if (!_config.UseAdvancedParameters)
if (!_config.UseAdvancedDyes)
return;
using var h = ImRaii.CollapsingHeader("Advanced Dyes");
if (!h)
return;
foreach (var ((key, value), i) in _selector.Selected!.Materials.WithIndex())
{
using var id = ImRaii.PushId(i);
ImGui.TextUnformatted($"{key:X16}");
ImGui.SameLine();
var enabled = value.Enabled;
ImGui.Checkbox("Enabled", ref enabled);
}
_materials.DrawDesignPanel(_selector.Selected!);
}
private void DrawCustomizeApplication()
@ -384,7 +379,7 @@ public class DesignPanel(
DrawCustomize();
DrawEquipment();
DrawCustomizeParameters();
//DrawMaterialValues(); TODO Materials
DrawMaterialValues();
_designDetails.Draw();
DrawApplicationRules();
_modAssociations.Draw();

View file

@ -18,20 +18,21 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
private readonly StateManager _stateManager;
private readonly PenumbraService _penumbra;
private readonly ActorManager _actors;
private readonly Configuration _config;
private int _lastSlot;
private readonly ThreadLocal<List<MaterialValueIndex>> _deleteList = new(() => []);
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra)
public MaterialManager(PrepareColorSet prepareColorSet, StateManager stateManager, ActorManager actors, PenumbraService penumbra,
Configuration config)
{
_stateManager = stateManager;
_actors = actors;
_penumbra = penumbra;
_config = config;
_event = prepareColorSet;
// TODO Material
//_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager);
_event.Subscribe(OnPrepareColorSet, PrepareColorSet.Priority.MaterialManager);
}
public void Dispose()
@ -39,6 +40,9 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
private void OnPrepareColorSet(CharacterBase* characterBase, MaterialResourceHandle* material, ref StainId stain, ref nint ret)
{
if (!_config.UseAdvancedDyes)
return;
var actor = _penumbra.GameObjectFromDrawObject(characterBase);
var validType = FindType(characterBase, actor, out var type);
var (slotId, materialId) = FindMaterial(characterBase, material);
@ -176,7 +180,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
}
/// <summary> We need to get the temporary set, variant and stain that is currently being set if it is available. </summary>
private CharacterWeapon GetTempSlot(Human* human, byte slotId)
private static CharacterWeapon GetTempSlot(Human* human, byte slotId)
{
if (human->ChangedEquipData == null)
return ((Model)human).GetArmor(((uint)slotId).ToEquipSlot()).ToWeapon(0);
@ -188,7 +192,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable
/// We need to get the temporary set, variant and stain that is currently being set if it is available.
/// Weapons do not change in skeleton id without being reconstructed, so this is not changeable data.
/// </summary>
private CharacterWeapon GetTempSlot(Weapon* weapon)
private static CharacterWeapon GetTempSlot(Weapon* weapon)
{
var changedData = *(void**)((byte*)weapon + 0x918);
if (changedData == null)

View file

@ -2,6 +2,7 @@
using FFXIVClientStructs.Interop;
using Glamourer.Interop.Structs;
using Newtonsoft.Json;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
namespace Glamourer.Interop.Material;
@ -150,6 +151,16 @@ public readonly record struct MaterialValueIndex(
: this((DrawObjectType)(key >> 24), (byte)(key >> 16), (byte)(key >> 8), (byte)key)
{ }
public override string ToString()
=> DrawObject switch
{
DrawObjectType.Human when SlotIndex < 10 =>
$"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
DrawObjectType.Mainhand when SlotIndex == 0 => $"{EquipSlot.MainHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
DrawObjectType.Offhand when SlotIndex == 0 => $"{EquipSlot.OffHand.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
_ => $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}",
};
private class Converter : JsonConverter<MaterialValueIndex>
{
public override void WriteJson(JsonWriter writer, MaterialValueIndex value, JsonSerializer serializer)

View file

@ -126,16 +126,26 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa
}
[JsonConverter(typeof(Converter))]
public struct MaterialValueDesign(ColorRow value, bool enabled)
public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert)
{
public ColorRow Value = value;
public bool Enabled = enabled;
public bool Revert = revert;
public readonly bool Apply(ref MaterialValueState state)
{
if (!Enabled)
return false;
if (revert)
{
if (state.Model.NearEqual(state.Game))
return false;
state.Model = state.Game;
return true;
}
if (state.Model.NearEqual(Value))
return false;
@ -148,6 +158,8 @@ public struct MaterialValueDesign(ColorRow value, bool enabled)
public override void WriteJson(JsonWriter writer, MaterialValueDesign value, JsonSerializer serializer)
{
writer.WriteStartObject();
writer.WritePropertyName("Revert");
writer.WriteValue(value.Revert);
writer.WritePropertyName("DiffuseR");
writer.WriteValue(value.Value.Diffuse.X);
writer.WritePropertyName("DiffuseG");
@ -180,6 +192,7 @@ public struct MaterialValueDesign(ColorRow value, bool enabled)
JsonSerializer serializer)
{
var obj = JObject.Load(reader);
Set(ref existingValue.Revert, obj["Revert"]?.Value<bool>());
Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value<float>());
Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value<float>());
@ -235,7 +248,7 @@ public struct MaterialValueState(
&& Game.NearEqual(rhsRow);
public readonly MaterialValueDesign Convert()
=> new(Model, true);
=> new(Model, true, false);
}
public readonly struct MaterialValueManager<T>

View file

@ -278,7 +278,7 @@ public class StateApplier(
public unsafe void ChangeMaterialValue(ActorData data, MaterialValueIndex index, ColorRow? value, bool force)
{
if (!force && !_config.UseAdvancedParameters)
if (!force && !_config.UseAdvancedDyes)
return;
foreach (var actor in data.Objects.Where(a => a is { IsCharacter: true, Model.IsHuman: true }))

View file

@ -172,7 +172,8 @@ public class StateEditor(
return;
var actors = Applier.ChangeMaterialValue(state, index, settings.Source.RequiresChange());
Glamourer.Log.Verbose($"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]");
Glamourer.Log.Verbose(
$"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.MaterialValue, settings.Source, state, actors, (oldValue, newValue.Game, index));
}
@ -288,13 +289,25 @@ public class StateEditor(
if (!value.Enabled)
continue;
var idx = MaterialValueIndex.FromKey(key);
// TODO
//if (state.Materials.TryGetValue(idx, out var materialState))
//{
// if (!settings.RespectManual || materialState.Source.IsManual())
// Editor.ChangeMaterialValue(state, idx, new MaterialValueState(materialState.Game, value.Value, materialState.DrawData));
//}
var idx = MaterialValueIndex.FromKey(key);
var source = settings.Source.SetPending();
if (state.Materials.TryGetValue(idx, out var materialState))
{
if (settings.RespectManual && !materialState.Source.IsManual())
continue;
if (value.Revert)
Editor.ChangeMaterialValue(state, idx, default, StateSource.Game, out _, settings.Key);
else
Editor.ChangeMaterialValue(state, idx,
new MaterialValueState(materialState.Game, value.Value, materialState.DrawData, source), settings.Source, out _,
settings.Key);
}
else if (!value.Revert)
{
Editor.ChangeMaterialValue(state, idx, new MaterialValueState(ColorRow.Empty, value.Value, CharacterWeapon.Empty, source),
settings.Source, out _, settings.Key);
}
}
}
@ -321,7 +334,8 @@ public class StateEditor(
public void ApplyDesign(object data, DesignBase design, ApplySettings settings)
{
var merged = settings.MergeLinks && design is Design d
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false, Config.AlwaysApplyAssociatedMods)
? merger.Merge(d.AllLinks, ((ActorState)data).ModelData.Customize, ((ActorState)data).BaseData, false,
Config.AlwaysApplyAssociatedMods)
: new MergedDesign(design);
ApplyDesign(data, merged, settings with

View file

@ -4,6 +4,7 @@ using Glamourer.Designs.Links;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.Interop.Material;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
@ -11,6 +12,7 @@ using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System;
namespace Glamourer.State;
@ -265,9 +267,16 @@ public sealed class StateManager(
var actors = ActorData.Invalid;
if (source is not StateSource.Game)
{
actors = Applier.ChangeParameters(state, CustomizeParameterExtensions.All, true);
foreach (var (idx, mat) in state.Materials.Values)
Applier.ChangeMaterialValue(actors, MaterialValueIndex.FromKey(idx), mat.Game, true);
}
state.Materials.Clear();
Glamourer.Log.Verbose(
$"Reset advanced customization state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
$"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
StateChanged.Invoke(StateChanged.Type.Reset, source, state, actors, null);
}

@ -1 +1 @@
Subproject commit 1a187f756f2e8823197bd43db1c3383231f5eaff
Subproject commit 75c5a7b220e7f799f85741288e3de4a20af9bcf4