So much stuff...

This commit is contained in:
Ottermandias 2021-08-21 00:52:46 +02:00
parent f71b800b2e
commit 80ad0d774b
31 changed files with 2949 additions and 1226 deletions

View file

@ -2,397 +2,55 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Windows.Forms;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Interface;
using Dalamud.Plugin;
using Glamourer.Customization;
using Glamourer.Designs;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Penumbra.Api;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.PlayerWatch;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Gui
{
internal partial class Interface
{
// Push the stain color to type and if it is too bright, turn the text color black.
// Return number of pushed styles.
private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
{
ImGui.PushStyleColor(type, stain.RgbaColor);
if (stain.Intensity > 127)
{
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
return 2;
}
return 1;
}
// Update actors without triggering PlayerWatcher Events,
// then manually redraw using Penumbra.
public void UpdateActors(Actor actor)
{
var newEquip = _playerWatcher.UpdateActorWithoutEvent(actor);
GlamourerPlugin.Penumbra?.RedrawActor(actor, RedrawType.WithSettings);
// Special case for carrying over changes to the gPose actor to the regular player actor, too.
var gPose = _actors[GPoseActorId];
var player = _actors[0];
if (gPose != null && actor.Address == gPose.Address && player != null)
newEquip.Write(player.Address);
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static void FixUpAttributes(LazyCustomization customization)
{
var set = GlamourerPlugin.Customization.GetList(customization.Value.Clan, customization.Value.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.HighlightsOnFlag: break;
case CustomizationId.Face:
if (customization.Value.Race != Race.Hrothgar)
goto default;
break;
default:
var count = set.Count(id);
if (customization.Value[id] >= count)
if (count == 0)
customization.Value[id] = 0;
else
customization.Value[id] = set.Data(id, 0).Value;
break;
}
}
}
// Change a race and fix up all required customizations afterwards.
private static bool ChangeRace(LazyCustomization customization, SubRace clan)
{
if (clan == customization.Value.Clan)
return false;
var race = clan.ToRace();
customization.Value.Race = race;
customization.Value.Clan = clan;
customization.Value.Gender = race switch
{
Race.Hrothgar => Gender.Male,
Race.Viera => Gender.Female,
_ => customization.Value.Gender,
};
FixUpAttributes(customization);
return true;
}
// Change a gender and fix up all required customizations afterwards.
private static bool ChangeGender(LazyCustomization customization, Gender gender)
{
if (gender == customization.Value.Gender)
return false;
customization.Value.Gender = gender;
FixUpAttributes(customization);
return true;
}
}
internal partial class Interface
{
private const float ColorButtonWidth = 22.5f;
private const float ColorComboWidth = 140f;
private const float ItemComboWidth = 300f;
private ComboWithFilter<Stain> CreateDefaultStainCombo(IReadOnlyList<Stain> stains)
=> new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains,
s => s.Name.ToString())
{
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
PreList = () =>
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
},
PostList = () => { ImGui.PopStyleVar(3); },
CreateSelectable = s =>
{
var push = PushColor(s);
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
ImGui.PopStyleColor(push);
return ret;
},
ItemsAtOnce = 12,
};
private ComboWithFilter<Item> CreateItemCombo(EquipSlot slot, IReadOnlyList<Item> items)
=> new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name)
{
Flags = ImGuiComboFlags.HeightLarge,
};
private (ComboWithFilter<Item>, ComboWithFilter<Stain>) CreateCombos(EquipSlot slot, IReadOnlyList<Item> items,
ComboWithFilter<Stain> defaultStain)
=> (CreateItemCombo(slot, items), new ComboWithFilter<Stain>($"##{slot}Stain", defaultStain));
}
internal partial class Interface
{
private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
{
stainCombo.PostPreview = null;
if (_stains.TryGetValue((byte) stainIdx, out var stain))
{
var previewPush = PushColor(stain, ImGuiCol.FrameBg);
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
}
if (stainCombo.Draw(string.Empty, out var newStain) && _player != null && !newStain.RowIndex.Equals(stainIdx))
{
newStain.Write(_player.Address, slot);
return true;
}
return false;
}
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item? item)
{
var currentName = item?.Name.ToString() ?? "Nothing";
if (equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && _player != null && newItem.Base.RowId != item?.RowId)
{
newItem.Write(_player.Address);
return true;
}
return false;
}
private bool DrawEquip(EquipSlot slot, ActorArmor equip)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
ImGui.SameLine();
var item = _identifier.Identify(equip.Set, new WeaponType(), equip.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
private bool DrawWeapon(EquipSlot slot, ActorWeapon weapon)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
ImGui.SameLine();
var item = _identifier.Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
}
internal partial class Interface
{
private static bool DrawColorPickerPopup(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);
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
{
value = custom;
ret = true;
ImGui.CloseCurrentPopup();
}
if (i % 8 != 7)
ImGui.SameLine();
}
ImGui.EndPopup();
return ret;
}
private Vector2 _iconSize = Vector2.Zero;
private Vector2 _actualIconSize = Vector2.Zero;
private float _raceSelectorWidth = 0;
private float _inputIntSize = 0;
private float _comboSelectorSize = 0;
private float _percentageSize = 0;
private float _itemComboWidth = 0;
private bool InputInt(string label, ref int value, int minValue, int maxValue)
{
var ret = false;
var tmp = value + 1;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt(label, ref tmp, 1) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue)
{
value = tmp - 1;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Input Range: [{minValue}, {maxValue}]");
return ret;
}
private static (int, Customization.Customization) GetCurrentCustomization(LazyCustomization customization, CustomizationId id,
CustomizationSet set)
{
var current = set.DataByValue(id, customization.Value[id], out var custom);
if (current < 0)
{
PluginLog.Warning($"Read invalid customization value {customization.Value[id]} for {id}.");
current = 0;
custom = set.Data(id, 0);
}
return (current, custom!.Value);
}
private bool DrawColorPicker(string label, string tooltip, LazyCustomization customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
var count = set.Count(id);
var (current, custom) = GetCurrentCustomization(customization, id, set);
var popupName = $"Color Picker##{id}";
if (ImGui.ColorButton($"{current + 1}##color_{id}", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None,
_actualIconSize))
ImGui.OpenPopup(popupName);
ImGui.SameLine();
using (var group = ImGuiRaii.NewGroup())
{
if (InputInt($"##text_{id}", ref current, 1, count))
{
customization.Value[id] = set.Data(id, current - 1).Value;
ret = true;
}
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
}
if (!DrawColorPickerPopup(popupName, set, id, out var newCustom))
return ret;
customization.Value[id] = newCustom.Value;
ret = true;
return ret;
}
}
internal partial class Interface : IDisposable
{
public const int GPoseActorId = 201;
private const string PluginName = "Glamourer";
public const float SelectorWidth = 200;
public const float MinWindowWidth = 675;
public const int GPoseActorId = 201;
private const string PluginName = "Glamourer";
private readonly string _glamourerHeader;
private readonly IReadOnlyDictionary<byte, Stain> _stains;
private readonly IReadOnlyDictionary<EquipSlot, List<Item>> _equip;
private readonly ActorTable _actors;
private readonly IObjectIdentifier _identifier;
private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
private readonly IPlayerWatcher _playerWatcher;
private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
private readonly Dictionary<EquipSlot, string> _equipSlotNames;
private readonly DesignManager _designs;
private readonly GlamourerPlugin _plugin;
private bool _visible = false;
private bool _inGPose = false;
private Actor? _player;
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource != null)
{
var rawImage = new byte[resource.Length];
resource.Read(rawImage, 0, (int) resource.Length);
return GlamourerPlugin.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
}
return null;
}
private static Dictionary<EquipSlot, string> GetEquipSlotNames()
{
var sheet = GlamourerPlugin.PluginInterface.Data.GetExcelSheet<Addon>();
var ret = new Dictionary<EquipSlot, string>(12)
{
[EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
[EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
[EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head",
[EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body",
[EquipSlot.Hands] = sheet.GetRow(750)?.Text.ToString() ?? "Hands",
[EquipSlot.Legs] = sheet.GetRow(742)?.Text.ToString() ?? "Legs",
[EquipSlot.Feet] = sheet.GetRow(744)?.Text.ToString() ?? "Feet",
[EquipSlot.Ears] = sheet.GetRow(745)?.Text.ToString() ?? "Ears",
[EquipSlot.Neck] = sheet.GetRow(746)?.Text.ToString() ?? "Neck",
[EquipSlot.Wrists] = sheet.GetRow(747)?.Text.ToString() ?? "Wrists",
[EquipSlot.RFinger] = sheet.GetRow(748)?.Text.ToString() ?? "Right Ring",
[EquipSlot.LFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Left Ring",
};
return ret;
}
public Interface()
public Interface(GlamourerPlugin plugin)
{
_plugin = plugin;
_designs = plugin.Designs;
_glamourerHeader = GlamourerPlugin.Version.Length > 0
? $"{PluginName} v{GlamourerPlugin.Version}###{PluginName}Main"
: $"{PluginName}###{PluginName}Main";
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi += Draw;
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi += ToggleVisibility;
GlamourerPlugin.PluginInterface.UiBuilder.DisableGposeUiHide = true;
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi += Draw;
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi += ToggleVisibility;
_equipSlotNames = GetEquipSlotNames();
_stains = GameData.Stains(GlamourerPlugin.PluginInterface);
_equip = GameData.ItemsBySlot(GlamourerPlugin.PluginInterface);
_identifier = Penumbra.GameData.GameData.GetIdentifier(GlamourerPlugin.PluginInterface);
_actors = GlamourerPlugin.PluginInterface.ClientState.Actors;
_playerWatcher = PlayerWatchFactory.Create(GlamourerPlugin.PluginInterface);
_stains = GameData.Stains(GlamourerPlugin.PluginInterface);
_identifier = Penumbra.GameData.GameData.GetIdentifier(GlamourerPlugin.PluginInterface);
_actors = GlamourerPlugin.PluginInterface.ClientState.Actors;
var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
_combos = _equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
var equip = GameData.ItemsBySlot(GlamourerPlugin.PluginInterface);
_combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
_legacyTattooIcon = GetLegacyTattooIcon();
}
@ -402,524 +60,37 @@ namespace Glamourer.Gui
public void Dispose()
{
_legacyTattooIcon?.Dispose();
_playerWatcher?.Dispose();
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi -= Draw;
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi -= ToggleVisibility;
}
private string _currentActorName = "";
private SubRace _currentSubRace = SubRace.Midlander;
private Gender _currentGender = Gender.Male;
private bool DrawListSelector(string label, string tooltip, LazyCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
int current = customization.Value[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.BeginCombo($"##combo_{id}", $"{set.Option(id)} #{current + 1}"))
{
for (var i = 0; i < count; ++i)
{
if (ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) && i != current)
{
customization.Value[id] = (byte) i;
ret = true;
}
}
ImGui.EndCombo();
}
ImGui.SameLine();
if (InputInt($"##text_{id}", ref current, 1, count))
{
customization.Value[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 bool DrawMultiSelector(LazyCustomization customization, CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
using (var raii = ImGuiRaii.NewGroup())
{
for (var i = 0; i < count; ++i)
{
var enabled = customization.Value.FacialFeature(i);
var feature = set.FacialFeature(set.Race == Race.Hrothgar ? customization.Value.Hairstyle : customization.Value.Face, i);
var icon = i == count - 1
? _legacyTattooIcon ?? GlamourerPlugin.Customization.GetIcon(feature.IconId)
: GlamourerPlugin.Customization.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int) ImGui.GetStyle().FramePadding.X,
Vector4.Zero,
enabled ? NoColor : RedColor))
{
ret = true;
customization.Value.FacialFeature(i, !enabled);
}
if (ImGui.IsItemHovered())
{
using var tt = ImGuiRaii.NewTooltip();
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
}
if (i % 4 != 3)
ImGui.SameLine();
}
}
ImGui.SameLine();
using var group = ImGuiRaii.NewGroup();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
int value = customization.Value[CustomizationId.FacialFeaturesTattoos];
if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256))
{
customization.Value[CustomizationId.FacialFeaturesTattoos] = (byte) value;
ret = true;
}
ImGui.Text(set.Option(CustomizationId.FacialFeaturesTattoos));
return ret;
}
private 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, _iconSize))
{
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 bool DrawIconSelector(string label, string tooltip, LazyCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
var count = set.Count(id);
var current = set.DataByValue(id, customization.Value[id], out var custom);
if (current < 0)
{
PluginLog.Warning($"Read invalid customization value {customization.Value[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, _iconSize))
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();
if (InputInt($"##text_{id}", ref current, 1, count))
{
customization.Value[id] = set.Data(id, current).Value;
ret = true;
}
if (DrawIconPickerPopup(popupName, set, id, out var newCustom))
{
customization.Value[id] = newCustom.Value;
ret = true;
}
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private bool DrawPercentageSelector(string label, string tooltip, LazyCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
int value = customization.Value[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(_percentageSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.SliderInt($"##slider_{id}", ref value, 0, count - 1, "") && value != customization.Value[id])
{
customization.Value[id] = (byte) value;
ret = true;
}
ImGui.SameLine();
--value;
if (InputInt($"##input_{id}", ref value, 0, count - 1))
{
customization.Value[id] = (byte) (value + 1);
ret = true;
}
ImGui.SameLine();
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private string ClanName(SubRace race, Gender gender)
{
if (gender == Gender.Female)
return race switch
{
SubRace.Midlander => GlamourerPlugin.Customization.GetName(CustomName.MidlanderM),
SubRace.Highlander => GlamourerPlugin.Customization.GetName(CustomName.HighlanderM),
SubRace.Wildwood => GlamourerPlugin.Customization.GetName(CustomName.WildwoodM),
SubRace.Duskwight => GlamourerPlugin.Customization.GetName(CustomName.DuskwightM),
SubRace.Plainsfolk => GlamourerPlugin.Customization.GetName(CustomName.PlainsfolkM),
SubRace.Dunesfolk => GlamourerPlugin.Customization.GetName(CustomName.DunesfolkM),
SubRace.SeekerOfTheSun => GlamourerPlugin.Customization.GetName(CustomName.SeekerOfTheSunM),
SubRace.KeeperOfTheMoon => GlamourerPlugin.Customization.GetName(CustomName.KeeperOfTheMoonM),
SubRace.Seawolf => GlamourerPlugin.Customization.GetName(CustomName.SeawolfM),
SubRace.Hellsguard => GlamourerPlugin.Customization.GetName(CustomName.HellsguardM),
SubRace.Raen => GlamourerPlugin.Customization.GetName(CustomName.RaenM),
SubRace.Xaela => GlamourerPlugin.Customization.GetName(CustomName.XaelaM),
SubRace.Helion => GlamourerPlugin.Customization.GetName(CustomName.HelionM),
SubRace.Lost => GlamourerPlugin.Customization.GetName(CustomName.LostM),
SubRace.Rava => GlamourerPlugin.Customization.GetName(CustomName.RavaF),
SubRace.Veena => GlamourerPlugin.Customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
return race switch
{
SubRace.Midlander => GlamourerPlugin.Customization.GetName(CustomName.MidlanderF),
SubRace.Highlander => GlamourerPlugin.Customization.GetName(CustomName.HighlanderF),
SubRace.Wildwood => GlamourerPlugin.Customization.GetName(CustomName.WildwoodF),
SubRace.Duskwight => GlamourerPlugin.Customization.GetName(CustomName.DuskwightF),
SubRace.Plainsfolk => GlamourerPlugin.Customization.GetName(CustomName.PlainsfolkF),
SubRace.Dunesfolk => GlamourerPlugin.Customization.GetName(CustomName.DunesfolkF),
SubRace.SeekerOfTheSun => GlamourerPlugin.Customization.GetName(CustomName.SeekerOfTheSunF),
SubRace.KeeperOfTheMoon => GlamourerPlugin.Customization.GetName(CustomName.KeeperOfTheMoonF),
SubRace.Seawolf => GlamourerPlugin.Customization.GetName(CustomName.SeawolfF),
SubRace.Hellsguard => GlamourerPlugin.Customization.GetName(CustomName.HellsguardF),
SubRace.Raen => GlamourerPlugin.Customization.GetName(CustomName.RaenF),
SubRace.Xaela => GlamourerPlugin.Customization.GetName(CustomName.XaelaF),
SubRace.Helion => GlamourerPlugin.Customization.GetName(CustomName.HelionM),
SubRace.Lost => GlamourerPlugin.Customization.GetName(CustomName.LostM),
SubRace.Rava => GlamourerPlugin.Customization.GetName(CustomName.RavaF),
SubRace.Veena => GlamourerPlugin.Customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
}
private bool DrawRaceSelector(LazyCustomization customization)
{
using var group = ImGuiRaii.NewGroup();
var ret = false;
_currentSubRace = customization.Value.Clan;
ImGui.SetNextItemWidth(_raceSelectorWidth);
if (ImGui.BeginCombo("##subRaceCombo", ClanName(_currentSubRace, customization.Value.Gender)))
{
for (var i = 0; i < (int) SubRace.Veena; ++i)
{
if (ImGui.Selectable(ClanName((SubRace) i + 1, customization.Value.Gender), (int) _currentSubRace == i + 1))
{
_currentSubRace = (SubRace) i + 1;
ret |= ChangeRace(customization, _currentSubRace);
}
}
ImGui.EndCombo();
}
ImGui.Text(
$"{GlamourerPlugin.Customization.GetName(CustomName.Gender)} & {GlamourerPlugin.Customization.GetName(CustomName.Clan)}");
return ret;
}
private bool DrawGenderSelector(LazyCustomization customization)
{
var ret = false;
ImGui.PushFont(UiBuilder.IconFont);
var icon = _currentGender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
var restricted = false;
if (customization.Value.Race == Race.Viera)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f);
icon = FontAwesomeIcon.VenusDouble;
restricted = true;
}
else if (customization.Value.Race == Race.Hrothgar)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f);
icon = FontAwesomeIcon.MarsDouble;
restricted = true;
}
if (ImGui.Button(icon.ToIconString(), _actualIconSize) && !restricted)
{
_currentGender = _currentGender == Gender.Male ? Gender.Female : Gender.Male;
ret = ChangeGender(customization, _currentGender);
}
if (restricted)
ImGui.PopStyleVar();
ImGui.PopFont();
return ret;
}
private bool DrawPicker(CustomizationSet set, CustomizationId id, LazyCustomization customization)
{
if (!set.IsAvailable(id))
return false;
switch (set.Type(id))
{
case CharaMakeParams.MenuType.ColorPicker: return DrawColorPicker(set.OptionName[(int) id], "", customization, id, set);
case CharaMakeParams.MenuType.ListSelector: return DrawListSelector(set.OptionName[(int) id], "", customization, id, set);
case CharaMakeParams.MenuType.IconSelector: return DrawIconSelector(set.OptionName[(int) id], "", customization, id, set);
case CharaMakeParams.MenuType.MultiIconSelector: return DrawMultiSelector(customization, set);
case CharaMakeParams.MenuType.Percentage: return DrawPercentageSelector(set.OptionName[(int) id], "", customization, id, set);
}
return false;
}
private static readonly CustomizationId[] AllCustomizations = (CustomizationId[]) Enum.GetValues(typeof(CustomizationId));
private bool DrawStuff(LazyCustomization x)
{
_currentSubRace = x.Value.Clan;
_currentGender = x.Value.Gender;
var ret = DrawGenderSelector(x);
ImGui.SameLine();
ret |= DrawRaceSelector(x);
var set = GlamourerPlugin.Customization.GetList(_currentSubRace, _currentGender);
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.Percentage))
ret |= DrawPicker(set, id, x);
var odd = true;
foreach (var id in AllCustomizations.Where((c, i) => set.Type(c) == CharaMakeParams.MenuType.IconSelector))
{
ret |= DrawPicker(set, id, x);
if (odd)
ImGui.SameLine();
odd = !odd;
}
if (!odd)
ImGui.NewLine();
ret |= DrawPicker(set, CustomizationId.FacialFeaturesTattoos, x);
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ListSelector))
ret |= DrawPicker(set, id, x);
odd = true;
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ColorPicker))
{
ret |= DrawPicker(set, id, x);
if (odd)
ImGui.SameLine();
odd = !odd;
}
if (!odd)
ImGui.NewLine();
var tmp = x.Value.HighlightsOn;
if (ImGui.Checkbox(set.Option(CustomizationId.HighlightsOnFlag), ref tmp) && tmp != x.Value.HighlightsOn)
{
x.Value.HighlightsOn = tmp;
ret = true;
}
var xPos = _inputIntSize + _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
ImGui.SameLine(xPos);
tmp = x.Value.FacePaintReversed;
if (ImGui.Checkbox($"{GlamourerPlugin.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}", ref tmp)
&& tmp != x.Value.FacePaintReversed)
{
x.Value.FacePaintReversed = tmp;
ret = true;
}
tmp = x.Value.SmallIris;
if (ImGui.Checkbox($"{GlamourerPlugin.Customization.GetName(CustomName.IrisSmall)} {set.Option(CustomizationId.EyeColorL)}",
ref tmp)
&& tmp != x.Value.SmallIris)
{
x.Value.SmallIris = tmp;
ret = true;
}
if (x.Value.Race != Race.Hrothgar)
{
tmp = x.Value.Lipstick;
ImGui.SameLine(xPos);
if (ImGui.Checkbox(set.Option(CustomizationId.LipColor), ref tmp) && tmp != x.Value.Lipstick)
{
x.Value.Lipstick = tmp;
ret = true;
}
}
return ret;
}
private void Draw()
{
ImGui.SetNextWindowSizeConstraints(Vector2.One * 450 * ImGui.GetIO().FontGlobalScale,
if (!_visible)
return;
ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
if (!_visible || !ImGui.Begin(_glamourerHeader, ref _visible))
if (!ImGui.Begin(_glamourerHeader, ref _visible))
return;
try
{
var inCombo = ImGui.BeginCombo("Actor", _currentActorName);
var idx = 0;
_player = null;
foreach (var actor in _actors.Where(a => a.ObjectKind == ObjectKind.Player))
{
if (_currentActorName == actor.Name)
_player = actor;
using var raii = new ImGuiRaii();
if (!raii.Begin(() => ImGui.BeginTabBar("##tabBar"), ImGui.EndTabBar))
return;
if (inCombo && ImGui.Selectable($"{actor.Name}##{idx++}"))
_currentActorName = actor.Name;
}
_inGPose = _actors[GPoseActorId] != null;
_iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2;
_actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
_comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
_percentageSize = _comboSelectorSize;
_inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X;
_raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
_itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
if (_player == null)
{
_player = _actors[0];
_currentActorName = _player?.Name ?? string.Empty;
}
if (inCombo)
ImGui.EndCombo();
if (_player == _actors[0] && _actors[GPoseActorId] != null)
_player = _actors[GPoseActorId];
if (_player == null || !GlamourerPlugin.PluginInterface.ClientState.Condition.Any())
{
ImGui.TextColored(new Vector4(0.4f, 0.1f, 0.1f, 1f),
"No player character available.");
}
else
{
var equip = new ActorEquipment(_player);
_iconSize = Vector2.One * ImGui.GetTextLineHeightWithSpacing() * 2;
_actualIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
_comboSelectorSize = 4 * _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
_percentageSize = _comboSelectorSize;
_inputIntSize = 2 * _actualIconSize.X + ImGui.GetStyle().ItemSpacing.X;
_raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
_itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
var changes = false;
if (ImGui.CollapsingHeader("Character Equipment"))
{
changes |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
changes |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
changes |= DrawEquip(EquipSlot.Head, equip.Head);
changes |= DrawEquip(EquipSlot.Body, equip.Body);
changes |= DrawEquip(EquipSlot.Hands, equip.Hands);
changes |= DrawEquip(EquipSlot.Legs, equip.Legs);
changes |= DrawEquip(EquipSlot.Feet, equip.Feet);
changes |= DrawEquip(EquipSlot.Ears, equip.Ears);
changes |= DrawEquip(EquipSlot.Neck, equip.Neck);
changes |= DrawEquip(EquipSlot.Wrists, equip.Wrists);
changes |= DrawEquip(EquipSlot.RFinger, equip.RFinger);
changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger);
}
var x = new LazyCustomization(_player!.Address);
if (ImGui.CollapsingHeader("Character Customization"))
changes |= DrawStuff(x);
if (ImGui.Button("Copy to Clipboard"))
{
var save = new CharacterSave();
save.Load(x.Value);
save.Load(equip);
Clipboard.SetText(save.ToBase64());
}
ImGui.SameLine();
if (ImGui.Button("Apply from Clipboard"))
{
var text = Clipboard.GetText();
if (text.Any())
{
try
{
var save = CharacterSave.FromString(text);
save.Customizations.Write(_player.Address);
save.Equipment.Write(_player.Address);
changes = true;
}
catch (Exception e)
{
PluginLog.Information($"{e}");
}
}
}
if (changes)
UpdateActors(_player);
}
DrawActorTab();
DrawSaves();
}
finally
{

View file

@ -0,0 +1,188 @@
using System;
using System.Linq;
using System.Numerics;
using System.Windows.Forms;
using Dalamud.Interface;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.FileSystem;
using ImGuiNET;
namespace Glamourer.Gui
{
internal partial class Interface
{
private readonly CharacterSave _currentSave = new();
private string _newDesignName = string.Empty;
private bool _keyboardFocus = false;
private const string DesignNamePopupLabel = "Save Design As...";
private const uint RedHeaderColor = 0xFF1818C0;
private const uint GreenHeaderColor = 0xFF18C018;
private void DrawActorHeader()
{
var color = _player == null ? RedHeaderColor : GreenHeaderColor;
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
using var raii = new ImGuiRaii()
.PushColor(ImGuiCol.Text, color)
.PushColor(ImGuiCol.Button, buttonColor)
.PushColor(ImGuiCol.ButtonHovered, buttonColor)
.PushColor(ImGuiCol.ButtonActive, buttonColor)
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
ImGui.Button($"{_currentActorName}##actorHeader", -Vector2.UnitX * 0.0001f);
}
private static void DrawCopyClipboardButton(CharacterSave save)
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
Clipboard.SetText(save.ToBase64());
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Copy customization code to clipboard.");
}
private bool DrawApplyClipboardButton()
{
ImGui.PushFont(UiBuilder.IconFont);
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Apply customization code from clipboard.");
if (!applyButton)
return false;
var text = Clipboard.GetText();
if (!text.Any())
return false;
try
{
var save = CharacterSave.FromString(text);
save.Apply(_player!);
}
catch (Exception e)
{
PluginLog.Information($"{e}");
return false;
}
return true;
}
private void DrawSaveDesignButton()
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
OpenDesignNamePopup(DesignNameUse.SaveCurrent);
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Save the current design.");
DrawDesignNamePopup(DesignNameUse.SaveCurrent);
}
private void DrawTargetPlayerButton()
{
if (ImGui.Button("Target Player"))
GlamourerPlugin.PluginInterface.ClientState.Targets.SetCurrentTarget(_player);
}
private void DrawApplyToPlayerButton(CharacterSave save)
{
if (ImGui.Button("Apply to Self"))
{
var player = _inGPose
? GlamourerPlugin.PluginInterface.ClientState.Actors[GPoseActorId]
: GlamourerPlugin.PluginInterface.ClientState.LocalPlayer;
var fallback = _inGPose ? GlamourerPlugin.PluginInterface.ClientState.LocalPlayer : null;
if (player != null)
{
save.Apply(player);
if (_inGPose)
save.Apply(fallback!);
_plugin.UpdateActors(player, fallback);
}
}
}
private void DrawApplyToTargetButton(CharacterSave save)
{
if (ImGui.Button("Apply to Target"))
{
var player = GlamourerPlugin.PluginInterface.ClientState.Targets.CurrentTarget;
if (player != null)
{
var fallBackActor = _playerNames[player.Name];
save.Apply(player);
if (fallBackActor != null)
save.Apply(fallBackActor);
_plugin.UpdateActors(player, fallBackActor);
}
}
}
private void SaveNewDesign(CharacterSave save)
{
try
{
var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
if (name.Any())
{
var newDesign = new Design(folder, name) { Data = save };
folder.AddChild(newDesign);
_designs.Designs[newDesign.FullName()] = save;
_designs.SaveToFile();
}
}
catch (Exception e)
{
PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
}
}
private void DrawActorPanel()
{
ImGui.BeginGroup();
DrawActorHeader();
if (!ImGui.BeginChild("##actorData", -Vector2.One, true))
return;
DrawCopyClipboardButton(_currentSave);
ImGui.SameLine();
var changes = DrawApplyClipboardButton();
ImGui.SameLine();
DrawSaveDesignButton();
ImGui.SameLine();
DrawApplyToPlayerButton(_currentSave);
if (!_inGPose)
{
ImGui.SameLine();
DrawApplyToTargetButton(_currentSave);
if (_player != null)
{
ImGui.SameLine();
DrawTargetPlayerButton();
}
}
if (DrawCustomization(ref _currentSave.Customizations) && _player != null)
{
_currentSave.Customizations.Write(_player.Address);
changes = true;
}
changes |= DrawEquip(_currentSave.Equipment);
changes |= DrawMiscellaneous(_currentSave, _player);
if (_player != null && changes)
_plugin.UpdateActors(_player);
ImGui.EndChild();
ImGui.EndGroup();
}
}
}

