diff --git a/Glamourer/Gui/GlamourerChangelog.cs b/Glamourer/Gui/GlamourerChangelog.cs index 26723de..7c1d188 100644 --- a/Glamourer/Gui/GlamourerChangelog.cs +++ b/Glamourer/Gui/GlamourerChangelog.cs @@ -57,7 +57,7 @@ public class GlamourerChangelog .RegisterEntry( "This was a considerable backend change on both automation sets and design application. I may have messed up and introduced bugs. Please let me know if something does not work right anymore.", 1) - .RegisterHighlight("Added advanced dye options for equipment. You can now runtime-edit the color sets of your gear.") + .RegisterHighlight("Added advanced dye options for equipment. You can now live-edit the color sets of your gear.") .RegisterEntry( "The logic for this is very complicated and may interfere with other options or not update correctly, it will need a lot of testing.", 1) diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index eadf321..43036e8 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -1,7 +1,7 @@ -using System.Reflection.Metadata.Ecma335; -using Dalamud.Interface; +using Dalamud.Interface; using Dalamud.Interface.Utility; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; using FFXIVClientStructs.Interop; using Glamourer.Designs; using Glamourer.Interop.Material; @@ -13,6 +13,7 @@ using OtterGui.Raii; using OtterGui.Services; using Penumbra.GameData.Enums; using Penumbra.GameData.Files; +using Penumbra.String; namespace Glamourer.Gui.Materials; @@ -68,6 +69,20 @@ public sealed unsafe class AdvancedDyePopup( ImGuiUtil.HoverTooltip("Open advanced dyes for this slot."); } + private (string Path, string GamePath) ResourceName(MaterialValueIndex index) + { + var materialHandle = (MaterialResourceHandle*)_actor.Model.AsCharacterBase->MaterialsSpan[ + index.MaterialIndex + index.SlotIndex * MaterialService.MaterialsPerModel].Value; + var model = _actor.Model.AsCharacterBase->ModelsSpan[index.SlotIndex].Value; + var modelHandle = model == null ? null : model->ModelResourceHandle; + var path = materialHandle == null + ? string.Empty + : ByteString.FromSpanUnsafe(materialHandle->ResourceHandle.FileName.AsSpan(), true).ToString(); + var gamePath = modelHandle == null + ? string.Empty + : modelHandle->GetMaterialFileNameBySlotAsString(index.MaterialIndex); + return (path, gamePath); + } private void DrawTabBar(ReadOnlySpan> textures, ref bool firstAvailable) { @@ -79,6 +94,7 @@ public sealed unsafe class AdvancedDyePopup( { var index = _drawIndex!.Value with { MaterialIndex = i }; var available = index.TryGetTexture(textures, out var texture) && index.TryGetColorTable(texture, out var table); + if (index == preview.LastValueIndex with { RowIndex = 0 }) table = preview.LastOriginalColorTable; @@ -91,11 +107,16 @@ public sealed unsafe class AdvancedDyePopup( firstAvailable = false; using var tab = _label.TabItem(i, select); - if (!available) + if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) { - using var disabled = ImRaii.Enabled(); - ImGuiUtil.HoverTooltip("This material does not exist or does not have an associated color set.", - ImGuiHoveredFlags.AllowWhenDisabled); + using var enabled = ImRaii.Enabled(); + var (path, gamePath) = ResourceName(index); + if (gamePath.Length == 0 || path.Length == 0) + ImGui.SetTooltip("This material does not exist."); + else if (!available) + ImGui.SetTooltip($"This material does not have an associated color set.\n\n{gamePath}\n{path}"); + else + ImGui.SetTooltip($"{gamePath}\n{path}"); } if ((tab.Success || select is ImGuiTabItemFlags.SetSelected) && available) @@ -186,7 +207,7 @@ public sealed unsafe class AdvancedDyePopup( if (!changed) { var internalRow = new ColorRow(row); - var slot = index.ToSlot(); + var slot = index.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand ? _state.ModelData.Weapon(slot) : _state.ModelData.Armor(slot).ToWeapon(0); diff --git a/Glamourer/Interop/Material/MaterialValueIndex.cs b/Glamourer/Interop/Material/MaterialValueIndex.cs index a53e162..1229cd7 100644 --- a/Glamourer/Interop/Material/MaterialValueIndex.cs +++ b/Glamourer/Interop/Material/MaterialValueIndex.cs @@ -20,7 +20,7 @@ public readonly record struct MaterialValueIndex( => ToKey(DrawObject, SlotIndex, MaterialIndex, RowIndex); public bool Valid - => Validate(DrawObject) && ValidateSlot(SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex); + => Validate(DrawObject) && ValidateSlot(DrawObject, SlotIndex) && ValidateMaterial(MaterialIndex) && ValidateRow(RowIndex); public static bool FromKey(uint key, out MaterialValueIndex index) { @@ -42,7 +42,7 @@ public readonly record struct MaterialValueIndex( return Invalid; } - public EquipSlot ToSlot() + public EquipSlot ToEquipSlot() => DrawObject switch { DrawObjectType.Human when SlotIndex < 10 => ((uint)SlotIndex).ToEquipSlot(), @@ -155,8 +155,14 @@ public readonly record struct MaterialValueIndex( public static bool Validate(DrawObjectType type) => type is not DrawObjectType.Invalid && Enum.IsDefined(type); - public static bool ValidateSlot(byte slotIndex) - => slotIndex < 10; + public static bool ValidateSlot(DrawObjectType type, byte slotIndex) + => type switch + { + DrawObjectType.Human => slotIndex < 14, + DrawObjectType.Mainhand => slotIndex == 0, + DrawObjectType.Offhand => slotIndex == 0, + _ => false, + }; public static bool ValidateMaterial(byte materialIndex) => materialIndex < MaterialService.MaterialsPerModel; @@ -178,12 +184,19 @@ public readonly record struct MaterialValueIndex( { } public override string ToString() - { - var slot = ToSlot(); - return slot is EquipSlot.Unknown - ? $"{DrawObject} Slot {SlotIndex} Material #{MaterialIndex + 1} Row #{RowIndex + 1}" - : $"{slot.ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}"; - } + => DrawObject switch + { + DrawObjectType.Invalid => "Invalid", + DrawObjectType.Human when SlotIndex < 10 => + $"{((uint)SlotIndex).ToEquipSlot().ToName()} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 10 => $"BodySlot.Hair.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 11 => $"BodySlot.Face.ToString() Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 12 => $"{BodySlot.Tail} / {BodySlot.Ear} Material #{MaterialIndex + 1} Row #{RowIndex + 1}", + DrawObjectType.Human when SlotIndex == 13 => $"Connectors 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 {