This commit is contained in:
Ottermandias 2022-10-02 23:43:21 +02:00
parent e3a58340b3
commit 6a4b5fc3b2
15 changed files with 740 additions and 300 deletions

View file

@ -77,6 +77,7 @@ public static class GameData
EmptySlot(EquipSlot.Feet),
EmptyNpc(EquipSlot.Feet),
},
[EquipSlot.MainHand] = new(2000),
[EquipSlot.RFinger] = new(200)
{
EmptySlot(EquipSlot.RFinger),
@ -87,8 +88,6 @@ public static class GameData
EmptySlot(EquipSlot.Neck),
EmptyNpc(EquipSlot.Neck),
},
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
[EquipSlot.Wrists] = new(200)
{
EmptySlot(EquipSlot.Wrists),
@ -112,10 +111,10 @@ public static class GameData
continue;
slot = slot.ToSlot();
if (!_itemsBySlot.TryGetValue(slot, out var list))
continue;
list.Add(new Item(item, name, slot));
if (slot == EquipSlot.OffHand)
slot = EquipSlot.MainHand;
if (_itemsBySlot.TryGetValue(slot, out var list))
list.Add(new Item(item, name, slot));
}
foreach (var list in _itemsBySlot.Values)

View file

@ -24,6 +24,9 @@ public readonly struct Item
public bool IsBothHand
=> (EquipSlot)Base.EquipSlotCategory.Row == EquipSlot.BothHand;
public WeaponCategory WeaponCategory
=> (WeaponCategory?) Base.ItemUICategory?.Row ?? WeaponCategory.Unknown;
// Create a new item from its sheet list with the given name and either the inferred equip slot or the given one.
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
{

View file

@ -142,7 +142,7 @@ internal partial class CustomizationDrawer
// Update all relevant Actors by calling the UpdateCustomize game function.
private void UpdateActors()
{
foreach (var actor in _actors.Where(a => a && a.DrawObject))
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
foreach (var actor in _actors)
Glamourer.RedrawManager.UpdateCustomize(actor, _customize);
}
}

View file

@ -25,7 +25,7 @@ internal partial class CustomizationDrawer
private void ListCombo()
{
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
if (!combo)
return;
@ -83,8 +83,7 @@ internal partial class CustomizationDrawer
if (ImGui.Checkbox(label, ref tmp) && tmp != current)
{
setter(tmp);
foreach (var actor in _actors.Where(a => a && a.DrawObject))
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
UpdateActors();
}
}
@ -99,6 +98,7 @@ internal partial class CustomizationDrawer
var data = _set.Data(_currentId, currentIndex, _customize.Face);
UpdateValue(data.Value);
}
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
}
}

View file