View file

@ -0,0 +1,151 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Interface;
using ImGuiNET;
namespace Glamourer.Gui
{
internal partial class Interface
{
private Actor? _player;
private string _currentActorName = string.Empty;
private string _actorFilter = string.Empty;
private string _actorFilterLower = string.Empty;
private readonly Dictionary<string, Actor?> _playerNames = new(400);
private void DrawActorFilter()
{
using var raii = new ImGuiRaii()
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
if (ImGui.InputTextWithHint("##actorFilter", "Filter Players...", ref _actorFilter, 32))
_actorFilterLower = _actorFilter.ToLowerInvariant();
}
private void DrawActorSelectable(Actor actor, bool gPose)
{
var actorName = actor.Name;
if (!actorName.Any())
return;
if (_playerNames.ContainsKey(actorName))
{
_playerNames[actorName] = actor;
return;
}
_playerNames.Add(actorName, null);
var label = gPose ? $"{actorName} (GPose)" : actorName;
if (!_actorFilterLower.Any() || actorName.ToLowerInvariant().Contains(_actorFilterLower))
if (ImGui.Selectable(label, _currentActorName == actorName))
{
_currentActorName = actorName;
_currentSave.LoadActor(actor);
_player = actor;
return;
}
if (_currentActorName == actor.Name)
{
_currentSave.LoadActor(actor);
_player = actor;
}
}
private void DrawSelectionButtons()
{
using var raii = new ImGuiRaii()
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0)
.PushFont(UiBuilder.IconFont);
Actor? select = null;
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth))
select = GlamourerPlugin.PluginInterface.ClientState.LocalPlayer;
raii.PopFonts();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Select the local player character.");
ImGui.SameLine();
raii.PushFont(UiBuilder.IconFont);
if (_inGPose)
{
raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth);
raii.PopStyles();
}
else
{
if (ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth))
select = GlamourerPlugin.PluginInterface.ClientState.Targets.CurrentTarget;
}
raii.PopFonts();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Select the current target, if it is a player actor.");
if (select == null || select.ObjectKind != ObjectKind.Player)
return;
_player = select;
_currentActorName = _player.Name;
_currentSave.LoadActor(_player);
}
private void DrawActorSelector()
{
ImGui.BeginGroup();
DrawActorFilter();
if (!ImGui.BeginChild("##actorSelector",
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
return;
_playerNames.Clear();
for (var i = GPoseActorId; i < GPoseActorId + 48; ++i)
{
var actor = _actors[i];
if (actor == null)
break;
if (actor.ObjectKind == ObjectKind.Player)
DrawActorSelectable(actor, true);
}
for (var i = 0; i < GPoseActorId; i += 2)
{
var actor = _actors[i];
if (actor != null && actor.ObjectKind == ObjectKind.Player)
DrawActorSelectable(actor, false);
}
using (var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
{
ImGui.EndChild();
}
DrawSelectionButtons();
ImGui.EndGroup();
}
private void DrawActorTab()
{
using var raii = new ImGuiRaii();
if (!raii.Begin(() => ImGui.BeginTabItem("Current Players"), ImGui.EndTabItem))
return;
_player = null;
DrawActorSelector();
if (!_currentActorName.Any())
return;
ImGui.SameLine();
DrawActorPanel();
}
}
}

View file

@ -0,0 +1,480 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Plugin;
using Glamourer.Customization;
using ImGuiNET;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui
{
internal partial class Interface
{
private static bool DrawColorPickerPopup(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);
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
{
value = custom;
ret = true;
ImGui.CloseCurrentPopup();
}
if (i % 8 != 7)
ImGui.SameLine();
}
ImGui.EndPopup();
return ret;
}
private Vector2 _iconSize = Vector2.Zero;
private Vector2 _actualIconSize = Vector2.Zero;
private float _raceSelectorWidth = 0;
private float _inputIntSize = 0;
private float _comboSelectorSize = 0;
private float _percentageSize = 0;
private float _itemComboWidth = 0;
private bool InputInt(string label, ref int value, int minValue, int maxValue)
{
var ret = false;
var tmp = value + 1;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt(label, ref tmp, 1) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue)
{
value = tmp - 1;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip($"Input Range: [{minValue}, {maxValue}]");
return ret;
}
private static (int, Customization.Customization) GetCurrentCustomization(ref ActorCustomization customization, CustomizationId id,
CustomizationSet set)
{
var current = set.DataByValue(id, customization[id], out var custom);
if (set.IsAvailable(id) && current < 0)
{
PluginLog.Warning($"Read invalid customization value {customization[id]} for {id}.");
current = 0;
custom = set.Data(id, 0);
}
return (current, custom!.Value);
}
private bool DrawColorPicker(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
CustomizationSet set)
{
var ret = false;
var count = set.Count(id);
var (current, custom) = GetCurrentCustomization(ref customization, id, set);
var popupName = $"Color Picker##{id}";
if (ImGui.ColorButton($"{current + 1}##color_{id}", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None,
_actualIconSize))
ImGui.OpenPopup(popupName);
ImGui.SameLine();
using (var group = ImGuiRaii.NewGroup())
{
if (InputInt($"##text_{id}", ref current, 1, count))
{
customization[id] = set.Data(id, current - 1).Value;
ret = true;
}
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 bool DrawListSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
int current = customization[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.BeginCombo($"##combo_{id}", $"{set.Option(id)} #{current + 1}"))
{
for (var i = 0; i < count; ++i)
{
if (ImGui.Selectable($"{set.Option(id)} #{i + 1}##combo", i == current) && i != current)
{
customization[id] = (byte) i;
ret = true;
}
}
ImGui.EndCombo();
}
ImGui.SameLine();
if (InputInt($"##text_{id}", ref current, 1, 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 bool DrawMultiSelector(ref ActorCustomization customization, CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
using (var _ = ImGuiRaii.NewGroup())
{
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 = i == count - 1
? _legacyTattooIcon ?? GlamourerPlugin.Customization.GetIcon(feature.IconId)
: GlamourerPlugin.Customization.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, 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));
}
if (i % 4 != 3)
ImGui.SameLine();
}
}
ImGui.SameLine();
using var group = ImGuiRaii.NewGroup();
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
int value = customization[CustomizationId.FacialFeaturesTattoos];
if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256))
{
customization[CustomizationId.FacialFeaturesTattoos] = (byte) value;
ret = true;
}
ImGui.Text(set.Option(CustomizationId.FacialFeaturesTattoos));
return ret;
}
private 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, _iconSize))
{
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 bool DrawIconSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
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, _iconSize))
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();
if (InputInt($"##text_{id}", ref current, 1, count))
{
customization[id] = set.Data(id, current).Value;
ret = true;
}
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 bool DrawPercentageSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
CustomizationSet set)
{
using var bigGroup = ImGuiRaii.NewGroup();
var ret = false;
int value = customization[id];
var count = set.Count(id);
ImGui.SetNextItemWidth(_percentageSize * ImGui.GetIO().FontGlobalScale);
if (ImGui.SliderInt($"##slider_{id}", ref value, 0, count - 1, "") && value != customization[id])
{
customization[id] = (byte) value;
ret = true;
}
ImGui.SameLine();
--value;
if (InputInt($"##input_{id}", ref value, 0, count - 1))
{
customization[id] = (byte) (value + 1);
ret = true;
}
ImGui.SameLine();
ImGui.Text(label);
if (tooltip.Any() && ImGui.IsItemHovered())
ImGui.SetTooltip(tooltip);
return ret;
}
private bool DrawRaceSelector(ref ActorCustomization customization)
{
using var group = ImGuiRaii.NewGroup();
var ret = false;
ImGui.SetNextItemWidth(_raceSelectorWidth);
if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender)))
{
for (var i = 0; i < (int)SubRace.Veena; ++i)
{
if (ImGui.Selectable(ClanName((SubRace)i + 1, customization.Gender), (int)customization.Clan == i + 1))
{
var race = (SubRace)i + 1;
ret |= ChangeRace(ref customization, race);
}
}
ImGui.EndCombo();
}
ImGui.Text(
$"{GlamourerPlugin.Customization.GetName(CustomName.Gender)} & {GlamourerPlugin.Customization.GetName(CustomName.Clan)}");
return ret;
}
private bool DrawGenderSelector(ref ActorCustomization customization)
{
var ret = false;
ImGui.PushFont(UiBuilder.IconFont);
var icon = customization.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
var restricted = false;
if (customization.Race == Race.Viera)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f);
icon = FontAwesomeIcon.VenusDouble;
restricted = true;
}
else if (customization.Race == Race.Hrothgar)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.25f);
icon = FontAwesomeIcon.MarsDouble;
restricted = true;
}
if (ImGui.Button(icon.ToIconString(), _actualIconSize) && !restricted)
{
var gender = customization.Gender == Gender.Male ? Gender.Female : Gender.Male;
ret = ChangeGender(ref customization, gender);
}
if (restricted)
ImGui.PopStyleVar();
ImGui.PopFont();
return ret;
}
private bool DrawPicker(CustomizationSet set, CustomizationId id, ref ActorCustomization customization)
{
if (!set.IsAvailable(id))
return false;
switch (set.Type(id))
{
case CharaMakeParams.MenuType.ColorPicker: return DrawColorPicker(set.OptionName[(int)id], "", ref customization, id, set);
case CharaMakeParams.MenuType.ListSelector: return DrawListSelector(set.OptionName[(int)id], "", ref customization, id, set);
case CharaMakeParams.MenuType.IconSelector: return DrawIconSelector(set.OptionName[(int)id], "", ref customization, id, set);
case CharaMakeParams.MenuType.MultiIconSelector: return DrawMultiSelector(ref customization, set);
case CharaMakeParams.MenuType.Percentage: return DrawPercentageSelector(set.OptionName[(int)id], "", ref customization, id, set);
}
return false;
}
private static readonly CustomizationId[] AllCustomizations = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
private bool DrawCustomization(ref ActorCustomization custom)
{
if (!ImGui.CollapsingHeader("Character Customization"))
return false;
var ret = DrawGenderSelector(ref custom);
ImGui.SameLine();
ret |= DrawRaceSelector(ref custom);
var set = GlamourerPlugin.Customization.GetList(custom.Clan, custom.Gender);
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.Percentage))
ret |= DrawPicker(set, id, ref custom);
var odd = true;
foreach (var id in AllCustomizations.Where((c, _) => set.Type(c) == CharaMakeParams.MenuType.IconSelector))
{
ret |= DrawPicker(set, id, ref custom);
if (odd)
ImGui.SameLine();
odd = !odd;
}
if (!odd)
ImGui.NewLine();
ret |= DrawPicker(set, CustomizationId.FacialFeaturesTattoos, ref custom);
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ListSelector))
ret |= DrawPicker(set, id, ref custom);
odd = true;
foreach (var id in AllCustomizations.Where(c => set.Type(c) == CharaMakeParams.MenuType.ColorPicker))
{
ret |= DrawPicker(set, id, ref custom);
if (odd)
ImGui.SameLine();
odd = !odd;
}
if (!odd)
ImGui.NewLine();
var tmp = custom.HighlightsOn;
if (ImGui.Checkbox(set.Option(CustomizationId.HighlightsOnFlag), ref tmp) && tmp != custom.HighlightsOn)
{
custom.HighlightsOn = tmp;
ret = true;
}
var xPos = _inputIntSize + _actualIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
ImGui.SameLine(xPos);
tmp = custom.FacePaintReversed;
if (ImGui.Checkbox($"{GlamourerPlugin.Customization.GetName(CustomName.Reverse)} {set.Option(CustomizationId.FacePaint)}", ref tmp)
&& tmp != custom.FacePaintReversed)
{
custom.FacePaintReversed = tmp;
ret = true;
}
tmp = custom.SmallIris;
if (ImGui.Checkbox($"{GlamourerPlugin.Customization.GetName(CustomName.IrisSmall)} {set.Option(CustomizationId.EyeColorL)}",
ref tmp)
&& tmp != custom.SmallIris)
{
custom.SmallIris = tmp;
ret = true;
}
if (custom.Race != Race.Hrothgar)
{
tmp = custom.Lipstick;
ImGui.SameLine(xPos);
if (ImGui.Checkbox(set.Option(CustomizationId.LipColor), ref tmp) && tmp != custom.Lipstick)
{
custom.Lipstick = tmp;
ret = true;
}
}
return ret;
}
}
}

