Fix a bunch of shit and update.

This commit is contained in:
Ottermandias 2021-08-02 16:13:48 +02:00
parent 2d5e88745f
commit fbb41636df
9 changed files with 708 additions and 86 deletions

View file

@ -106,7 +106,7 @@ namespace Glamourer.Customization
{ {
var row = _customizeSheet.GetRow(value); var row = _customizeSheet.GetRow(value);
return row == null return row == null
? new Customization(id, (byte) index, value, 0) ? new Customization(id, (byte) (index + 1), value, 0)
: new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId); : new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId);
} }
@ -192,8 +192,6 @@ namespace Glamourer.Customization
if (set.Faces.Count > 0) if (set.Faces.Count > 0)
set.SetAvailable(CustomizationId.Face); set.SetAvailable(CustomizationId.Face);
var count = race.ToRace() == Race.Hrothgar ? set.HairStyles.Count : set.Faces.Count; var count = race.ToRace() == Race.Hrothgar ? set.HairStyles.Count : set.Faces.Count;
var featureDict = new List<IReadOnlyList<Customization>>(count); var featureDict = new List<IReadOnlyList<Customization>>(count);
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)

View file

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using ImGuiScene;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Customization namespace Glamourer.Customization
@ -37,7 +39,7 @@ namespace Glamourer.Customization
public Race Race public Race Race
=> Clan.ToRace(); => Clan.ToRace();
private int _settingAvailable = DefaultAvailable; private int _settingAvailable;
internal void SetAvailable(CustomizationId id) internal void SetAvailable(CustomizationId id)
=> _settingAvailable |= 1 << (int) id; => _settingAvailable |= 1 << (int) id;
@ -71,7 +73,52 @@ namespace Glamourer.Customization
public IReadOnlyDictionary<CustomizationId, string> OptionName { get; internal set; } = null!; public IReadOnlyDictionary<CustomizationId, string> OptionName { get; internal set; } = null!;
public Customization FacialFeature(int faceIdx, int idx) public Customization FacialFeature(int faceIdx, int idx)
=> FeaturesTattoos[faceIdx][idx]; => FeaturesTattoos[faceIdx - 1][idx];
public int DataByValue(CustomizationId id, byte value, out Customization? custom)
{
var type = id.ToType();
custom = null;
if (type == CharaMakeParams.MenuType.Percentage || type == CharaMakeParams.MenuType.ListSelector)
{
if (value < Count(id))
{
custom = new Customization(id, value, 0, value);
return value;
}
return -1;
}
int Get(IEnumerable<Customization> list, ref Customization? output)
{
var (val, idx) = list.Cast<Customization?>().Select((c, i) => (c, i)).FirstOrDefault(c => c.c!.Value.Value == value);
if (val == null)
return -1;
output = val;
return idx;
}
return id switch
{
CustomizationId.SkinColor => Get(SkinColors, ref custom),
CustomizationId.EyeColorL => Get(EyeColors, ref custom),
CustomizationId.EyeColorR => Get(EyeColors, ref custom),
CustomizationId.HairColor => Get(HairColors, ref custom),
CustomizationId.HighlightColor => Get(HighlightColors, ref custom),
CustomizationId.TattooColor => Get(TattooColors, ref custom),
CustomizationId.LipColor => Get(LipColorsDark.Concat(LipColorsLight), ref custom),
CustomizationId.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), ref custom),
CustomizationId.Face => Get(Faces, ref custom),
CustomizationId.Hairstyle => Get(HairStyles, ref custom),
CustomizationId.TailEarShape => Get(TailEarShapes, ref custom),
CustomizationId.FacePaint => Get(FacePaints, ref custom),
CustomizationId.FacialFeaturesTattoos => Get(FeaturesTattoos[0], ref custom),
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
};
}
public Customization Data(CustomizationId id, int idx) public Customization Data(CustomizationId id, int idx)
{ {

View file

@ -0,0 +1,247 @@
using System;
using System.Runtime.InteropServices;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization
{
[StructLayout(LayoutKind.Sequential)]
public unsafe struct CustomizationStruct
{
public CustomizationStruct(IntPtr ptr)
=> _ptr = (byte*) ptr;
private readonly byte* _ptr;
public byte this[CustomizationId id]
{
get => id switch
{
// Needs to handle the Highlander Race in enum.
CustomizationId.Race => (byte) (_ptr[(int) CustomizationId.Race] > 1 ? _ptr[(int) CustomizationId.Race] + 1 : 1),
// Needs to handle Gender.Unknown = 0.
CustomizationId.Gender => (byte) (_ptr[(int) id] + 1),
// Just a flag.
CustomizationId.HighlightsOnFlag => (byte) ((_ptr[(int) id] & 128) == 128 ? 1 : 0),
// Eye also includes iris flag at bit 128.
CustomizationId.EyeShape => (byte) (_ptr[(int) CustomizationId.EyeShape] & 127),
// Mouth also includes Lipstick flag at bit 128.
CustomizationId.Mouth => (byte) (_ptr[(int) CustomizationId.Mouth] & 127),
// FacePaint also includes Reverse bit at 128.
CustomizationId.FacePaint => (byte) (_ptr[(int) CustomizationId.FacePaint] & 127),
_ => _ptr[(int) id],
};
set
{
_ptr[(int) id] = id switch
{
CustomizationId.Race => (byte) (value > (byte) Race.Midlander ? value - 1 : value),
CustomizationId.Gender => (byte) (value - 1),
CustomizationId.HighlightsOnFlag => (byte) (value != 0 ? 128 : 0),
CustomizationId.EyeShape => (byte) ((_ptr[(int) CustomizationId.EyeShape] & 128) | (value & 127)),
CustomizationId.Mouth => (byte) ((_ptr[(int) CustomizationId.Mouth] & 128) | (value & 127)),
CustomizationId.FacePaint => (byte) ((_ptr[(int) CustomizationId.FacePaint] & 128) | (value & 127)),
_ => value,
};
if (Race != Race.Hrothgar)
return;
// Handle Hrothgar Face being dependent on hairstyle, 2 faces per hairstyle.
switch (id)
{
case CustomizationId.Hairstyle:
_ptr[(int) CustomizationId.Face] = (byte) ((value + 1) / 2);
break;
case CustomizationId.Face:
_ptr[(int) CustomizationId.Hairstyle] = (byte) (value * 2 - 1);
break;
}
}
}
public Race Race
{
get => (Race) this[CustomizationId.Race];
set => this[CustomizationId.Race] = (byte) value;
}
public Gender Gender
{
get => (Gender) this[CustomizationId.Gender];
set => this[CustomizationId.Gender] = (byte) value;
}
public byte BodyType
{
get => this[CustomizationId.BodyType];
set => this[CustomizationId.BodyType] = value;
}
public byte Height
{
get => _ptr[(int) CustomizationId.Height];
set => _ptr[(int) CustomizationId.Height] = value;
}
public SubRace Clan
{
get => (SubRace) this[CustomizationId.Clan];
set => this[CustomizationId.Clan] = (byte) value;
}
public byte Face
{
get => this[CustomizationId.Face];
set => this[CustomizationId.Face] = value;
}
public byte Hairstyle
{
get => this[CustomizationId.Hairstyle];
set => this[CustomizationId.Hairstyle] = value;
}
public bool HighlightsOn
{
get => this[CustomizationId.HighlightsOnFlag] == 1;
set => this[CustomizationId.HighlightsOnFlag] = (byte) (value ? 1 : 0);
}
public byte SkinColor
{
get => this[CustomizationId.SkinColor];
set => this[CustomizationId.SkinColor] = value;
}
public byte EyeColorRight
{
get => this[CustomizationId.EyeColorR];
set => this[CustomizationId.EyeColorR] = value;
}
public byte HairColor
{
get => this[CustomizationId.HairColor];
set => this[CustomizationId.HairColor] = value;
}
public byte HighlightsColor
{
get => this[CustomizationId.HighlightColor];
set => this[CustomizationId.HighlightColor] = value;
}
public bool FacialFeature(int idx)
=> (this[CustomizationId.FacialFeaturesTattoos] & (1 << idx)) != 0;
public void FacialFeature(int idx, bool set)
{
if (set)
this[CustomizationId.FacialFeaturesTattoos] |= (byte) (1 << idx);
else
this[CustomizationId.FacialFeaturesTattoos] &= (byte) ~(1 << idx);
}
public byte FacialFeatures
{
get => this[CustomizationId.FacialFeaturesTattoos];
set => this[CustomizationId.FacialFeaturesTattoos] = value;
}
public byte TattooColor
{
get => this[CustomizationId.TattooColor];
set => this[CustomizationId.TattooColor] = value;
}
public byte Eyebrow
{
get => this[CustomizationId.Eyebrows];
set => this[CustomizationId.Eyebrows] = value;
}
public byte EyeColorLeft
{
get => this[CustomizationId.EyeColorL];
set => this[CustomizationId.EyeColorL] = value;
}
public byte Eye
{
get => this[CustomizationId.EyeShape];
set => this[CustomizationId.EyeShape] = value;
}
public bool SmallIris
{
get => (_ptr[(int) CustomizationId.EyeShape] & 128) == 128;
set => _ptr[(int) CustomizationId.EyeShape] = (byte) (this[CustomizationId.EyeShape] | (value ? 128u : 0u));
}
public byte Nose
{
get => _ptr[(int) CustomizationId.Nose];
set => _ptr[(int) CustomizationId.Nose] = value;
}
public byte Jaw
{
get => _ptr[(int) CustomizationId.Jaw];
set => _ptr[(int) CustomizationId.Jaw] = value;
}
public byte Mouth
{
get => this[CustomizationId.Mouth];
set => this[CustomizationId.Mouth] = value;
}
public bool LipstickSet
{
get => (_ptr[(int) CustomizationId.Mouth] & 128) == 128;
set => _ptr[(int) CustomizationId.Mouth] = (byte) (this[CustomizationId.Mouth] | (value ? 128u : 0u));
}
public byte LipColor
{
get => _ptr[(int) CustomizationId.LipColor];
set => _ptr[(int) CustomizationId.LipColor] = value;
}
public byte MuscleMass
{
get => _ptr[(int) CustomizationId.MuscleToneOrTailEarLength];
set => _ptr[(int) CustomizationId.MuscleToneOrTailEarLength] = value;
}
public byte TailShape
{
get => _ptr[(int) CustomizationId.TailEarShape];
set => _ptr[(int) CustomizationId.TailEarShape] = value;
}
public byte BustSize
{
get => _ptr[(int) CustomizationId.BustSize];
set => _ptr[(int) CustomizationId.BustSize] = value;
}
public byte FacePaint
{
get => this[CustomizationId.FacePaint];
set => this[CustomizationId.FacePaint] = value;
}
public bool FacePaintReversed
{
get => (_ptr[(int) CustomizationId.FacePaint] & 128) == 128;
set => _ptr[(int) CustomizationId.FacePaint] = (byte) (this[CustomizationId.FacePaint] | (value ? 128u : 0u));
}
public byte FacePaintColor
{
get => _ptr[(int) CustomizationId.FacePaintColor];
set => _ptr[(int) CustomizationId.FacePaintColor] = value;
}
}
}

Binary file not shown.

View file

@ -4,8 +4,8 @@
<LangVersion>preview</LangVersion> <LangVersion>preview</LangVersion>
<RootNamespace>Glamourer</RootNamespace> <RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer</AssemblyName> <AssemblyName>Glamourer</AssemblyName>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>0.0.1.1</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>0.0.1.1</AssemblyVersion>
<Company>SoftOtter</Company> <Company>SoftOtter</Company>
<Product>Glamourer</Product> <Product>Glamourer</Product>
<Copyright>Copyright © 2020</Copyright> <Copyright>Copyright © 2020</Copyright>

View file

@ -3,7 +3,7 @@
"Name": "Glamourer", "Name": "Glamourer",
"Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.", "Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.",
"InternalName": "Glamourer", "InternalName": "Glamourer",
"AssemblyVersion": "0.0.1.0", "AssemblyVersion": "0.0.1.1",
"RepoUrl": "https://github.com/Ottermandias/Glamourer", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 3, "DalamudApiLevel": 3,

View file

@ -15,6 +15,7 @@ namespace Glamourer.Gui
private string _currentFilterLower = string.Empty; private string _currentFilterLower = string.Empty;
private bool _focus; private bool _focus;
private readonly float _size; private readonly float _size;
private readonly float _previewSize;
private readonly IReadOnlyList<T> _items; private readonly IReadOnlyList<T> _items;
private readonly IReadOnlyList<string> _itemNamesLower; private readonly IReadOnlyList<string> _itemNamesLower;
private readonly Func<T, string> _itemToName; private readonly Func<T, string> _itemToName;
@ -31,7 +32,7 @@ namespace Glamourer.Gui
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None; public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
public int ItemsAtOnce { get; set; } = 12; public int ItemsAtOnce { get; set; } = 12;
public ComboWithFilter(string label, float size, IReadOnlyList<T> items, Func<T, string> itemToName) public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList<T> items, Func<T, string> itemToName)
{ {
_label = label; _label = label;
_filterLabel = $"##_{label}_filter"; _filterLabel = $"##_{label}_filter";
@ -39,6 +40,7 @@ namespace Glamourer.Gui
_itemToName = itemToName; _itemToName = itemToName;
_items = items; _items = items;
_size = size; _size = size;
_previewSize = previewSize;
_itemNamesLower = _items.Select(i => _itemToName(i).ToLowerInvariant()).ToList(); _itemNamesLower = _items.Select(i => _itemToName(i).ToLowerInvariant()).ToList();
} }
@ -52,6 +54,7 @@ namespace Glamourer.Gui
_items = other._items; _items = other._items;
_itemNamesLower = other._itemNamesLower; _itemNamesLower = other._itemNamesLower;
_size = other._size; _size = other._size;
_previewSize = other._previewSize;
PrePreview = other.PrePreview; PrePreview = other.PrePreview;
PostPreview = other.PostPreview; PostPreview = other.PostPreview;
CreateSelectable = other.CreateSelectable; CreateSelectable = other.CreateSelectable;
@ -130,7 +133,7 @@ namespace Glamourer.Gui
public bool Draw(string currentName, out T? value) public bool Draw(string currentName, out T? value)
{ {
value = default; value = default;
ImGui.SetNextItemWidth(_size); ImGui.SetNextItemWidth(_previewSize);
PrePreview?.Invoke(); PrePreview?.Invoke();
if (!ImGui.BeginCombo(_label, currentName, Flags)) if (!ImGui.BeginCombo(_label, currentName, Flags))
{ {

View file

@ -2,9 +2,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Data.LuminaExtensions;
using Dalamud.Game.ClientState.Actors; using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Glamourer.Customization; using Glamourer.Customization;
using ImGuiNET; using ImGuiNET;
using Penumbra.Api; using Penumbra.Api;
@ -12,17 +12,17 @@ using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.PlayerWatch; using Penumbra.PlayerWatch;
using SDL2;
namespace Glamourer.Gui namespace Glamourer.Gui
{ {
internal class Interface : IDisposable internal partial class Interface : IDisposable
{ {
public const int GPoseActorId = 201; public const int GPoseActorId = 201;
private const string PluginName = "Glamourer"; private const string PluginName = "Glamourer";
private readonly string _glamourerHeader; private readonly string _glamourerHeader;
private const int ColorButtonWidth = 140; private const float ColorButtonWidth = 22.5f;
private const float ColorComboWidth = 140f;
private readonly IReadOnlyDictionary<byte, Stain> _stains; private readonly IReadOnlyDictionary<byte, Stain> _stains;
private readonly IReadOnlyDictionary<EquipSlot, List<Item>> _equip; private readonly IReadOnlyDictionary<EquipSlot, List<Item>> _equip;
@ -35,7 +35,11 @@ namespace Glamourer.Gui
private Actor? _player; private Actor? _player;
private static readonly Vector2 FeatureIconSize = new(80, 80); private static readonly Vector2 FeatureIconSizeIntern =
Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2 / ImGui.GetIO().FontGlobalScale;
public static Vector2 FeatureIconSize
=> FeatureIconSizeIntern * ImGui.GetIO().FontGlobalScale;
public Interface() public Interface()
@ -52,7 +56,7 @@ namespace Glamourer.Gui
_actors = GlamourerPlugin.PluginInterface.ClientState.Actors; _actors = GlamourerPlugin.PluginInterface.ClientState.Actors;
_playerWatcher = PlayerWatchFactory.Create(GlamourerPlugin.PluginInterface); _playerWatcher = PlayerWatchFactory.Create(GlamourerPlugin.PluginInterface);
var stainCombo = new ComboWithFilter<Stain>("##StainCombo", ColorButtonWidth, _stains.Values.ToArray(), var stainCombo = new ComboWithFilter<Stain>("##StainCombo", ColorComboWidth, ColorButtonWidth, _stains.Values.ToArray(),
s => s.Name.ToString()) s => s.Name.ToString())
{ {
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge, Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
@ -67,7 +71,7 @@ namespace Glamourer.Gui
{ {
var push = PushColor(s); var push = PushColor(s);
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}", var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
Vector2.UnitX * (ColorButtonWidth - ImGui.GetStyle().ScrollbarSize)); Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
ImGui.PopStyleColor(push); ImGui.PopStyleColor(push);
return ret; return ret;
}, },
@ -75,7 +79,7 @@ namespace Glamourer.Gui
}; };
_combos = _equip.ToDictionary(kvp => kvp.Key, _combos = _equip.ToDictionary(kvp => kvp.Key,
kvp => (new ComboWithFilter<Item>($"{kvp.Key}##Equip", 300, kvp.Value, i => i.Name) { Flags = ImGuiComboFlags.HeightLarge } kvp => (new ComboWithFilter<Item>($"{kvp.Key}##Equip", 300, 300, kvp.Value, i => i.Name) { Flags = ImGuiComboFlags.HeightLarge }
, new ComboWithFilter<Stain>($"##{kvp.Key}Stain", stainCombo)) , new ComboWithFilter<Stain>($"##{kvp.Key}Stain", stainCombo))
); );
} }
@ -115,7 +119,7 @@ namespace Glamourer.Gui
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush); stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
} }
if (stainCombo.Draw(name, out var newStain) && _player != null) if (stainCombo.Draw(string.Empty, out var newStain) && _player != null)
{ {
newStain.Write(_player.Address, slot); newStain.Write(_player.Address, slot);
return true; return true;
@ -176,101 +180,424 @@ namespace Glamourer.Gui
private SubRace _currentSubRace = SubRace.Midlander; private SubRace _currentSubRace = SubRace.Midlander;
private Gender _currentGender = Gender.Male; private Gender _currentGender = Gender.Male;
private CustomizationId _currentCustomization = CustomizationId.Hairstyle;
private static readonly string[] private static readonly string[]
SubRaceNames = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).Select(s => s.ToName()).ToArray(); SubRaceNames = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).Select(s => s.ToName()).ToArray();
private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
private void DrawStuff()
{ {
if (ImGui.BeginCombo("SubRace", _currentSubRace.ToString())) value = default;
if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize))
return false;
var ret = false;
var count = set.Count(id);
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
for (var i = 0; i < count; ++i)
{
var custom = set.Data(id, i);
if (ImGui.ColorButton($"{i}", ImGui.ColorConvertU32ToFloat4(custom.Color)))
{
value = custom;
ret = true;
ImGui.CloseCurrentPopup();
}
if (i % 8 != 7)
ImGui.SameLine();
}
ImGui.EndPopup();
return ret;
}
private static void FixUpAttributes(CustomizationStruct customization)
{
var set = GlamourerPlugin.Customization.GetList(customization.Clan, customization.Gender);
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
{
switch (id)
{
case CustomizationId.Race: break;
case CustomizationId.Clan: break;
case CustomizationId.BodyType: break;
case CustomizationId.Gender: break;
case CustomizationId.FacialFeaturesTattoos: break;
case CustomizationId.Face:
if (customization.Race != Race.Hrothgar)
goto default;
break;
default:
var count = set.Count(id);
if (customization[id] >= count)
customization[id] = set.Data(id, 0).Value;
break;
}
}
}
private static bool ChangeRace(CustomizationStruct customization, SubRace clan)
{
if (clan == customization.Clan)
return false;
var race = clan.ToRace();
customization.Race = race;
customization.Clan = clan;
customization.Gender = race switch
{
Race.Hrothgar => Gender.Male,
Race.Viera => Gender.Female,
_ => customization.Gender,
};
FixUpAttributes(customization);
return true;
}
private static bool ChangeGender(CustomizationStruct customization, Gender gender)
{
if (gender == customization.Gender)
return false;
customization.Gender = gender;
FixUpAttributes(customization);
return true;
}
private static bool DrawColorPicker(string label, string tooltip, CustomizationStruct customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
var count = set.Count(id);
var current = set.DataByValue(id, customization[id], out var custom);
if (current < 0)
{
PluginLog.Warning($"Read invalid customization value {customization[id]} for {id}.");
current = 0;
custom = set.Data(id, 0);
}
var popupName = $"Color Picker##{id}";
if (ImGui.ColorButton($"{current}##color_{id}", ImGui.ColorConvertU32ToFloat4(custom!.Value.Color)))
ImGui.OpenPopup(popupName);
ImGui.SameLine();
ImGui.SetNextItemWidth(50 + 2 * 22.5f * ImGui.GetIO().FontGlobalScale);
if (ImGui.InputInt($"##text_{id}", ref current, 1) && current != customization[id] && current >= 0 && current < count)
{
customization[id] = set.Data(id, current).Value;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Input Range: [0, {count - 1}]");
ImGui.SameLine();
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
if (!DrawColorPickerPopup(popupName, set, id, out var newCustom))
return ret;
customization[id] = newCustom.Value;
ret = true;
return ret;
}
private static bool DrawListSelector(string label, string tooltip, CustomizationStruct customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
int current = customization[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(150 * ImGui.GetIO().FontGlobalScale);
if (ImGui.BeginCombo($"##combo_{id}", $"{id} #{current + 1}"))
{
for (var i = 0; i < count; ++i)
{
if (ImGui.Selectable($"{id} #{i + 1}##combo", i == current) && i != current)
{
customization[id] = (byte) i;
ret = true;
}
}
ImGui.EndCombo();
}
ImGui.SameLine();
ImGui.SetNextItemWidth(50 + 2 * 22.5f * ImGui.GetIO().FontGlobalScale);
if (ImGui.InputInt($"##text_{id}", ref current, 1) && current != customization[id] && current >= 0 && current < count)
{
customization[id] = set.Data(id, current).Value;
ret = true;
}
ImGui.SameLine();
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private static readonly Vector4 NoColor = new(1f, 1f, 1f, 1f);
private static readonly Vector4 RedColor = new(0.6f, 0.3f, 0.3f, 1f);
private static bool DrawMultiSelector(CustomizationStruct customization, CustomizationSet set)
{
var ret = false;
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
for (var i = 0; i < count; ++i)
{
var enabled = customization.FacialFeature(i);
var feature = set.FacialFeature(set.Race == Race.Hrothgar ? customization.Hairstyle : customization.Face, i);
var icon = GlamourerPlugin.Customization.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, FeatureIconSize, Vector2.Zero, Vector2.One, (int) ImGui.GetStyle().FramePadding.X,
Vector4.Zero,
enabled ? NoColor : RedColor))
{
ret = true;
customization.FacialFeature(i, !enabled);
}
if (ImGui.IsItemHovered())
{
using var tt = ImGuiRaii.NewTooltip();
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
}
ImGui.SameLine();
}
raii.PopStyles();
raii.Group();
int value = customization[CustomizationId.FacialFeaturesTattoos];
ImGui.SetNextItemWidth(50 + 2 * 22.5f * ImGui.GetIO().FontGlobalScale);
if (ImGui.InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1)
&& value != customization[CustomizationId.FacialFeaturesTattoos]
&& value > 0
&& value < 256)
{
customization[CustomizationId.FacialFeaturesTattoos] = (byte) value;
ret = true;
}
ImGui.Text("Facial Features & Tattoos");
return ret;
}
private static bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
{
value = default;
if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize))
return false;
var ret = false;
var count = set.Count(id);
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
for (var i = 0; i < count; ++i)
{
var custom = set.Data(id, i);
var icon = GlamourerPlugin.Customization.GetIcon(custom.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, FeatureIconSize))
{
value = custom;
ret = true;
ImGui.CloseCurrentPopup();
}
if (ImGui.IsItemHovered())
{
using var tt = ImGuiRaii.NewTooltip();
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
}
if (i % 8 != 7)
ImGui.SameLine();
}
ImGui.EndPopup();
return ret;
}
private static bool DrawIconSelector(string label, string tooltip, CustomizationStruct customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
var count = set.Count(id);
var current = set.DataByValue(id, customization[id], out var custom);
if (current < 0)
{
PluginLog.Warning($"Read invalid customization value {customization[id]} for {id}.");
current = 0;
custom = set.Data(id, 0);
}
var popupName = $"Style Picker##{id}";
var icon = GlamourerPlugin.Customization.GetIcon(custom!.Value.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, FeatureIconSize))
ImGui.OpenPopup(popupName);
if (ImGui.IsItemHovered())
{
using var tt = ImGuiRaii.NewTooltip();
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
}
ImGui.SameLine();
using var group = ImGuiRaii.NewGroup();
ImGui.SetNextItemWidth(50 + 2 * 22.5f * ImGui.GetIO().FontGlobalScale);
var oldIdx = current;
if (ImGui.InputInt($"##text_{id}", ref current, 1) && current != oldIdx && current >= 0 && current < count)
{
customization[id] = set.Data(id, current).Value;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Input Range: [0, {count - 1}]");
if (DrawIconPickerPopup(popupName, set, id, out var newCustom))
{
customization[id] = newCustom.Value;
ret = true;
}
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private static bool DrawPercentageSelector(string label, string tooltip, CustomizationStruct customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
int value = customization[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(150 * ImGui.GetIO().FontGlobalScale);
if (ImGui.SliderInt($"##slider_{id}", ref value, 0, count - 1, "") && value != customization[id])
{
customization[id] = (byte) value;
ret = true;
}
ImGui.SameLine();
ImGui.SetNextItemWidth(50 + 2 * 22.5f * ImGui.GetIO().FontGlobalScale);
if (ImGui.InputInt($"##input_{id}", ref value, 1) && value != customization[id] && value >= 0 && value < count)
{
customization[id] = (byte) value;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Input Range: [0, {count - 1}]");
ImGui.SameLine();
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private bool DrawStuff()
{
var ret = false;
var x = new CustomizationStruct(_player!.Address + 0x1898);
_currentSubRace = x.Clan;
ImGui.SetNextItemWidth(150 * ImGui.GetIO().FontGlobalScale);
if (ImGui.BeginCombo("SubRace", SubRaceNames[(int) _currentSubRace - 1]))
{ {
for (var i = 0; i < SubRaceNames.Length; ++i) for (var i = 0; i < SubRaceNames.Length; ++i)
{ {
if (ImGui.Selectable(SubRaceNames[i], (int) _currentSubRace == i + 1)) if (ImGui.Selectable(SubRaceNames[i], (int) _currentSubRace == i + 1))
_currentSubRace = (SubRace) (i + 1); {
_currentSubRace = (SubRace) i + 1;
ret |= ChangeRace(x, _currentSubRace);
}
} }
ImGui.EndCombo(); ImGui.EndCombo();
} }
_currentGender = x.Gender;
ImGui.SetNextItemWidth(150 * ImGui.GetIO().FontGlobalScale);
if (ImGui.BeginCombo("Gender", _currentGender.ToName())) if (ImGui.BeginCombo("Gender", _currentGender.ToName()))
{ {
if (ImGui.Selectable(Gender.Male.ToName(), _currentGender == Gender.Male)) if (_currentSubRace.ToRace() != Race.Viera
&& ImGui.Selectable(Gender.Male.ToName(), _currentGender == Gender.Male)
&& _currentGender != Gender.Male)
{
_currentGender = Gender.Male; _currentGender = Gender.Male;
if (ImGui.Selectable(Gender.Female.ToName(), _currentGender == Gender.Female)) ret = ChangeGender(x, _currentGender);
}
if (_currentSubRace.ToRace() != Race.Hrothgar
&& ImGui.Selectable(Gender.Female.ToName(), _currentGender == Gender.Female)
&& _currentGender != Gender.Female)
{
_currentGender = Gender.Female; _currentGender = Gender.Female;
ret = ChangeGender(x, _currentGender);
}
ImGui.EndCombo(); ImGui.EndCombo();
} }
var set = GlamourerPlugin.Customization.GetList(_currentSubRace, _currentGender); var set = GlamourerPlugin.Customization.GetList(_currentSubRace, _currentGender);
if (ImGui.BeginCombo("Customization", _currentCustomization.ToString()))
{
foreach (CustomizationId customizationId in Enum.GetValues(typeof(CustomizationId))) foreach (CustomizationId customizationId in Enum.GetValues(typeof(CustomizationId)))
{ {
if (!set.IsAvailable(customizationId)) if (!set.IsAvailable(customizationId))
continue; continue;
if (ImGui.Selectable(customizationId.ToString(), customizationId == _currentCustomization)) switch (customizationId.ToType(_currentSubRace.ToRace() == Race.Hrothgar))
_currentCustomization = customizationId;
}
ImGui.EndCombo();
}
var count = set.Count(_currentCustomization);
var tmp = 0;
switch (_currentCustomization.ToType(_currentSubRace.ToRace() == Race.Hrothgar))
{ {
case CharaMakeParams.MenuType.ColorPicker: case CharaMakeParams.MenuType.ColorPicker:
{ ret |= DrawColorPicker(customizationId.ToString(), "", x,
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) customizationId, set);
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
for (var i = 0; i < count; ++i)
{
var data = set.Data(_currentCustomization, i);
ImGui.ColorButton($"{data.Value}", ImGui.ColorConvertU32ToFloat4(data.Color));
if (i % 8 != 7)
ImGui.SameLine();
}
}
break;
case CharaMakeParams.MenuType.Percentage:
ImGui.SliderInt("Percentage", ref tmp, 0, 100);
break; break;
case CharaMakeParams.MenuType.ListSelector: case CharaMakeParams.MenuType.ListSelector:
ImGui.Combo("List", ref tmp, Enumerable.Range(0, count).Select(i => $"{_currentCustomization} #{i}").ToArray(), count); ret |= DrawListSelector(customizationId.ToString(), "", x,
customizationId, set);
break; break;
case CharaMakeParams.MenuType.IconSelector: case CharaMakeParams.MenuType.IconSelector:
ret |= DrawIconSelector(customizationId.ToString(), "", x, customizationId, set);
break;
case CharaMakeParams.MenuType.MultiIconSelector: case CharaMakeParams.MenuType.MultiIconSelector:
{ ret |= DrawMultiSelector(x, set);
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero) break;
.PushStyle(ImGuiStyleVar.FrameRounding, 0f); case CharaMakeParams.MenuType.Percentage:
for (var i = 0; i < count; ++i) ret |= DrawPercentageSelector(customizationId.ToString(), "", x, customizationId, set);
{
var data = set.Data(_currentCustomization, i);
var texture = GlamourerPlugin.Customization.GetIcon(data.IconId);
ImGui.ImageButton(texture.ImGuiHandle, FeatureIconSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.IsItemHovered())
{
using var tooltip = ImGuiRaii.NewTooltip();
ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height));
}
if (i % 4 != 3)
ImGui.SameLine();
}
}
break; break;
} }
} }
return ret;
}
private void Draw() private void Draw()
{ {
ImGui.SetNextWindowSizeConstraints(Vector2.One * 600, Vector2.One * 5000); ImGui.SetNextWindowSizeConstraints(Vector2.One * 600, Vector2.One * 5000);
if (!_visible || !ImGui.Begin(_glamourerHeader)) if (!_visible || !ImGui.Begin(_glamourerHeader, ref _visible))
return; return;
try try
@ -311,11 +638,11 @@ namespace Glamourer.Gui
changes |= DrawEquip(EquipSlot.RFinger, equip.RFinger); changes |= DrawEquip(EquipSlot.RFinger, equip.RFinger);
changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger); changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger);
changes |= DrawStuff();
if (changes) if (changes)
UpdateActors(_player); UpdateActors(_player);
} }
DrawStuff();
} }
finally finally
{ {

View file

@ -4,8 +4,8 @@
"Name": "Glamourer", "Name": "Glamourer",
"Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.", "Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.",
"InternalName": "Glamourer", "InternalName": "Glamourer",
"AssemblyVersion": "0.0.1.0", "AssemblyVersion": "0.0.1.1",
"TestingAssemblyVersion": "0.0.1.0", "TestingAssemblyVersion": "0.0.1.1",
"RepoUrl": "https://github.com/Ottermandias/Glamourer", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 3, "DalamudApiLevel": 3,