@ -0,0 +1,353 @@
using System;
using System.Collections.Generic;
using Dalamud.Interface;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Lumina.Text;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Widgets;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Item = Glamourer.Structs.Item;
namespace Glamourer.Gui.Equipment;
public partial class EquipmentDrawer
{
public const int ItemComboWidth = 320;
private sealed class ItemCombo : FilterComboBase<Item>
{
public readonly string Label;
public readonly EquipSlot Slot;
public Lumina.Excel.GeneratedSheets.Item? LastItem;
private CharacterArmor _lastArmor;
private string _lastPreview = string.Empty;
private int _lastIndex;
public ItemCombo(EquipSlot slot)
: base(GetItems(slot), false)
{
Label = GetLabel(slot);
Slot = slot;
}
protected override string ToString(Item obj)
=> obj.Name;
private static string GetLabel(EquipSlot slot)
{
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
return slot switch
{
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",
_ => string.Empty,
};
}
public bool Draw(CharacterArmor armor, out int newIdx)
{
UpdateItem(armor);
newIdx = _lastIndex;
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
}
private void UpdateItem(CharacterArmor armor)
{
if (armor.Equals(_lastArmor))
return;
_lastArmor = armor;
LastItem = Identify(armor.Set, 0, armor.Variant, Slot);
_lastIndex = Items.IndexOf(i => i.Base.RowId == LastItem.RowId);
_lastPreview = _lastIndex >= 0 ? Items[_lastIndex].Name : LastItem.Name.ToString();
}
private static IReadOnlyList<Item> GetItems(EquipSlot slot)
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(slot, out var list) ? list : Array.Empty<Item>();
}
private sealed class WeaponCombo : FilterComboBase<Item>
{
public readonly string Label;
public readonly EquipSlot Slot;
public Lumina.Excel.GeneratedSheets.Item? LastItem;
private CharacterWeapon _lastWeapon = new(ulong.MaxValue);
private string _lastPreview = string.Empty;
private int _lastIndex;
public WeaponCategory LastCategory { get; private set; }
private bool _drawAll;
public WeaponCombo(EquipSlot slot)
: base(GetItems(slot), false)
{
Label = GetLabel(slot);
Slot = slot;
}
protected override string ToString(Item obj)
=> obj.Name;
private static string GetLabel(EquipSlot slot)
{
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
return slot switch
{
EquipSlot.MainHand => sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
EquipSlot.OffHand => sheet.GetRow(739)?.Text.ToString() ?? "Off Hand",
_ => string.Empty,
};
}
public bool Draw(CharacterWeapon weapon, out int newIdx, bool drawAll)
{
if (drawAll != _drawAll)
{
_drawAll = drawAll;
ResetFilter();
}
UpdateItem(weapon);
UpdateCategory((WeaponCategory?)LastItem!.ItemUICategory?.Row ?? WeaponCategory.Unknown);
newIdx = _lastIndex;
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
}
public bool Draw(CharacterWeapon weapon, WeaponCategory category, out int newIdx)
{
if (_drawAll)
{
_drawAll = false;
ResetFilter();
}
UpdateItem(weapon);
UpdateCategory(category);
newIdx = _lastIndex;
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
}
protected override bool IsVisible(int globalIndex, LowerString filter)
{
var item = Items[globalIndex];
return (_drawAll || item.WeaponCategory == LastCategory) && filter.IsContained(item.Name);
}
private void UpdateItem(CharacterWeapon weapon)
{
if (weapon.Equals(_lastWeapon))
return;
_lastWeapon = weapon;
LastItem = Identify(weapon.Set, weapon.Type, weapon.Variant, Slot);
_lastIndex = LastItem.RowId == 0 ? -1 : Items.IndexOf(i => i.Base.RowId == LastItem.RowId);
_lastPreview = _lastIndex >= 0 ? Items[_lastIndex].Name : LastItem.Name.ToString();
}
private void UpdateCategory(WeaponCategory category)
{
if (category == LastCategory)
return;
LastCategory = category;
ResetFilter();
}
private static IReadOnlyList<Item> GetItems(EquipSlot slot)
=> GameData.ItemsBySlot(Dalamud.GameData).TryGetValue(EquipSlot.MainHand, out var list) ? list : Array.Empty<Item>();
}
private static readonly IObjectIdentifier Identifier;
private static readonly IReadOnlyList<ItemCombo> ItemCombos;
private static readonly WeaponCombo MainHandCombo;
private static readonly WeaponCombo OffHandCombo;
private void DrawItemSelector()
{
var combo = ItemCombos[(int)_currentSlotIdx];
var change = combo.Draw(_currentArmor, out var idx);
var newItem = change ? ToArmor(combo.Items[idx], _currentArmor.Stain) : CharacterArmor.Empty;
if (!change && !ReferenceEquals(combo.LastItem, SmallClothes))
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
change = true;
}
if (!change)
return;
_currentArmor = newItem;
UpdateActors();
}
private static CharacterArmor ToArmor(Item item, StainId stain)
{
var (id, _, variant) = item.MainModel;
return new CharacterArmor(id, (byte)variant, stain);
}
private static CharacterWeapon ToWeapon(Item item, StainId stain)
{
var (id, type, variant) = item.MainModel;
return new CharacterWeapon(id, type, variant, stain);
}
private void DrawMainHandSelector(ref CharacterWeapon mainHand)
{
if (!MainHandCombo.Draw(mainHand, out var newIdx, false))
return;
mainHand = ToWeapon(MainHandCombo.Items[newIdx], mainHand.Stain);
foreach (var actor in _actors)
Glamourer.RedrawManager.LoadWeapon(actor, _currentSlot, mainHand);
}
private void DrawOffHandSelector(ref CharacterWeapon offHand, WeaponCategory category)
{
var change = OffHandCombo.Draw(offHand, category, out var newIdx);
var newWeapon = change ? ToWeapon(OffHandCombo.Items[newIdx], offHand.Stain) : CharacterWeapon.Empty;
if (!change && !ReferenceEquals(OffHandCombo.LastItem, SmallClothes))
{
ImGuiUtil.HoverTooltip("Right-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
change = true;
}
if (!change)
return;
offHand = newWeapon;
foreach (var actor in _actors)
Glamourer.RedrawManager.LoadWeapon(actor, _currentSlot, offHand);
}
//private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
//{
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
// ImGui.SameLine();
// var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
//}
//
//private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
//{
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawEquipSlot(slot, equip);
// return ret;
//}
//
//private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
//{
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
// ImGui.SameLine();
// var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
//}
//
//private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
//{
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawWeapon(slot, weapon);
// return ret;
//}
//
//private bool DrawEquip(CharacterEquipment 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(CharacterEquipment equip, ref CharacterEquipMask mask)
//{
// var ret = false;
// if (ImGui.CollapsingHeader("Character Equipment"))
// {
// ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
// ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
// }
//
// return ret;
//}
//
//
//
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new()
{
Name = new SeString("Nothing"),
RowId = 0,
};
private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new()
{
Name = new SeString("Smallclothes (NPC)"),
RowId = 1,
};
private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new()
{
Name = new SeString("Unknown"),
RowId = 2,
};
private static Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
{
return (uint)set switch
{
0 => SmallClothes,
9903 => SmallClothesNpc,
_ => Identifier.Identify(set, weapon, variant, slot) ?? Unknown,
};
}
}