View file

@ -0,0 +1,343 @@
using System;
using System.Linq;
using System.Numerics;
using System.Windows.Forms;
using Dalamud.Interface;
using Dalamud.Plugin;
using Glamourer.Designs;
using Glamourer.FileSystem;
using ImGuiNET;
namespace Glamourer.Gui
{
internal partial class Interface
{
private int _totalObject = 0;
private Design? _selection = null;
private string _newChildName = string.Empty;
private void DrawDesignSelector()
{
_totalObject = 0;
ImGui.BeginGroup();
if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, - ImGui.GetFrameHeight() - 1) , true))
{
DrawFolderContent(_designs.FileSystem.Root, SortMode.FoldersFirst);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGui.EndChild();
ImGui.PopStyleVar();
}
DrawDesignSelectorButtons();
ImGui.EndGroup();
}
private void DrawPasteClipboardButton()
{
if (_selection!.Data.WriteProtected)
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
ImGui.PushFont(UiBuilder.IconFont);
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
ImGui.PopFont();
if (_selection!.Data.WriteProtected)
ImGui.PopStyleVar();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Overwrite with customization code from clipboard.");
if (_selection!.Data.WriteProtected || !applyButton)
return;
var text = Clipboard.GetText();
if (!text.Any())
return;
try
{
_selection!.Data = CharacterSave.FromString(text);
_designs.SaveToFile();
}
catch (Exception e)
{
PluginLog.Information($"{e}");
}
}
private void DrawNewFolderButton()
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
OpenDesignNamePopup(DesignNameUse.NewFolder);
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new, empty Folder.");
DrawDesignNamePopup(DesignNameUse.NewFolder);
}
private void DrawNewDesignButton()
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
OpenDesignNamePopup(DesignNameUse.NewDesign);
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new, empty Design.");
DrawDesignNamePopup(DesignNameUse.NewDesign);
}
private void DrawClipboardDesignButton()
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
OpenDesignNamePopup(DesignNameUse.FromClipboard);
ImGui.PopFont();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Create a new design from the customization string in your clipboard.");
DrawDesignNamePopup(DesignNameUse.FromClipboard);
}
private void DrawDeleteDesignButton()
{
ImGui.PushFont(UiBuilder.IconFont);
var style = _selection == null;
if (style)
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
{
_designs.DeleteAllChildren(_selection, false);
_selection = null;
}
ImGui.PopFont();
if (style)
ImGui.PopStyleVar();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Delete the currently selected Design.");
}
private void DrawDuplicateDesignButton()
{
ImGui.PushFont(UiBuilder.IconFont);
if (_selection == null)
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
ImGui.PopFont();
if (_selection == null)
ImGui.PopStyleVar();
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Clone the currently selected Design.");
DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
}
private void DrawDesignSelectorButtons()
{
using var raii = new ImGuiRaii()
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
DrawNewFolderButton();
ImGui.SameLine();
DrawNewDesignButton();
ImGui.SameLine();
DrawClipboardDesignButton();
ImGui.SameLine();
DrawDuplicateDesignButton();
ImGui.SameLine();
DrawDeleteDesignButton();
}
private void DrawDesignHeaderButtons()
{
DrawCopyClipboardButton(_selection!.Data);
ImGui.SameLine();
DrawPasteClipboardButton();
ImGui.SameLine();
DrawApplyToPlayerButton(_selection!.Data);
if (!_inGPose)
{
ImGui.SameLine();
DrawApplyToTargetButton(_selection!.Data);
}
ImGui.SameLine();
DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
}
private void DrawDesignPanel()
{
if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
{
DrawDesignHeaderButtons();
var data = _selection!.Data;
var prot = _selection!.Data.WriteProtected;
if (prot)
{
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
data = data.Copy();
}
DrawGeneralSettings(data, prot);
var mask = data.WriteEquipment;
if (DrawEquip(data.Equipment, ref mask) && !prot)
{
data.WriteEquipment = mask;
_designs.SaveToFile();
}
if (DrawCustomization(ref data.Customizations) && !prot)
_designs.SaveToFile();
if (DrawMiscellaneous(data, null) && !prot)
_designs.SaveToFile();
if (prot)
ImGui.PopStyleVar();
ImGui.EndChild();
}
}
private void DrawSaves()
{
using var raii = new ImGuiRaii();
raii.PushStyle(ImGuiStyleVar.IndentSpacing, 25f);
if (!raii.Begin(() => ImGui.BeginTabItem("Saves"), ImGui.EndTabItem))
return;
DrawDesignSelector();
if (_selection != null)
{
ImGui.SameLine();
DrawDesignPanel();
}
}
private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
{
var tmp = value;
if (ImGui.Checkbox(label, ref tmp) && tmp != value)
{
setter(tmp);
if (!prot)
_designs.SaveToFile();
}
}
private void DrawGeneralSettings(CharacterSave data, bool prot)
{
ImGui.BeginGroup();
DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
ImGui.EndGroup();
ImGui.SameLine();
ImGui.BeginGroup();
DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
ImGui.EndGroup();
}
private void RenameChildInput(IFileSystemBase child)
{
ImGui.SetNextItemWidth(150);
if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
ImGuiInputTextFlags.EnterReturnsTrue))
return;
if (_newChildName.Any() && _newChildName != child.Name)
try
{
var oldPath = child.FullName();
if (_designs.FileSystem.Rename(child, _newChildName))
_designs.UpdateAllChildren(oldPath, child);
}
catch (Exception e)
{
PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
}
else if (child is Folder f)
try
{
var oldPath = child.FullName();
if (_designs.FileSystem.Merge(f, f.Parent, true))
_designs.UpdateAllChildren(oldPath, f.Parent);
}
catch (Exception e)
{
PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
}
_newChildName = string.Empty;
}
private void ContextMenu(IFileSystemBase child)
{
var label = $"##fsPopup{child.FullName()}";
var renameLabel = $"{label}_rename";
if (ImGui.BeginPopup(label))
{
if (ImGui.MenuItem("Delete"))
_designs.DeleteAllChildren(child, false);
RenameChildInput(child);
if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
Clipboard.SetText(d.Data.ToBase64());
ImGui.EndPopup();
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
_newChildName = child.Name;
ImGui.OpenPopup(label);
}
}
private void DrawFolderContent(Folder folder, SortMode mode)
{
foreach (var child in folder.AllChildren(mode).ToArray())
{
if (child.IsFolder(out var subFolder))
{
var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
DrawOrnaments(child);
if (treeNode)
{
DrawFolderContent(subFolder, mode);
ImGui.TreePop();
}
else
{
_totalObject += subFolder.TotalDescendantLeaves();
}
}
else
{
++_totalObject;
var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
DrawOrnaments(child);
if (selected)
if (child is Design d)
_selection = d;
}
}
}
private void DrawOrnaments(IFileSystemBase child)
{
FileSystemImGui.DragDropSource(child);
if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
_designs.UpdateAllChildren(oldPath, draggedFolder!);
ContextMenu(child);
}
}
}

View file

@ -0,0 +1,138 @@
using ImGuiNET;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui
{
internal partial class Interface
{
private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
{
stainCombo.PostPreview = null;
if (_stains.TryGetValue((byte) stainIdx, out var stain))
{
var previewPush = PushColor(stain, ImGuiCol.FrameBg);
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
}
if (stainCombo.Draw(string.Empty, out var newStain) && _player != null && !newStain.RowIndex.Equals(stainIdx))
{
newStain.Write(_player.Address, slot);
return true;
}
return false;
}
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item? item)
{
var currentName = item?.Name.ToString() ?? "Nothing";
if (equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && _player != null && newItem.Base.RowId != item?.RowId)
{
newItem.Write(_player.Address);
return true;
}
return false;
}
private static bool DrawCheckbox(ActorEquipMask flag, ref ActorEquipMask mask)
{
var tmp = (uint) mask;
var ret = false;
if (ImGui.CheckboxFlags($"##flag_{(uint) flag}", ref tmp, (uint) flag) && tmp != (uint) mask)
{
mask = (ActorEquipMask) tmp;
ret = true;
}
if (ImGui.IsItemHovered())
ImGui.SetTooltip("Enable writing this slot in this save.");
return ret;
}
private bool DrawEquipSlot(EquipSlot slot, ActorArmor equip)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
ImGui.SameLine();
var item = _identifier.Identify(equip.Set, new WeaponType(), equip.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
private bool DrawEquipSlotWithCheck(EquipSlot slot, ActorArmor equip, ActorEquipMask flag, ref ActorEquipMask mask)
{
var ret = DrawCheckbox(flag, ref mask);
ImGui.SameLine();
ret |= DrawEquipSlot(slot, equip);
return ret;
}
private bool DrawWeapon(EquipSlot slot, ActorWeapon weapon)
{
var (equipCombo, stainCombo) = _combos[slot];
var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
ImGui.SameLine();
var item = _identifier.Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
ret |= DrawItemSelector(equipCombo, item);
return ret;
}
private bool DrawWeaponWithCheck(EquipSlot slot, ActorWeapon weapon, ActorEquipMask flag, ref ActorEquipMask mask)
{
var ret = DrawCheckbox(flag, ref mask);
ImGui.SameLine();
ret |= DrawWeapon(slot, weapon);
return ret;
}
private bool DrawEquip(ActorEquipment equip)
{
var ret = false;
if (ImGui.CollapsingHeader("Character Equipment"))
{
ret |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
ret |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
ret |= DrawEquipSlot(EquipSlot.Head, equip.Head);
ret |= DrawEquipSlot(EquipSlot.Body, equip.Body);
ret |= DrawEquipSlot(EquipSlot.Hands, equip.Hands);
ret |= DrawEquipSlot(EquipSlot.Legs, equip.Legs);
ret |= DrawEquipSlot(EquipSlot.Feet, equip.Feet);
ret |= DrawEquipSlot(EquipSlot.Ears, equip.Ears);
ret |= DrawEquipSlot(EquipSlot.Neck, equip.Neck);
ret |= DrawEquipSlot(EquipSlot.Wrists, equip.Wrists);
ret |= DrawEquipSlot(EquipSlot.RFinger, equip.RFinger);
ret |= DrawEquipSlot(EquipSlot.LFinger, equip.LFinger);
}
return ret;
}
private bool DrawEquip(ActorEquipment equip, ref ActorEquipMask mask)
{
var ret = false;
if (ImGui.CollapsingHeader("Character Equipment"))
{
ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, ActorEquipMask.MainHand, ref mask);
ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, ActorEquipMask.OffHand, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, ActorEquipMask.Head, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, ActorEquipMask.Body, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, ActorEquipMask.Hands, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, ActorEquipMask.Legs, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, ActorEquipMask.Feet, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, ActorEquipMask.Ears, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, ActorEquipMask.Neck, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, ActorEquipMask.Wrists, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, ActorEquipMask.RFinger, ref mask);
ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, ActorEquipMask.LFinger, ref mask);
}
return ret;
}
}
}