View file

@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment;
public partial class EquipmentDrawer
{
private Race _race;
private Gender _gender;
private CharacterEquip _equip;
private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
private CharacterArmor _currentArmor;
private EquipSlot _currentSlot;
private uint _currentSlotIdx;
static EquipmentDrawer()
{
Stains = GameData.Stains(Dalamud.GameData);
StainCombo = new FilterStainCombo(140);
Identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData);
ItemCombos = EquipSlotExtensions.EqdpSlots.Select(s => new ItemCombo(s)).ToArray();
MainHandCombo = new WeaponCombo(EquipSlot.MainHand);
OffHandCombo = new WeaponCombo(EquipSlot.OffHand);
}
public static void Draw(Customize customize, CharacterEquip equip, ref CharacterWeapon mainHand, ref CharacterWeapon offHand,
IReadOnlyCollection<Actor> actors, bool locked)
{
var d = new EquipmentDrawer()
{
_race = customize.Race,
_gender = customize.Gender,
_equip = equip,
_actors = actors,
};
if (!ImGui.CollapsingHeader("Character Equipment"))
return;
using var disabled = ImRaii.Disabled(locked);
d.DrawInternal(ref mainHand, ref offHand);
}
public static void Draw(Customize customize, CharacterEquip equip, ref CharacterWeapon mainHand, ref CharacterWeapon offHand,
ref ApplicationFlags flags, IReadOnlyCollection<Actor> actors,
bool locked)
{
var d = new EquipmentDrawer()
{
_race = customize.Race,
_gender = customize.Gender,
_equip = equip,
_actors = actors,
};
if (!ImGui.CollapsingHeader("Character Equipment"))
return;
using var disabled = ImRaii.Disabled(locked);
d.DrawInternal(ref mainHand, ref offHand, ref flags);
}
private void DrawInternal(ref CharacterWeapon mainHand, ref CharacterWeapon offHand)
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
using var id = SetSlot(slot);
DrawStainSelector();
ImGui.SameLine();
DrawItemSelector();
}
_currentSlot = EquipSlot.MainHand;
DrawStainSelector();
ImGui.SameLine();
DrawMainHandSelector(ref mainHand);
var offhand = MainHandCombo.LastCategory.AllowsOffHand();
if (offhand != WeaponCategory.Unknown)
{
_currentSlot = EquipSlot.OffHand;
DrawStainSelector();
ImGui.SameLine();
DrawOffHandSelector(ref offHand, offhand);
}
}
private void DrawInternal(ref CharacterWeapon mainHand, ref CharacterWeapon offHand, ref ApplicationFlags flags)
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
using var id = SetSlot(slot);
DrawCheckbox(ref flags);
ImGui.SameLine();
DrawStainSelector();
ImGui.SameLine();
DrawItemSelector();
}
_currentSlot = EquipSlot.MainHand;
DrawCheckbox(ref flags);
ImGui.SameLine();
DrawStainSelector();
ImGui.SameLine();
DrawMainHandSelector(ref mainHand);
var offhand = MainHandCombo.LastCategory.AllowsOffHand();
if (offhand != WeaponCategory.Unknown)
{
_currentSlot = EquipSlot.OffHand;
DrawCheckbox(ref flags);
ImGui.SameLine();
DrawStainSelector();
ImGui.SameLine();
DrawOffHandSelector(ref offHand, offhand);
}
}
private ImRaii.Id SetSlot(EquipSlot slot)
{
_currentSlot = slot;
_currentSlotIdx = slot.ToIndex();
_currentArmor = _equip[slot];
return ImRaii.PushId((int)slot);
}
private void UpdateActors()
{
_equip[_currentSlotIdx] = _currentArmor;
foreach (var actor in _actors)
Glamourer.RedrawManager.ChangeEquip(actor, _currentSlotIdx, _currentArmor);
}
}

View file

@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Stain = Glamourer.Structs.Stain;
namespace Glamourer.Gui.Equipment;
public partial class EquipmentDrawer
{
private static readonly IReadOnlyDictionary<StainId, Stain> Stains;
private static readonly FilterStainCombo StainCombo;
private sealed class FilterStainCombo : FilterComboBase<Stain>
{
private readonly float _comboWidth;
private Vector2 _buttonSize;
public FilterStainCombo(float comboWidth)
: base(Stains.Values.ToArray(), false)
=> _comboWidth = comboWidth;
protected override float GetFilterWidth()
=> _buttonSize.X + ImGui.GetStyle().ScrollbarSize;
protected override void DrawList(float width, float itemHeight)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.WindowPadding, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
_buttonSize = new Vector2(_comboWidth * ImGuiHelpers.GlobalScale, 0);
if (ImGui.GetScrollMaxY() > 0)
_buttonSize.X += ImGui.GetStyle().ScrollbarSize;
base.DrawList(width, itemHeight);
}
protected override string ToString(Stain obj)
=> obj.Name;
protected override bool DrawSelectable(int globalIdx, bool selected)
{
var stain = Items[globalIdx];
// Push the stain color to type and if it is too bright, turn the text color black.
using var colors = ImRaii.PushColor(ImGuiCol.Button, stain.RgbaColor)
.Push(ImGuiCol.Text, 0xFF101010, stain.Intensity > 127)
.Push(ImGuiCol.Border, 0xFF2020D0, selected);
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2f * ImGuiHelpers.GlobalScale, selected);
return ImGui.Button(stain.Name, _buttonSize);
}
}
private void DrawStainSelector()
{
var foundIdx = StainCombo.Items.IndexOf(s => s.RowIndex.Equals(_currentArmor.Stain));
var stain = foundIdx >= 0 ? StainCombo.Items[foundIdx] : default;
using var color = ImRaii.PushColor(ImGuiCol.FrameBg, stain.RgbaColor, foundIdx >= 0);
var change = StainCombo.Draw("##stainSelector", string.Empty, ref foundIdx, ImGui.GetFrameHeight(), ImGui.GetFrameHeight(),
ImGuiComboFlags.NoArrowButton);
if (!change && (byte)_currentArmor.Stain != 0)
{
ImGuiUtil.HoverTooltip($"{stain.Name}\nRight-click to clear.");
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
change = true;
foundIdx = -1;
}
}
if (change)
{
_currentArmor = new CharacterArmor(_currentArmor.Set, _currentArmor.Variant,
foundIdx >= 0 ? StainCombo.Items[foundIdx].RowIndex : Stain.None.RowIndex);
UpdateActors();
}
}
private void DrawCheckbox(ref ApplicationFlags flags)
=> DrawCheckbox("##checkbox", "Enable writing this slot in this save.", ref flags, _currentSlot.ToApplicationFlag());
private static void DrawCheckbox(string label, string tooltip, ref ApplicationFlags flags, ApplicationFlags flag)
{
var tmp = (uint)flags;
if (ImGui.CheckboxFlags(label, ref tmp, (uint)flag))
flags = (ApplicationFlags)tmp;
ImGuiUtil.HoverTooltip(tooltip);
}
}