View file

@ -0,0 +1,208 @@
using System;
using System.Linq;
using System.Windows.Forms;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Plugin;
using Glamourer.Customization;
using ImGuiNET;
using Penumbra.Api;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui
{
internal partial class Interface
{
// Push the stain color to type and if it is too bright, turn the text color black.
// Return number of pushed styles.
private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
{
ImGui.PushStyleColor(type, stain.RgbaColor);
if (stain.Intensity > 127)
{
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
return 2;
}
return 1;
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static void FixUpAttributes(ref ActorCustomization 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.HighlightsOnFlag: break;
case CustomizationId.Face:
if (customization.Race != Race.Hrothgar)
goto default;
break;
default:
var count = set.Count(id);
if (set.DataByValue(id, customization[id], out var value) < 0)
if (count == 0)
customization[id] = 0;
else
customization[id] = set.Data(id, 0).Value;
break;
}
}
}
// Change a race and fix up all required customizations afterwards.
private static bool ChangeRace(ref ActorCustomization 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(ref customization);
return true;
}
// Change a gender and fix up all required customizations afterwards.
private static bool ChangeGender(ref ActorCustomization customization, Gender gender)
{
if (gender == customization.Gender)
return false;
customization.Gender = gender;
FixUpAttributes(ref customization);
return true;
}
private static string ClanName(SubRace race, Gender gender)
{
if (gender == Gender.Female)
return race switch
{
SubRace.Midlander => GlamourerPlugin.Customization.GetName(CustomName.MidlanderM),
SubRace.Highlander => GlamourerPlugin.Customization.GetName(CustomName.HighlanderM),
SubRace.Wildwood => GlamourerPlugin.Customization.GetName(CustomName.WildwoodM),
SubRace.Duskwight => GlamourerPlugin.Customization.GetName(CustomName.DuskwightM),
SubRace.Plainsfolk => GlamourerPlugin.Customization.GetName(CustomName.PlainsfolkM),
SubRace.Dunesfolk => GlamourerPlugin.Customization.GetName(CustomName.DunesfolkM),
SubRace.SeekerOfTheSun => GlamourerPlugin.Customization.GetName(CustomName.SeekerOfTheSunM),
SubRace.KeeperOfTheMoon => GlamourerPlugin.Customization.GetName(CustomName.KeeperOfTheMoonM),
SubRace.Seawolf => GlamourerPlugin.Customization.GetName(CustomName.SeawolfM),
SubRace.Hellsguard => GlamourerPlugin.Customization.GetName(CustomName.HellsguardM),
SubRace.Raen => GlamourerPlugin.Customization.GetName(CustomName.RaenM),
SubRace.Xaela => GlamourerPlugin.Customization.GetName(CustomName.XaelaM),
SubRace.Helion => GlamourerPlugin.Customization.GetName(CustomName.HelionM),
SubRace.Lost => GlamourerPlugin.Customization.GetName(CustomName.LostM),
SubRace.Rava => GlamourerPlugin.Customization.GetName(CustomName.RavaF),
SubRace.Veena => GlamourerPlugin.Customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
return race switch
{
SubRace.Midlander => GlamourerPlugin.Customization.GetName(CustomName.MidlanderF),
SubRace.Highlander => GlamourerPlugin.Customization.GetName(CustomName.HighlanderF),
SubRace.Wildwood => GlamourerPlugin.Customization.GetName(CustomName.WildwoodF),
SubRace.Duskwight => GlamourerPlugin.Customization.GetName(CustomName.DuskwightF),
SubRace.Plainsfolk => GlamourerPlugin.Customization.GetName(CustomName.PlainsfolkF),
SubRace.Dunesfolk => GlamourerPlugin.Customization.GetName(CustomName.DunesfolkF),
SubRace.SeekerOfTheSun => GlamourerPlugin.Customization.GetName(CustomName.SeekerOfTheSunF),
SubRace.KeeperOfTheMoon => GlamourerPlugin.Customization.GetName(CustomName.KeeperOfTheMoonF),
SubRace.Seawolf => GlamourerPlugin.Customization.GetName(CustomName.SeawolfF),
SubRace.Hellsguard => GlamourerPlugin.Customization.GetName(CustomName.HellsguardF),
SubRace.Raen => GlamourerPlugin.Customization.GetName(CustomName.RaenF),
SubRace.Xaela => GlamourerPlugin.Customization.GetName(CustomName.XaelaF),
SubRace.Helion => GlamourerPlugin.Customization.GetName(CustomName.HelionM),
SubRace.Lost => GlamourerPlugin.Customization.GetName(CustomName.LostM),
SubRace.Rava => GlamourerPlugin.Customization.GetName(CustomName.RavaF),
SubRace.Veena => GlamourerPlugin.Customization.GetName(CustomName.VeenaF),
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
};
}
private enum DesignNameUse
{
SaveCurrent,
NewDesign,
DuplicateDesign,
NewFolder,
FromClipboard,
}
private void DrawDesignNamePopup(DesignNameUse use)
{
if (ImGui.BeginPopup($"{DesignNamePopupLabel}{use}"))
{
if (ImGui.InputText("##designName", ref _newDesignName, 64, ImGuiInputTextFlags.EnterReturnsTrue)
&& _newDesignName.Any())
{
switch (use)
{
case DesignNameUse.SaveCurrent:
SaveNewDesign(_currentSave);
break;
case DesignNameUse.NewDesign:
var empty = new CharacterSave();
empty.Load(ActorCustomization.Default);
empty.WriteCustomizations = false;
SaveNewDesign(empty);
break;
case DesignNameUse.DuplicateDesign:
SaveNewDesign(_selection!.Data.Copy());
break;
case DesignNameUse.NewFolder:
_designs.FileSystem.CreateAllFolders($"{_newDesignName}/a"); // Filename is just ignored, but all folders are created.
break;
case DesignNameUse.FromClipboard:
try
{
var text = Clipboard.GetText();
var save = CharacterSave.FromString(text);
SaveNewDesign(save);
}
catch (Exception e)
{
PluginLog.Information($"Could not save new Design from Clipboard:\n{e}");
}
break;
}
_newDesignName = string.Empty;
ImGui.CloseCurrentPopup();
}
if (_keyboardFocus)
{
ImGui.SetKeyboardFocusHere();
_keyboardFocus = false;
}
ImGui.EndPopup();
}
}
private void OpenDesignNamePopup(DesignNameUse use)
{
_newDesignName = string.Empty;
_keyboardFocus = true;
ImGui.OpenPopup($"{DesignNamePopupLabel}{use}");
}
}
}