View file

@ -1,225 +0,0 @@
using System;
using System.Collections.Generic;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Structs;
using ImGuiNET;
using Lumina.Text;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui;
internal partial class Interface
{
//public class EquipmentDrawer
//{
// private static readonly IReadOnlyDictionary<StainId, Stain> Stains;
//
// private Race _race;
// private Gender _gender;
// private CharacterEquip _equip;
// private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
//
// static EquipmentDrawer()
// => Stains = GameData.Stains(Dalamud.GameData);
//
// public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
// {
// var d = new EquipmentDrawer()
// {
// _race = customize.Race,
// _gender = customize.Gender,
// _actors = actors,
// };
//
// if (!ImGui.CollapsingHeader("Character Equipment"))
// return;
//
// using var disabled = ImRaii.Disabled(locked);
// }
//
// private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
// {
// stainCombo.PostPreview = null;
// var found = Stains.TryGetValue(stainIdx, out var stain);
// using var color = ImRaii.PushColor(ImGuiCol.FrameBg, stain.RgbaColor, found);
// var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
// if ()
// if (!change && (byte)stainIdx != 0)
// {
// ImGuiUtil.HoverTooltip("Right-click to clear.");
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
// {
// change = true;
// newStain = Stain.None;
// }
// }
//
// if (!change)
// return false;
//
// if (_player == null)
// return _inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false);
//
// Glamourer.RevertableDesigns.Add(_player);
// newStain.Write(_player.Address, slot);
// return true;
// }
//
// private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item item, EquipSlot slot = EquipSlot.Unknown)
// {
// var currentName = item.Name.ToString();
// var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
// if (!change && !ReferenceEquals(item, SmallClothes))
// {
// ImGuiUtil.HoverTooltip("Right-click to clear.");
// if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
// {
// change = true;
// newItem = Item.Nothing(slot);
// }
// }
//
// if (!change)
// return false;
//
// newItem = new Item(newItem.Base, newItem.Name, slot);
// if (_player == null)
// return _inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false);
//
// Glamourer.RevertableDesigns.Add(_player);
// newItem.Write(_player.Address);
// return true;
// }
//
// private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var tmp = (uint)mask;
// var ret = false;
// if (ImGui.CheckboxFlags($"##flag_{(uint)flag}", ref tmp, (uint)flag) && tmp != (uint)mask)
// {
// mask = (CharacterEquipMask)tmp;
// ret = true;
// }
//
// if (ImGui.IsItemHovered())
// ImGui.SetTooltip("Enable writing this slot in this save.");
// return ret;
// }
//
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothes = new()
// {
// Name = new SeString("Nothing"),
// RowId = 0,
// };
//
// private static readonly Lumina.Excel.GeneratedSheets.Item SmallClothesNpc = new()
// {
// Name = new SeString("Smallclothes (NPC)"),
// RowId = 1,
// };
//
// private static readonly Lumina.Excel.GeneratedSheets.Item Unknown = new()
// {
// Name = new SeString("Unknown"),
// RowId = 2,
// };
//
// private Lumina.Excel.GeneratedSheets.Item Identify(SetId set, WeaponType weapon, ushort variant, EquipSlot slot)
// {
// return (uint)set switch
// {
// 0 => SmallClothes,
// 9903 => SmallClothesNpc,
// _ => _identifier.Identify(set, weapon, variant, slot) ?? Unknown,
// };
// }
//
// private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
// {
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, equip.Stain);
// ImGui.SameLine();
// var item = Identify(equip.Set, new WeaponType(), equip.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
// }
//
// private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawEquipSlot(slot, equip);
// return ret;
// }
//
// private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
// {
// var (equipCombo, stainCombo) = _combos[slot];
//
// var ret = DrawStainSelector(stainCombo, slot, weapon.Stain);
// ImGui.SameLine();
// var item = Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
// ret |= DrawItemSelector(equipCombo, item, slot);
//
// return ret;
// }
//
// private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
// {
// var ret = DrawCheckbox(flag, ref mask);
// ImGui.SameLine();
// ret |= DrawWeapon(slot, weapon);
// return ret;
// }
//
// private bool DrawEquip(CharacterEquipment 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(CharacterEquipment equip, ref CharacterEquipMask mask)
// {
// var ret = false;
// if (ImGui.CollapsingHeader("Character Equipment"))
// {
// ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
// ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
// ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
// }
//
// return ret;
// }
//}
}

View file

@ -3,6 +3,7 @@ using System.Numerics;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
@ -60,6 +61,8 @@ internal partial class Interface
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
_identifier is Actor.SpecialIdentifier);
EquipmentDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, ref _currentSave.Data.MainHand, ref _currentSave.Data.OffHand, _currentData.Objects, _identifier is Actor.SpecialIdentifier);
}
private const uint RedHeaderColor = 0xFF1818C0;

View file

@ -10,19 +10,6 @@ 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;
// }
//
//

View file

@ -17,28 +17,28 @@ namespace Glamourer.Gui;
//
// private static readonly Vector4 GreyVector = new(0.5f, 0.5f, 0.5f, 1);
//
// 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 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)

View file

@ -48,11 +48,33 @@ public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
public CharacterEquip Equip
=> new((CharacterArmor*)Pointer->EquipSlotData);
public unsafe CharacterWeapon MainHand
=> CharacterWeapon.Empty;
public CharacterWeapon MainHand
{
get
{
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
if (child == null)
return CharacterWeapon.Empty;
return *(CharacterWeapon*)(child + 0x8F0);
}
}
public unsafe CharacterWeapon OffHand
=> CharacterWeapon.Empty;
{
get
{
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
if (child == null)
return CharacterWeapon.Empty;
var sibling = (byte*) child->NextSiblingObject;
if (sibling == null)
return CharacterWeapon.Empty;
return *(CharacterWeapon*)(child + 0x8F0);
}
}
public unsafe bool VisorEnabled
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
@ -113,6 +135,7 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
ident = GetIdentifier();
return true;
}
ident = IIdentifier.Invalid;
return false;
}
@ -156,16 +179,16 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
public CharacterEquip Equip
=> new((CharacterArmor*)Pointer->EquipSlotData);
public unsafe CharacterWeapon MainHand
public CharacterWeapon MainHand
{
get => *(CharacterWeapon*)(Address + 0x06C0 + 0x10);
set => *(CharacterWeapon*)(Address + 0x06C0 + 0x10) = value;
get => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel;
set => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel = value;
}
public unsafe CharacterWeapon OffHand
public CharacterWeapon OffHand
{
get => *(CharacterWeapon*)(Address + 0x06C0 + 0x10 + 0x68);
set => *(CharacterWeapon*)(Address + 0x06C0 + 0x10 + 0x68) = value;
get => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel;
set => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel = value;
}
public unsafe bool VisorEnabled

View file