View file

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Numerics;
using System.Reflection;
using ImGuiNET;
using Penumbra.GameData.Enums;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.Gui
{
internal partial class Interface
{
private const float ColorButtonWidth = 22.5f;
private const float ColorComboWidth = 140f;
private const float ItemComboWidth = 300f;
private static ComboWithFilter<Stain> CreateDefaultStainCombo(IReadOnlyList<Stain> stains)
=> new("##StainCombo", ColorComboWidth, ColorButtonWidth, stains,
s => s.Name.ToString())
{
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
PreList = () =>
{
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
},
PostList = () => { ImGui.PopStyleVar(3); },
CreateSelectable = s =>
{
var push = PushColor(s);
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
Vector2.UnitX * (ColorComboWidth - ImGui.GetStyle().ScrollbarSize));
ImGui.PopStyleColor(push);
return ret;
},
ItemsAtOnce = 12,
};
private ComboWithFilter<Item> CreateItemCombo(EquipSlot slot, IReadOnlyList<Item> items)
=> new($"{_equipSlotNames[slot]}##Equip", ItemComboWidth, ItemComboWidth, items, i => i.Name)
{
Flags = ImGuiComboFlags.HeightLarge,
};
private (ComboWithFilter<Item>, ComboWithFilter<Stain>) CreateCombos(EquipSlot slot, IReadOnlyList<Item> items,
ComboWithFilter<Stain> defaultStain)
=> (CreateItemCombo(slot, items), new ComboWithFilter<Stain>($"##{slot}Stain", defaultStain));
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
{
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
if (resource != null)
{
var rawImage = new byte[resource.Length];
resource.Read(rawImage, 0, (int) resource.Length);
return GlamourerPlugin.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
}
return null;
}
private static Dictionary<EquipSlot, string> GetEquipSlotNames()
{
var sheet = GlamourerPlugin.PluginInterface.Data.GetExcelSheet<Addon>();
var ret = new Dictionary<EquipSlot, string>(12)
{
[EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
[EquipSlot.OffHand] = sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
[EquipSlot.Head] = sheet.GetRow(740)?.Text.ToString() ?? "Head",
[EquipSlot.Body] = sheet.GetRow(741)?.Text.ToString() ?? "Body",
[EquipSlot.Hands] = sheet.GetRow(742)?.Text.ToString() ?? "Hands",
[EquipSlot.Legs] = sheet.GetRow(744)?.Text.ToString() ?? "Legs",
[EquipSlot.Feet] = sheet.GetRow(745)?.Text.ToString() ?? "Feet",
[EquipSlot.Ears] = sheet.GetRow(746)?.Text.ToString() ?? "Ears",
[EquipSlot.Neck] = sheet.GetRow(747)?.Text.ToString() ?? "Neck",
[EquipSlot.Wrists] = sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
[EquipSlot.RFinger] = sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
[EquipSlot.LFinger] = sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
};
return ret;
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using Dalamud.Game.ClientState.Actors.Types;
using ImGuiNET;
namespace Glamourer.Gui
{
internal partial class Interface
{
private static bool DrawCheckMark(string label, bool value, Action<bool> setter)
{
var startValue = value;
if (ImGui.Checkbox(label, ref startValue) && startValue != value)
{
setter(startValue);
return true;
}
return false;
}
private static bool DrawMiscellaneous(CharacterSave save, Actor? player)
{
var ret = false;
if (!ImGui.CollapsingHeader("Miscellaneous"))
return ret;
ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
{
save.HatState = v;
player?.SetHatHidden(!v);
});
ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
{
save.WeaponState = v;
player?.SetWeaponHidden(!v);
});
ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
{
save.VisorState = v;
player?.SetVisorToggled(v);
});
ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
{
save.IsWet = v;
player?.SetWetness(v);
});
var alpha = save.Alpha;
if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
{
alpha = (float) Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
save.Alpha = alpha;
ret = true;
if (player != null)
player.Alpha() = alpha;
}
return ret;
}
}
}