@ -1,13 +1,17 @@
using System;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.State;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Interop;
@ -66,27 +70,30 @@ public unsafe partial class RedrawManager
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
}
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
{
if (!drawObject)
return false;
var slotIndex = slot.ToIndex();
if (slotIndex > 9)
if (slotIdx > 9)
return false;
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIndex, &data) != 0;
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
}
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
=> actor && ChangeEquip(actor.DrawObject, slot, data);
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
=> ChangeEquip(drawObject, slot.ToIndex(), data);
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
}
public unsafe partial class RedrawManager
{
// The character weapon object manipulated is inside the actual character.
public const int CharacterWeaponOffset = 0x6C0;
public static readonly int CharacterWeaponOffset = (int) Marshal.OffsetOf<Character>("DrawData");
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
byte skipGameObject,
@ -219,7 +226,7 @@ public unsafe partial class RedrawManager : IDisposable
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
if (gameObjectCustomize.Equals(customize))
customize.Load(save.Data.Customize);
// Compare game object equip data against draw object equip data for transformations.
// Apply each piece of equip that should be applied if they correspond.
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
@ -262,14 +269,24 @@ public unsafe partial class RedrawManager : IDisposable
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
public bool UpdateCustomize(DrawObject drawObject, Customize customize)
public bool UpdateCustomize(Actor actor, Customize customize)
{
if (!drawObject.Valid)
if (!actor.Valid || !actor.DrawObject.Valid)
return false;
return _changeCustomize(drawObject.Pointer, (byte*)customize.Data, 1);
var d = actor.DrawObject;
if (NeedsRedraw(d.Customize, customize))
{
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, true);
return true;
}
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
}
public static bool NeedsRedraw(Customize lhs, Customize rhs)
=> lhs.Race != rhs.Race || lhs.Gender != rhs.Gender || lhs.Face != rhs.Face || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
public static void SetVisor(Human* data, bool on)
{

View file

@ -57,6 +57,53 @@ public enum ApplicationFlags : uint
Wet = 0x040000,
}
public static class ApplicationFlagExtensions
{
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => ApplicationFlags.MainHand,
EquipSlot.OffHand => ApplicationFlags.OffHand,
EquipSlot.Head => ApplicationFlags.Head,
EquipSlot.Body => ApplicationFlags.Body,
EquipSlot.Hands => ApplicationFlags.Hands,
EquipSlot.Legs => ApplicationFlags.Legs,
EquipSlot.Feet => ApplicationFlags.Feet,
EquipSlot.Ears => ApplicationFlags.Ears,
EquipSlot.Neck => ApplicationFlags.Neck,
EquipSlot.Wrists => ApplicationFlags.Wrist,
EquipSlot.RFinger => ApplicationFlags.RFinger,
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
EquipSlot.LFinger => ApplicationFlags.LFinger,
EquipSlot.HeadBody => ApplicationFlags.Body,
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
EquipSlot.LegsFeet => ApplicationFlags.Legs,
EquipSlot.FullBody => ApplicationFlags.Body,
EquipSlot.BodyHands => ApplicationFlags.Body,
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
EquipSlot.ChestHands => ApplicationFlags.Body,
_ => 0,
};
public static EquipSlot ToSlot(this ApplicationFlags flags)
=> flags switch
{
ApplicationFlags.MainHand => EquipSlot.MainHand,
ApplicationFlags.OffHand => EquipSlot.OffHand,
ApplicationFlags.Head => EquipSlot.Head,
ApplicationFlags.Body => EquipSlot.Body,
ApplicationFlags.Hands => EquipSlot.Hands,
ApplicationFlags.Legs => EquipSlot.Legs,
ApplicationFlags.Feet => EquipSlot.Feet,
ApplicationFlags.Ears => EquipSlot.Ears,
ApplicationFlags.Neck => EquipSlot.Neck,
ApplicationFlags.Wrist => EquipSlot.Wrists,
ApplicationFlags.RFinger => EquipSlot.RFinger,
ApplicationFlags.LFinger => EquipSlot.LFinger,
_ => EquipSlot.Unknown,
};
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CharacterData
{
@ -153,6 +200,8 @@ public struct CharacterData
ModelId = designable.ModelId;
Customize.Load(designable.Customize);
Equipment.Load(designable.Equip);
MainHand = designable.MainHand;
OffHand = designable.OffHand;
Flags = SaveFlags | (designable.VisorEnabled ? ApplicationFlags.Visor : 0) | (designable.WeaponEnabled ? ApplicationFlags.Weapon : 0);
}
}

View file

@ -28,13 +28,6 @@ public unsafe class CurrentDesign : ICharacterData
_drawData = _initialData.Clone();
}
public void SaveCustomization(Customize customize, IReadOnlyCollection<Actor> actors)
{
_drawData.Customize.Load(customize);
foreach (var actor in actors.Where(a => a && a.DrawObject))
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _drawData.Customize);
}
public void Update(Actor actor)
{
if (!actor)