mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
.
This commit is contained in:
parent
145b64bb7a
commit
4cf082aa19
37 changed files with 1362 additions and 1255 deletions
|
|
@ -59,7 +59,7 @@ public partial class CustomizationOptions
|
||||||
|
|
||||||
public partial class CustomizationOptions
|
public partial class CustomizationOptions
|
||||||
{
|
{
|
||||||
internal readonly bool Valid;
|
private readonly bool _valid;
|
||||||
|
|
||||||
public string GetName(CustomName name)
|
public string GetName(CustomName name)
|
||||||
=> _names[(int)name];
|
=> _names[(int)name];
|
||||||
|
|
@ -68,7 +68,7 @@ public partial class CustomizationOptions
|
||||||
{
|
{
|
||||||
var tmp = new TemporaryData(gameData, this);
|
var tmp = new TemporaryData(gameData, this);
|
||||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||||
Valid = tmp.Valid;
|
_valid = tmp.Valid;
|
||||||
SetNames(gameData, tmp);
|
SetNames(gameData, tmp);
|
||||||
foreach (var race in Clans)
|
foreach (var race in Clans)
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +77,6 @@ public partial class CustomizationOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Obtain localized names of customization options and race names from the game data.
|
// Obtain localized names of customization options and race names from the game data.
|
||||||
private readonly string[] _names = new string[Enum.GetValues<CustomName>().Length];
|
private readonly string[] _names = new string[Enum.GetValues<CustomName>().Length];
|
||||||
|
|
||||||
|
|
@ -430,10 +429,14 @@ public partial class CustomizationOptions
|
||||||
|
|
||||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
hairList.Add(hairRow != null
|
if (hairRow == null)
|
||||||
? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
{
|
||||||
(ushort)hairRow.RowId)
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||||
: new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
}
|
||||||
|
else if (_options._icons.IconExists(hairRow.Icon))
|
||||||
|
{
|
||||||
|
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hairList.ToArray();
|
return hairList.ToArray();
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ public unsafe struct Customize
|
||||||
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
||||||
=> Data = data;
|
=> Data = data;
|
||||||
|
|
||||||
|
public Customize(ref Penumbra.GameData.Structs.CustomizeData data)
|
||||||
|
{
|
||||||
|
fixed (Penumbra.GameData.Structs.CustomizeData* ptr = &data)
|
||||||
|
{
|
||||||
|
Data = ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Race Race
|
public Race Race
|
||||||
{
|
{
|
||||||
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
||||||
|
|
@ -96,4 +104,18 @@ public unsafe struct Customize
|
||||||
|
|
||||||
public string WriteBase64()
|
public string WriteBase64()
|
||||||
=> Data->WriteBase64();
|
=> Data->WriteBase64();
|
||||||
|
|
||||||
|
public static CustomizeFlag Compare(Customize lhs, Customize rhs)
|
||||||
|
{
|
||||||
|
CustomizeFlag ret = 0;
|
||||||
|
foreach (var idx in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
var l = lhs[idx];
|
||||||
|
var r = rhs[idx];
|
||||||
|
if (l.Value != r.Value)
|
||||||
|
ret |= idx.ToFlag();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,11 @@ public enum CustomizeFlag : ulong
|
||||||
|
|
||||||
public static class CustomizeFlagExtensions
|
public static class CustomizeFlagExtensions
|
||||||
{
|
{
|
||||||
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
|
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
|
||||||
|
public const CustomizeFlag RedrawRequired = CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face;
|
||||||
|
|
||||||
|
public static bool RequiresRedraw(this CustomizeFlag flags)
|
||||||
|
=> (flags & RedrawRequired) != 0;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
|
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,5 @@ public static class Sigs
|
||||||
{
|
{
|
||||||
public const string ChangeJob = "88 51 ?? 44 3B CA";
|
public const string ChangeJob = "88 51 ?? 44 3B CA";
|
||||||
public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A";
|
public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A";
|
||||||
|
public const string ChangeCustomize = "E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public readonly struct JobGroup
|
||||||
Id = group.RowId;
|
Id = group.RowId;
|
||||||
Name = group.Name.ToString();
|
Name = group.Name.ToString();
|
||||||
|
|
||||||
Debug.Assert(jobs.RowCount < 64);
|
Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount}).");
|
||||||
foreach (var job in jobs)
|
foreach (var job in jobs)
|
||||||
{
|
{
|
||||||
var abbr = job.Abbreviation.ToString();
|
var abbr = job.Abbreviation.ToString();
|
||||||
|
|
@ -29,7 +29,7 @@ public readonly struct JobGroup
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var prop = group.GetType().GetProperty(abbr);
|
var prop = group.GetType().GetProperty(abbr);
|
||||||
Debug.Assert(prop != null);
|
Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property.");
|
||||||
|
|
||||||
if (!(bool)prop.GetValue(group)!)
|
if (!(bool)prop.GetValue(group)!)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,9 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Structs;
|
|
||||||
using ImGuiNET;
|
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
|
|
@ -17,33 +13,76 @@ public unsafe class PenumbraAttach : IDisposable
|
||||||
public const int RequiredPenumbraBreakingVersion = 4;
|
public const int RequiredPenumbraBreakingVersion = 4;
|
||||||
public const int RequiredPenumbraFeatureVersion = 15;
|
public const int RequiredPenumbraFeatureVersion = 15;
|
||||||
|
|
||||||
private EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||||
private EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||||
private ActionSubscriber<GameObject, RedrawType> _redrawSubscriber;
|
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||||
public EventSubscriber<nint, string, nint, nint, nint> CreatingCharacterBase;
|
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||||
public EventSubscriber<nint, string, nint> CreatedCharacterBase;
|
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||||
private FuncSubscriber<int, int> _cutsceneParent;
|
private FuncSubscriber<int, int> _cutsceneParent;
|
||||||
|
|
||||||
private readonly EventSubscriber _initializedEvent;
|
private readonly EventSubscriber _initializedEvent;
|
||||||
private readonly EventSubscriber _disposedEvent;
|
private readonly EventSubscriber _disposedEvent;
|
||||||
public bool Available { get; private set; }
|
public bool Available { get; private set; }
|
||||||
|
|
||||||
public PenumbraAttach(bool attach)
|
public PenumbraAttach()
|
||||||
{
|
{
|
||||||
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
|
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
|
||||||
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
|
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
|
||||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip);
|
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface);
|
||||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick);
|
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface);
|
||||||
CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||||
CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||||
Reattach(attach);
|
Reattach();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Reattach()
|
public event Action<MouseButton, ChangedItemType, uint> Click
|
||||||
=> Reattach(Glamourer.Config.AttachToPenumbra);
|
{
|
||||||
|
add => _clickSubscriber.Event += value;
|
||||||
|
remove => _clickSubscriber.Event -= value;
|
||||||
|
}
|
||||||
|
|
||||||
public void Reattach(bool attach)
|
public event Action<ChangedItemType, uint> Tooltip
|
||||||
|
{
|
||||||
|
add => _tooltipSubscriber.Event += value;
|
||||||
|
remove => _tooltipSubscriber.Event -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public event Action<nint, string, nint, nint, nint> CreatingCharacterBase
|
||||||
|
{
|
||||||
|
add => _creatingCharacterBase.Event += value;
|
||||||
|
remove => _creatingCharacterBase.Event -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<nint, string, nint> CreatedCharacterBase
|
||||||
|
{
|
||||||
|
add => _createdCharacterBase.Event += value;
|
||||||
|
remove => _createdCharacterBase.Event -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
||||||
|
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : Actor.Null;
|
||||||
|
|
||||||
|
public int CutsceneParent(int idx)
|
||||||
|
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
||||||
|
|
||||||
|
public void RedrawObject(Actor actor, RedrawType settings)
|
||||||
|
{
|
||||||
|
if (!actor || !Available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_redrawSubscriber.Invoke(actor.Index, settings);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reattach()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -54,22 +93,19 @@ public unsafe class PenumbraAttach : IDisposable
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||||
|
|
||||||
if (!attach)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_tooltipSubscriber.Enable();
|
_tooltipSubscriber.Enable();
|
||||||
_clickSubscriber.Enable();
|
_clickSubscriber.Enable();
|
||||||
CreatingCharacterBase.Enable();
|
_creatingCharacterBase.Enable();
|
||||||
CreatedCharacterBase.Enable();
|
_createdCharacterBase.Enable();
|
||||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
|
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
|
||||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
|
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
|
||||||
_redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface);
|
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface);
|
||||||
Available = true;
|
Available = true;
|
||||||
PluginLog.Debug("Glamourer attached to Penumbra.");
|
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
PluginLog.Debug($"Could not attach to Penumbra:\n{e}");
|
Glamourer.Log.Debug($"Could not attach to Penumbra:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,8 +113,8 @@ public unsafe class PenumbraAttach : IDisposable
|
||||||
{
|
{
|
||||||
_tooltipSubscriber.Disable();
|
_tooltipSubscriber.Disable();
|
||||||
_clickSubscriber.Disable();
|
_clickSubscriber.Disable();
|
||||||
CreatingCharacterBase.Disable();
|
_creatingCharacterBase.Disable();
|
||||||
CreatedCharacterBase.Disable();
|
_createdCharacterBase.Disable();
|
||||||
if (Available)
|
if (Available)
|
||||||
{
|
{
|
||||||
Available = false;
|
Available = false;
|
||||||
|
|
@ -91,85 +127,66 @@ public unsafe class PenumbraAttach : IDisposable
|
||||||
Unattach();
|
Unattach();
|
||||||
_tooltipSubscriber.Dispose();
|
_tooltipSubscriber.Dispose();
|
||||||
_clickSubscriber.Dispose();
|
_clickSubscriber.Dispose();
|
||||||
CreatingCharacterBase.Dispose();
|
_creatingCharacterBase.Dispose();
|
||||||
CreatedCharacterBase.Dispose();
|
_createdCharacterBase.Dispose();
|
||||||
_initializedEvent.Dispose();
|
_initializedEvent.Dispose();
|
||||||
_disposedEvent.Dispose();
|
_disposedEvent.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
//private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||||
{
|
//{
|
||||||
if (type == ChangedItemType.Item)
|
// if (type == ChangedItemType.Item)
|
||||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
// ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
||||||
|
//{
|
||||||
|
// if (button != MouseButton.Right || type != ChangedItemType.Item)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
|
||||||
|
// var writeItem = new Item2(item, string.Empty);
|
||||||
|
//
|
||||||
|
// UpdateItem(_objects.GPosePlayer, writeItem);
|
||||||
|
// UpdateItem(_objects.Player, writeItem);
|
||||||
|
//}
|
||||||
|
|
||||||
private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
//private static void UpdateItem(Actor actor, Item2 item2)
|
||||||
{
|
//{
|
||||||
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
// if (!actor || !actor.DrawObject)
|
||||||
return;
|
// return;
|
||||||
|
//
|
||||||
var item = (Lumina.Excel.GeneratedSheets.Item)type.GetObject(Dalamud.GameData, id)!;
|
// switch (item2.EquippableTo)
|
||||||
var writeItem = new Item2(item, string.Empty);
|
// {
|
||||||
|
// case EquipSlot.MainHand:
|
||||||
UpdateItem(ObjectManager.GPosePlayer, writeItem);
|
// {
|
||||||
UpdateItem(ObjectManager.Player, writeItem);
|
// var off = item2.HasSubModel
|
||||||
}
|
// ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
||||||
|
// : item2.IsBothHand
|
||||||
private static void UpdateItem(Actor actor, Item2 item2)
|
// ? CharacterWeapon.Empty
|
||||||
{
|
// : actor.OffHand;
|
||||||
if (!actor || !actor.DrawObject)
|
// var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
|
||||||
return;
|
// actor.DrawObject.MainHand.Stain);
|
||||||
|
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||||
switch (item2.EquippableTo)
|
// return;
|
||||||
{
|
// }
|
||||||
case EquipSlot.MainHand:
|
// case EquipSlot.OffHand:
|
||||||
{
|
// {
|
||||||
var off = item2.HasSubModel
|
// var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
|
||||||
? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
// actor.DrawObject.OffHand.Stain);
|
||||||
: item2.IsBothHand
|
// var main = actor.MainHand;
|
||||||
? CharacterWeapon.Empty
|
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||||
: actor.OffHand;
|
// return;
|
||||||
var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.MainHand.Stain);
|
// }
|
||||||
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
// default:
|
||||||
return;
|
// {
|
||||||
}
|
// var current = actor.DrawObject.Equip[item2.EquippableTo];
|
||||||
case EquipSlot.OffHand:
|
// var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
|
||||||
{
|
// Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor);
|
||||||
var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.OffHand.Stain);
|
// return;
|
||||||
var main = actor.MainHand;
|
// }
|
||||||
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
// }
|
||||||
return;
|
//}
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
var current = actor.DrawObject.Equip[item2.EquippableTo];
|
|
||||||
var armor = new CharacterArmor(item2.MainModel.id, (byte)item2.MainModel.variant, current.Stain);
|
|
||||||
Glamourer.RedrawManager.UpdateSlot(actor.DrawObject, item2.EquippableTo, armor);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
|
||||||
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero;
|
|
||||||
|
|
||||||
public int CutsceneParent(int idx)
|
|
||||||
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
|
||||||
|
|
||||||
public void RedrawObject(GameObject? actor, RedrawType settings)
|
|
||||||
{
|
|
||||||
if (actor == null || !Available)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_redrawSubscriber.Invoke(actor, settings);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update objects without triggering PlayerWatcher Events,
|
// Update objects without triggering PlayerWatcher Events,
|
||||||
// then manually redraw using Penumbra.
|
// then manually redraw using Penumbra.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Util;
|
using Glamourer.Util;
|
||||||
using OtterGui.Classes;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -103,6 +102,9 @@ public class DesignBase
|
||||||
return SetArmor(slot, set, variant, name, itemId);
|
return SetArmor(slot, set, variant, name, itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool SetArmor(EquipSlot slot, Item item)
|
||||||
|
=> SetArmor(slot, item.ModelBase, item.Variant, item.Name, item.ItemId);
|
||||||
|
|
||||||
protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force)
|
protected bool UpdateArmor(EquipSlot slot, CharacterArmor armor, bool force)
|
||||||
{
|
{
|
||||||
if (!force)
|
if (!force)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Glamourer.Util;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -62,6 +61,9 @@ public readonly struct Weapon
|
||||||
=> Model.Stain;
|
=> Model.Stain;
|
||||||
|
|
||||||
|
|
||||||
|
public Weapon WithStain(StainId id)
|
||||||
|
=> new(Name, ItemId, Model with { Stain = id }, Type);
|
||||||
|
|
||||||
public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
|
public Weapon(string name, uint itemId, CharacterWeapon weapon, FullEquipType type)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
|
|
@ -81,4 +83,23 @@ public readonly struct Weapon
|
||||||
? new Weapon()
|
? new Weapon()
|
||||||
: new Weapon(name, itemId, weapon, offType);
|
: new Weapon(name, itemId, weapon, offType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Weapon(Lumina.Excel.GeneratedSheets.Item item, bool offhand)
|
||||||
|
{
|
||||||
|
Name = string.Intern(item.Name.ToDalamudString().TextValue);
|
||||||
|
ItemId = item.RowId;
|
||||||
|
Type = item.ToEquipType();
|
||||||
|
var offType = Type.Offhand();
|
||||||
|
var model = offhand && offType == Type ? item.ModelSub : item.ModelMain;
|
||||||
|
Valid = Type.ToSlot() switch
|
||||||
|
{
|
||||||
|
EquipSlot.MainHand when !offhand => true,
|
||||||
|
EquipSlot.MainHand when offhand && offType == Type => true,
|
||||||
|
EquipSlot.OffHand when offhand => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
Model.Set = (SetId)model;
|
||||||
|
Model.Type = (WeaponType)(model >> 16);
|
||||||
|
Model.Variant = (byte)(model >> 32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
88
Glamourer/DrawObjectManager.cs
Normal file
88
Glamourer/DrawObjectManager.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using Glamourer.Api;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Glamourer.State;
|
||||||
|
using Glamourer.Util;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public class DrawObjectManager : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ItemManager _items;
|
||||||
|
private readonly ActorManager _actors;
|
||||||
|
private readonly ActiveDesign.Manager _manager;
|
||||||
|
private readonly Interop.Interop _interop;
|
||||||
|
private readonly PenumbraAttach _penumbra;
|
||||||
|
|
||||||
|
public DrawObjectManager(ItemManager items, ActorManager actors, ActiveDesign.Manager manager, Interop.Interop interop,
|
||||||
|
PenumbraAttach penumbra)
|
||||||
|
{
|
||||||
|
_items = items;
|
||||||
|
_actors = actors;
|
||||||
|
_manager = manager;
|
||||||
|
_interop = interop;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
|
||||||
|
_interop.EquipUpdate += FixEquipment;
|
||||||
|
_penumbra.CreatingCharacterBase += ApplyActiveDesign;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixEquipment(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item)
|
||||||
|
{
|
||||||
|
var customize = drawObject.Customize;
|
||||||
|
var (changed, newArmor) = _items.RestrictedGear.ResolveRestricted(item, slot, customize.Race, customize.Gender);
|
||||||
|
if (!changed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Glamourer.Log.Verbose(
|
||||||
|
$"Invalid armor {item.Set.Value}-{item.Variant} for {customize.Gender} {customize.Race} changed to {newArmor.Set.Value}-{newArmor.Variant}.");
|
||||||
|
item = newArmor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ApplyActiveDesign(nint gameObjectPtr, string collectionName, nint modelIdPtr, nint customizePtr, nint equipDataPtr)
|
||||||
|
{
|
||||||
|
var gameObject = (Actor)gameObjectPtr;
|
||||||
|
ref var modelId = ref *(uint*)modelIdPtr;
|
||||||
|
// Do not apply anything if the game object model id does not correspond to the draw object model id.
|
||||||
|
// This is the case if the actor is transformed to a different creature.
|
||||||
|
if (gameObject.ModelId != modelId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var identifier = _actors.FromObject((GameObject*)gameObjectPtr, out _, true, true);
|
||||||
|
if (!identifier.IsValid || !_manager.TryGetValue(identifier, out var design))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Compare game object customize data against draw object customize data for transformations.
|
||||||
|
// Apply customization if they correspond and there is customization to apply.
|
||||||
|
var gameObjectCustomize = gameObject.Customize;
|
||||||
|
var customize = new Customize((CustomizeData*)customizePtr);
|
||||||
|
if (gameObjectCustomize.Equals(customize))
|
||||||
|
customize.Load(design.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 = gameObject.Equip;
|
||||||
|
var equip = new CharacterEquip((CharacterArmor*)equipDataPtr);
|
||||||
|
if (gameObjectEquip.Equals(equip))
|
||||||
|
{
|
||||||
|
var saveEquip = design.Equipment();
|
||||||
|
foreach (var slot in EquipSlotExtensions.EquipmentSlots)
|
||||||
|
{
|
||||||
|
(_, equip[slot]) =
|
||||||
|
Glamourer.Items.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_penumbra.CreatingCharacterBase -= ApplyActiveDesign;
|
||||||
|
_interop.EquipUpdate -= FixEquipment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using Glamourer.Api;
|
using Glamourer.Api;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
|
|
@ -13,7 +12,6 @@ using Glamourer.Gui;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Util;
|
using Glamourer.Util;
|
||||||
using ImGuizmoNET;
|
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
|
|
@ -39,18 +37,21 @@ public class Glamourer : IDalamudPlugin
|
||||||
public static GlamourerConfig Config = null!;
|
public static GlamourerConfig Config = null!;
|
||||||
public static Logger Log = null!;
|
public static Logger Log = null!;
|
||||||
public static ActorManager Actors = null!;
|
public static ActorManager Actors = null!;
|
||||||
public static PenumbraAttach Penumbra = null!;
|
|
||||||
public static ICustomizationManager Customization = null!;
|
public static ICustomizationManager Customization = null!;
|
||||||
public static RedrawManager RedrawManager = null!;
|
public static RedrawManager RedrawManager = null!;
|
||||||
public static ItemManager Items = null!;
|
public static ItemManager Items = null!;
|
||||||
public readonly FixedDesigns FixedDesigns;
|
public readonly FixedDesigns FixedDesigns;
|
||||||
public readonly CurrentManipulations CurrentManipulations;
|
|
||||||
|
|
||||||
private readonly Design.Manager _designManager;
|
private readonly Interop.Interop _interop;
|
||||||
private readonly DesignFileSystem _fileSystem;
|
private readonly PenumbraAttach _penumbra;
|
||||||
private readonly FrameworkManager _framework;
|
private readonly ObjectManager _objectManager;
|
||||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
private readonly Design.Manager _designManager;
|
||||||
private readonly Interface _interface;
|
private readonly ActiveDesign.Manager _stateManager;
|
||||||
|
private readonly DrawObjectManager _drawObjectManager;
|
||||||
|
private readonly DesignFileSystem _fileSystem;
|
||||||
|
private readonly FrameworkManager _framework;
|
||||||
|
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||||
|
private readonly Interface _interface;
|
||||||
|
|
||||||
//public readonly DesignManager Designs;
|
//public readonly DesignManager Designs;
|
||||||
|
|
||||||
|
|
@ -65,24 +66,28 @@ public class Glamourer : IDalamudPlugin
|
||||||
Log = new Logger();
|
Log = new Logger();
|
||||||
|
|
||||||
_framework = new FrameworkManager(Dalamud.Framework, Log);
|
_framework = new FrameworkManager(Dalamud.Framework, Log);
|
||||||
|
_interop = new Interop.Interop();
|
||||||
|
_penumbra = new PenumbraAttach();
|
||||||
|
|
||||||
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
|
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
|
||||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||||
|
|
||||||
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
|
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
|
||||||
Config = GlamourerConfig.Load();
|
Config = GlamourerConfig.Load();
|
||||||
|
|
||||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
_objectManager = new ObjectManager(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
|
||||||
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui,
|
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.Framework, Dalamud.GameData,
|
||||||
i => (short)Penumbra.CutsceneParent(i));
|
Dalamud.GameGui, i => (short)_penumbra.CutsceneParent(i));
|
||||||
|
|
||||||
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
|
|
||||||
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
|
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
|
||||||
FixedDesigns = new FixedDesigns();
|
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
|
||||||
CurrentManipulations = new CurrentManipulations();
|
FixedDesigns = new FixedDesigns();
|
||||||
|
_stateManager = new ActiveDesign.Manager(Actors, _objectManager, _interop, _penumbra);
|
||||||
|
|
||||||
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
//GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
||||||
RedrawManager = new RedrawManager(FixedDesigns, CurrentManipulations);
|
RedrawManager = new RedrawManager(FixedDesigns, _stateManager);
|
||||||
|
_drawObjectManager = new DrawObjectManager(Items, Actors, _stateManager, _interop, _penumbra);
|
||||||
|
|
||||||
Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer)
|
Dalamud.Commands.AddHandler(MainCommandString, new CommandInfo(OnGlamourer)
|
||||||
{
|
{
|
||||||
|
|
@ -93,7 +98,7 @@ public class Glamourer : IDalamudPlugin
|
||||||
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
||||||
});
|
});
|
||||||
|
|
||||||
_interface = new Interface(Items, CurrentManipulations, _designManager, _fileSystem);
|
_interface = new Interface(Dalamud.PluginInterface, Items, _stateManager, _designManager, _fileSystem, _objectManager);
|
||||||
_windowSystem.AddWindow(_interface);
|
_windowSystem.AddWindow(_interface);
|
||||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||||
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
||||||
|
|
@ -108,13 +113,15 @@ public class Glamourer : IDalamudPlugin
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_drawObjectManager?.Dispose();
|
||||||
RedrawManager?.Dispose();
|
RedrawManager?.Dispose();
|
||||||
Penumbra?.Dispose();
|
|
||||||
if (_windowSystem != null)
|
if (_windowSystem != null)
|
||||||
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||||
_interface?.Dispose();
|
_interface?.Dispose();
|
||||||
_fileSystem?.Dispose();
|
_fileSystem?.Dispose();
|
||||||
//GlamourerIpc.Dispose();
|
//GlamourerIpc.Dispose();
|
||||||
|
_interop?.Dispose();
|
||||||
|
_penumbra?.Dispose();
|
||||||
_framework?.Dispose();
|
_framework?.Dispose();
|
||||||
Items?.Dispose();
|
Items?.Dispose();
|
||||||
Dalamud.Commands.RemoveHandler(ApplyCommandString);
|
Dalamud.Commands.RemoveHandler(ApplyCommandString);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using System.Numerics;
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private const string ColorPickerPopupName = "ColorPicker";
|
private const string ColorPickerPopupName = "ColorPicker";
|
||||||
|
|
||||||
|
|
@ -26,7 +27,6 @@ internal partial class CustomizationDrawer
|
||||||
DataInputInt(current);
|
DataInputInt(current);
|
||||||
ImGui.TextUnformatted(_currentOption);
|
ImGui.TextUnformatted(_currentOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawColorPickerPopup();
|
DrawColorPickerPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,10 +56,9 @@ internal partial class CustomizationDrawer
|
||||||
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||||
if (!_set.IsAvailable(index) || current >= 0)
|
if (_set.IsAvailable(index) && current < 0)
|
||||||
return (current, custom!.Value);
|
throw new Exception($"Read invalid customization value {_customize[index]} for {index}.");
|
||||||
|
|
||||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}.");
|
return (current, custom!.Value);
|
||||||
return (0, _set.Data(index, 0));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,12 +6,12 @@ using Glamourer.Util;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Api.Enums;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private void DrawRaceGenderSelector()
|
private void DrawRaceGenderSelector()
|
||||||
{
|
{
|
||||||
|
|
@ -26,21 +26,20 @@ internal partial class CustomizationDrawer
|
||||||
|
|
||||||
private void DrawGenderSelector()
|
private void DrawGenderSelector()
|
||||||
{
|
{
|
||||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||||
var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
var icon = _customize.Gender switch
|
||||||
var restricted = _customize.Race == Race.Hrothgar;
|
{
|
||||||
if (restricted)
|
Gender.Male when _customize.Race is Race.Hrothgar => FontAwesomeIcon.MarsDouble,
|
||||||
icon = FontAwesomeIcon.MarsDouble;
|
Gender.Male => FontAwesomeIcon.Mars,
|
||||||
|
Gender.Female => FontAwesomeIcon.Venus,
|
||||||
|
|
||||||
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted, true))
|
_ => throw new Exception($"Gender value {_customize.Gender} is not a valid gender for a design."),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, icon == FontAwesomeIcon.MarsDouble, true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var gender = _customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
|
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
|
||||||
if (!_customize.ChangeGender(_equip, gender))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var actor in _actors.Where(a => a))
|
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRaceCombo()
|
private void DrawRaceCombo()
|
||||||
|
|
@ -52,12 +51,8 @@ internal partial class CustomizationDrawer
|
||||||
|
|
||||||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||||
{
|
{
|
||||||
if (!ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)
|
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan))
|
||||||
|| !_customize.ChangeRace(_equip, subRace))
|
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace);
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Api.Enums;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private const string IconSelectorPopup = "Style Picker";
|
private const string IconSelectorPopup = "Style Picker";
|
||||||
|
|
||||||
|
|
@ -40,22 +38,23 @@ internal partial class CustomizationDrawer
|
||||||
FaceInputInt(current);
|
FaceInputInt(current);
|
||||||
else
|
else
|
||||||
DataInputInt(current);
|
DataInputInt(current);
|
||||||
|
|
||||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawIconPickerPopup();
|
DrawIconPickerPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFace(CustomizeData data)
|
private bool UpdateFace(CustomizeData data)
|
||||||
{
|
{
|
||||||
// Hrothgar Hack
|
// Hrothgar Hack
|
||||||
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||||
if (_customize.Face == value)
|
if (_customize.Face == value)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
_customize.Face = value;
|
_customize.Face = value;
|
||||||
foreach (var actor in _actors)
|
Changed |= CustomizeFlag.Face;
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FaceInputInt(int currentIndex)
|
private void FaceInputInt(int currentIndex)
|
||||||
|
|
@ -78,6 +77,7 @@ internal partial class CustomizationDrawer
|
||||||
if (!popup)
|
if (!popup)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var ret = false;
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
|
@ -107,4 +107,45 @@ internal partial class CustomizationDrawer
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Only used for facial features, so fixed ID.
|
||||||
|
private void DrawMultiIconSelector()
|
||||||
|
{
|
||||||
|
using var bigGroup = ImRaii.Group();
|
||||||
|
DrawMultiIcons();
|
||||||
|
ImGui.SameLine();
|
||||||
|
using var group = ImRaii.Group();
|
||||||
|
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
|
||||||
|
|
||||||
|
_currentCount = 256;
|
||||||
|
PercentageInputInt();
|
||||||
|
|
||||||
|
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiIcons()
|
||||||
|
{
|
||||||
|
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||||
|
using var _ = ImRaii.Group();
|
||||||
|
foreach (var (featureIdx, idx) in options.WithIndex())
|
||||||
|
{
|
||||||
|
using var id = SetId(featureIdx);
|
||||||
|
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||||
|
var feature = _set.Data(featureIdx, 0, _customize.Face);
|
||||||
|
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
||||||
|
? _legacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||||
|
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||||
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||||
|
Vector4.Zero, enabled ? Vector4.One : _redTint))
|
||||||
|
{
|
||||||
|
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
||||||
|
Changed |= _currentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
|
if (idx % 4 != 3)
|
||||||
|
ImGui.SameLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Reflection;
|
|
||||||
using Glamourer.Customization;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using ImGuiNET;
|
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Raii;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
|
||||||
{
|
|
||||||
private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f);
|
|
||||||
private static readonly ImGuiScene.TextureWrap? LegacyTattoo;
|
|
||||||
|
|
||||||
private readonly Vector2 _iconSize;
|
|
||||||
private readonly Vector2 _framedIconSize;
|
|
||||||
private readonly float _inputIntSize;
|
|
||||||
private readonly float _comboSelectorSize;
|
|
||||||
private readonly float _raceSelectorWidth;
|
|
||||||
|
|
||||||
private Customize _customize;
|
|
||||||
private CharacterEquip _equip;
|
|
||||||
private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
|
|
||||||
private CustomizationSet _set = null!;
|
|
||||||
|
|
||||||
private CustomizationDrawer()
|
|
||||||
{
|
|
||||||
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
|
|
||||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
|
||||||
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
|
||||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
|
||||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
|
||||||
}
|
|
||||||
|
|
||||||
static CustomizationDrawer()
|
|
||||||
=> LegacyTattoo = GetLegacyTattooIcon();
|
|
||||||
|
|
||||||
public static void Dispose()
|
|
||||||
=> LegacyTattoo?.Dispose();
|
|
||||||
|
|
||||||
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
|
|
||||||
{
|
|
||||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
|
||||||
if (resource == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var rawImage = new byte[resource.Length];
|
|
||||||
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
|
||||||
return length == resource.Length
|
|
||||||
? Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
|
|
||||||
{
|
|
||||||
var d = new CustomizationDrawer()
|
|
||||||
{
|
|
||||||
_customize = customize,
|
|
||||||
_equip = equip,
|
|
||||||
_actors = actors,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
|
||||||
return;
|
|
||||||
|
|
||||||
using var disabled = ImRaii.Disabled(locked);
|
|
||||||
|
|
||||||
d.DrawRaceGenderSelector();
|
|
||||||
|
|
||||||
d._set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
|
||||||
|
|
||||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.Percentage])
|
|
||||||
d.PercentageSelector(id);
|
|
||||||
|
|
||||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.IconSelector], d.DrawIconSelector, ImGui.SameLine);
|
|
||||||
|
|
||||||
d.DrawMultiIconSelector();
|
|
||||||
|
|
||||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.ListSelector])
|
|
||||||
d.DrawListSelector(id);
|
|
||||||
|
|
||||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
|
|
||||||
|
|
||||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox,
|
|
||||||
() => ImGui.SameLine(d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Draw(Customize customize, IReadOnlyCollection<Actor> actors, bool locked = false)
|
|
||||||
=> Draw(customize, CharacterEquip.Null, actors, locked);
|
|
||||||
|
|
||||||
public static void Draw(Customize customize, CharacterEquip equip, bool locked = false)
|
|
||||||
=> Draw(customize, equip, Array.Empty<Actor>(), locked);
|
|
||||||
|
|
||||||
public static void Draw(Customize customize, bool locked = false)
|
|
||||||
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
|
|
||||||
|
|
||||||
// Set state for drawing of current customization.
|
|
||||||
private CustomizeIndex _currentIndex;
|
|
||||||
private CustomizeValue _currentByte = CustomizeValue.Zero;
|
|
||||||
private int _currentCount;
|
|
||||||
private string _currentOption = string.Empty;
|
|
||||||
|
|
||||||
// Prepare a new customization option.
|
|
||||||
private ImRaii.Id SetId(CustomizeIndex index)
|
|
||||||
{
|
|
||||||
_currentIndex = index;
|
|
||||||
_currentByte = _customize[index];
|
|
||||||
_currentCount = _set.Count(index, _customize.Face);
|
|
||||||
_currentOption = _set.Option(index);
|
|
||||||
return ImRaii.PushId((int)index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the current id with a value,
|
|
||||||
// also update actors if any.
|
|
||||||
private void UpdateValue(CustomizeValue value)
|
|
||||||
{
|
|
||||||
if (_customize[_currentIndex] == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_customize[_currentIndex] = value;
|
|
||||||
UpdateActors();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update all relevant Actors by calling the UpdateCustomize game function.
|
|
||||||
private void UpdateActors()
|
|
||||||
{
|
|
||||||
foreach (var actor in _actors)
|
|
||||||
Glamourer.RedrawManager.UpdateCustomize(actor, _customize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
using Glamourer.Customization;
|
|
||||||
using ImGuiNET;
|
|
||||||
using OtterGui;
|
|
||||||
using OtterGui.Raii;
|
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
|
||||||
{
|
|
||||||
// Only used for facial features, so fixed ID.
|
|
||||||
private void DrawMultiIconSelector()
|
|
||||||
{
|
|
||||||
using var bigGroup = ImRaii.Group();
|
|
||||||
DrawMultiIcons();
|
|
||||||
ImGui.SameLine();
|
|
||||||
using var group = ImRaii.Group();
|
|
||||||
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
|
|
||||||
|
|
||||||
_currentCount = 256;
|
|
||||||
PercentageInputInt();
|
|
||||||
|
|
||||||
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawMultiIcons()
|
|
||||||
{
|
|
||||||
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
|
||||||
using var _ = ImRaii.Group();
|
|
||||||
foreach (var (featureIdx, idx) in options.WithIndex())
|
|
||||||
{
|
|
||||||
using var id = SetId(featureIdx);
|
|
||||||
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
|
||||||
var feature = _set.Data(featureIdx, 0, _customize.Face);
|
|
||||||
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
|
||||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
|
||||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
|
||||||
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
|
||||||
{
|
|
||||||
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
|
||||||
UpdateActors();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
|
||||||
if (idx % 4 != 3)
|
|
||||||
ImGui.SameLine();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,44 +6,8 @@ using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
public partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private void DrawListSelector(CustomizeIndex index)
|
|
||||||
{
|
|
||||||
using var _ = SetId(index);
|
|
||||||
using var bigGroup = ImRaii.Group();
|
|
||||||
|
|
||||||
ListCombo();
|
|
||||||
ImGui.SameLine();
|
|
||||||
ListInputInt();
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.TextUnformatted(_currentOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ListCombo()
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
|
||||||
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
|
|
||||||
|
|
||||||
if (!combo)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
|
||||||
{
|
|
||||||
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
|
||||||
UpdateValue((CustomizeValue)i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ListInputInt()
|
|
||||||
{
|
|
||||||
var tmp = _currentByte.Value + 1;
|
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
|
||||||
UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PercentageSelector(CustomizeIndex index)
|
private void PercentageSelector(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
using var _ = SetId(index);
|
using var _ = SetId(index);
|
||||||
|
|
@ -73,26 +37,6 @@ internal partial class CustomizationDrawer
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Draw one of the four checkboxes for single bool customization options.
|
|
||||||
private void Checkbox(string label, bool current, Action<bool> setter)
|
|
||||||
{
|
|
||||||
var tmp = current;
|
|
||||||
if (ImGui.Checkbox(label, ref tmp) && tmp != current)
|
|
||||||
{
|
|
||||||
setter(tmp);
|
|
||||||
UpdateActors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a customize checkbox.
|
|
||||||
private void DrawCheckbox(CustomizeIndex idx)
|
|
||||||
{
|
|
||||||
using var id = SetId(idx);
|
|
||||||
Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero,
|
|
||||||
b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Integral input for an icon- or color based item.
|
// Integral input for an icon- or color based item.
|
||||||
private void DataInputInt(int currentIndex)
|
private void DataInputInt(int currentIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -107,4 +51,52 @@ internal partial class CustomizationDrawer
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawListSelector(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
using var _ = SetId(index);
|
||||||
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
|
ListCombo();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ListInputInt();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.TextUnformatted(_currentOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListCombo()
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||||
|
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
|
||||||
|
|
||||||
|
if (!combo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
||||||
|
UpdateValue((CustomizeValue)i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListInputInt()
|
||||||
|
{
|
||||||
|
var tmp = _currentByte.Value + 1;
|
||||||
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
|
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
||||||
|
UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||||
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw a customize checkbox.
|
||||||
|
private void DrawCheckbox(CustomizeIndex idx)
|
||||||
|
{
|
||||||
|
using var id = SetId(idx);
|
||||||
|
var tmp = _currentByte != CustomizeValue.Zero;
|
||||||
|
if (ImGui.Checkbox(_currentOption, ref tmp))
|
||||||
|
{
|
||||||
|
_customize.Set(idx, tmp ? CustomizeValue.Max : CustomizeValue.Zero);
|
||||||
|
Changed |= _currentFlag;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
172
Glamourer/Gui/Customization/CustomizationDrawer.cs
Normal file
172
Glamourer/Gui/Customization/CustomizationDrawer.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
|
public partial class CustomizationDrawer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||||
|
private readonly ImGuiScene.TextureWrap? _legacyTattoo;
|
||||||
|
|
||||||
|
private bool _withFlags = false;
|
||||||
|
private Exception? _terminate = null;
|
||||||
|
|
||||||
|
private readonly unsafe CustomizeData* _data;
|
||||||
|
|
||||||
|
private Customize _customize;
|
||||||
|
private CustomizationSet _set = null!;
|
||||||
|
|
||||||
|
public unsafe CustomizeData CustomizeData
|
||||||
|
=> *_data;
|
||||||
|
|
||||||
|
public CustomizeFlag CurrentFlag { get; private set; }
|
||||||
|
public CustomizeFlag Changed { get; private set; }
|
||||||
|
|
||||||
|
public bool RequiresRedraw
|
||||||
|
=> Changed.RequiresRedraw();
|
||||||
|
|
||||||
|
private bool _locked = false;
|
||||||
|
private Vector2 _iconSize;
|
||||||
|
private Vector2 _framedIconSize;
|
||||||
|
private float _inputIntSize;
|
||||||
|
private float _comboSelectorSize;
|
||||||
|
private float _raceSelectorWidth;
|
||||||
|
|
||||||
|
public CustomizationDrawer(DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
_legacyTattoo = GetLegacyTattooIcon(pi);
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
_data = (CustomizeData*)Marshal.AllocHGlobal(sizeof(CustomizeData));
|
||||||
|
*_data = Customize.Default;
|
||||||
|
_customize = new Customize(_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_legacyTattoo?.Dispose();
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal((nint)_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(Customize current, bool locked)
|
||||||
|
{
|
||||||
|
_withFlags = false;
|
||||||
|
CurrentFlag = CustomizeFlagExtensions.All;
|
||||||
|
Init(current, locked);
|
||||||
|
return DrawInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(Customize current, CustomizeFlag currentFlags, bool locked)
|
||||||
|
{
|
||||||
|
_withFlags = true;
|
||||||
|
CurrentFlag = currentFlags;
|
||||||
|
Init(current, locked);
|
||||||
|
return DrawInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Init(Customize current, bool locked)
|
||||||
|
{
|
||||||
|
UpdateSizes();
|
||||||
|
_terminate = null;
|
||||||
|
Changed = 0;
|
||||||
|
_customize.Load(current);
|
||||||
|
_locked = locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set state for drawing of current customization.
|
||||||
|
private CustomizeIndex _currentIndex;
|
||||||
|
private CustomizeFlag _currentFlag;
|
||||||
|
private CustomizeValue _currentByte = CustomizeValue.Zero;
|
||||||
|
private int _currentCount;
|
||||||
|
private string _currentOption = string.Empty;
|
||||||
|
|
||||||
|
// Prepare a new customization option.
|
||||||
|
private ImRaii.Id SetId(CustomizeIndex index)
|
||||||
|
{
|
||||||
|
_currentIndex = index;
|
||||||
|
_currentFlag = index.ToFlag();
|
||||||
|
_currentByte = _customize[index];
|
||||||
|
_currentCount = _set.Count(index, _customize.Face);
|
||||||
|
_currentOption = _set.Option(index);
|
||||||
|
return ImRaii.PushId((int)index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the current id with a new value.
|
||||||
|
private void UpdateValue(CustomizeValue value)
|
||||||
|
{
|
||||||
|
if (_currentByte == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_customize[_currentIndex] = value;
|
||||||
|
Changed |= _currentFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DrawInternal()
|
||||||
|
{
|
||||||
|
using var disabled = ImRaii.Disabled(_locked);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DrawRaceGenderSelector();
|
||||||
|
_set = Glamourer.Customization.GetList(_customize.Clan, _customize.Gender);
|
||||||
|
|
||||||
|
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
|
||||||
|
PercentageSelector(id);
|
||||||
|
|
||||||
|
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
|
||||||
|
|
||||||
|
DrawMultiIconSelector();
|
||||||
|
|
||||||
|
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector])
|
||||||
|
DrawListSelector(id);
|
||||||
|
|
||||||
|
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
|
||||||
|
|
||||||
|
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox,
|
||||||
|
() => ImGui.SameLine(_inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X));
|
||||||
|
return Changed != 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_terminate = ex;
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF4040FF);
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGuiUtil.TextWrapped(_terminate.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateSizes()
|
||||||
|
{
|
||||||
|
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
|
||||||
|
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||||
|
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||||
|
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||||
|
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon(DalamudPluginInterface pi)
|
||||||
|
{
|
||||||
|
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||||
|
if (resource == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var rawImage = new byte[resource.Length];
|
||||||
|
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
||||||
|
return length == resource.Length
|
||||||
|
? pi.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Utility;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Util;
|
using Glamourer.Util;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -13,26 +15,55 @@ using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Equipment;
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
public partial class EquipmentDrawer
|
public class EquipmentDrawer
|
||||||
{
|
{
|
||||||
private readonly FilterComboColors _stainCombo;
|
private readonly ItemManager _items;
|
||||||
private readonly StainData _stainData;
|
private readonly FilterComboColors _stainCombo;
|
||||||
private readonly ItemCombo[] _itemCombo;
|
private readonly StainData _stainData;
|
||||||
|
private readonly ItemCombo[] _itemCombo;
|
||||||
|
private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo;
|
||||||
|
|
||||||
public EquipmentDrawer(ItemManager items)
|
public EquipmentDrawer(ItemManager items)
|
||||||
{
|
{
|
||||||
|
_items = items;
|
||||||
_stainData = items.Stains;
|
_stainData = items.Stains;
|
||||||
_stainCombo = new FilterComboColors(140,
|
_stainCombo = new FilterComboColors(140,
|
||||||
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
_stainData.Data.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))));
|
||||||
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(items, e)).ToArray();
|
_itemCombo = EquipSlotExtensions.EqdpSlots.Select(e => new ItemCombo(items, e)).ToArray();
|
||||||
|
_weaponCombo = new Dictionary<(FullEquipType, EquipSlot), WeaponCombo>(FullEquipTypeExtensions.WeaponTypes.Count * 2);
|
||||||
|
foreach (var type in Enum.GetValues<FullEquipType>())
|
||||||
|
{
|
||||||
|
if (type.ToSlot() is EquipSlot.MainHand)
|
||||||
|
_weaponCombo.TryAdd((type, EquipSlot.MainHand), new WeaponCombo(items, type, EquipSlot.MainHand));
|
||||||
|
else if (type.ToSlot() is EquipSlot.OffHand)
|
||||||
|
_weaponCombo.TryAdd((type, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
|
||||||
|
|
||||||
|
var offhand = type.Offhand();
|
||||||
|
if (offhand is not FullEquipType.Unknown && !_weaponCombo.ContainsKey((offhand, EquipSlot.OffHand)))
|
||||||
|
_weaponCombo.TryAdd((offhand, EquipSlot.OffHand), new WeaponCombo(items, type, EquipSlot.OffHand));
|
||||||
|
}
|
||||||
|
|
||||||
|
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawArmor(DesignBase design, EquipSlot slot, out Item armor)
|
private string VerifyRestrictedGear(Item gear, EquipSlot slot, Gender gender, Race race)
|
||||||
{
|
{
|
||||||
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
|
if (slot.IsAccessory())
|
||||||
|
return gear.Name;
|
||||||
|
|
||||||
|
var (changed, _) = _items.RestrictedGear.ResolveRestricted(gear.Model, slot, race, gender);
|
||||||
|
if (changed)
|
||||||
|
return gear.Name + " (Restricted)";
|
||||||
|
|
||||||
|
return gear.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawArmor(Item current, EquipSlot slot, out Item armor, Gender gender = Gender.Unknown, Race race = Race.Unknown)
|
||||||
|
{
|
||||||
|
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(DrawArmor)} on {slot}.");
|
||||||
var combo = _itemCombo[slot.ToIndex()];
|
var combo = _itemCombo[slot.ToIndex()];
|
||||||
armor = design.Armor(slot);
|
armor = current;
|
||||||
var change = combo.Draw(armor.Name, armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
|
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
|
||||||
if (armor.ModelBase.Value != 0)
|
if (armor.ModelBase.Value != 0)
|
||||||
{
|
{
|
||||||
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||||
|
|
@ -41,6 +72,10 @@ public partial class EquipmentDrawer
|
||||||
change = true;
|
change = true;
|
||||||
armor = ItemManager.NothingItem(slot);
|
armor = ItemManager.NothingItem(slot);
|
||||||
}
|
}
|
||||||
|
else if (change)
|
||||||
|
{
|
||||||
|
armor = combo.CurrentSelection.WithStain(armor.Stain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (change)
|
else if (change)
|
||||||
{
|
{
|
||||||
|
|
@ -50,14 +85,77 @@ public partial class EquipmentDrawer
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawStain(DesignBase design, EquipSlot slot, out Stain stain)
|
public bool DrawStain(StainId current, EquipSlot slot, out Stain stain)
|
||||||
{
|
{
|
||||||
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
|
var found = _stainData.TryGetValue(current, out stain);
|
||||||
var armor = design.Armor(slot);
|
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found))
|
||||||
var found = _stainData.TryGetValue(armor.Stain, out stain);
|
|
||||||
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, found))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain);
|
return _stainData.TryGetValue(_stainCombo.CurrentSelection.Key, out stain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DrawMainhand(Weapon current, bool drawAll, out Weapon weapon)
|
||||||
|
{
|
||||||
|
weapon = current;
|
||||||
|
if (!_weaponCombo.TryGetValue((drawAll ? FullEquipType.Unknown : current.Type, EquipSlot.MainHand), out var combo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
weapon = combo.CurrentSelection.WithStain(current.Stain);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawOffhand(Weapon current, FullEquipType mainType, out Weapon weapon)
|
||||||
|
{
|
||||||
|
weapon = current;
|
||||||
|
var offType = mainType.Offhand();
|
||||||
|
if (offType == FullEquipType.Unknown)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_weaponCombo.TryGetValue((offType, EquipSlot.OffHand), out var combo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var change = combo.Draw(weapon.Name, weapon.ItemId, 320 * ImGuiHelpers.GlobalScale);
|
||||||
|
if (offType.ToSlot() is EquipSlot.OffHand && weapon.ModelBase.Value != 0)
|
||||||
|
{
|
||||||
|
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
|
{
|
||||||
|
change = true;
|
||||||
|
weapon = ItemManager.NothingItem(offType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (change)
|
||||||
|
{
|
||||||
|
weapon = combo.CurrentSelection.WithStain(current.Stain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawApply(Design design, EquipSlot slot, out bool enabled)
|
||||||
|
{
|
||||||
|
enabled = design.DoApplyEquip(slot);
|
||||||
|
var tmp = enabled;
|
||||||
|
if (!ImGuiUtil.Checkbox($"##apply{slot}", string.Empty, enabled, v => tmp = v))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var change = enabled != tmp;
|
||||||
|
enabled = tmp;
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DrawApplyStain(Design design, EquipSlot slot, out bool enabled)
|
||||||
|
{
|
||||||
|
enabled = design.DoApplyStain(slot);
|
||||||
|
var tmp = enabled;
|
||||||
|
if (!ImGuiUtil.Checkbox($"##applyStain{slot}", string.Empty, enabled, v => tmp = v))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var change = enabled != tmp;
|
||||||
|
enabled = tmp;
|
||||||
|
return change;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Interface;
|
|
||||||
using Glamourer.Structs;
|
|
||||||
using Glamourer.Util;
|
using Glamourer.Util;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
@ -12,52 +8,53 @@ using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
using Item = Glamourer.Designs.Item;
|
using Item = Glamourer.Designs.Item;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Equipment;
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
public sealed class ItemCombo : FilterComboCache<Item>
|
public sealed class ItemCombo : FilterComboCache<Item>
|
||||||
{
|
{
|
||||||
public readonly string Label;
|
public readonly string Label;
|
||||||
public readonly EquipSlot Slot;
|
private uint _currentItem;
|
||||||
|
|
||||||
private uint _lastItemId;
|
|
||||||
private uint _previewId;
|
|
||||||
|
|
||||||
public ItemCombo(ItemManager items, EquipSlot slot)
|
public ItemCombo(ItemManager items, EquipSlot slot)
|
||||||
: base(GetItems(items, slot))
|
: base(() => GetItems(items, slot))
|
||||||
{
|
{
|
||||||
Label = GetLabel(slot);
|
Label = GetLabel(slot);
|
||||||
Slot = slot;
|
_currentItem = ItemManager.NothingId(slot);
|
||||||
_lastItemId = ItemManager.NothingId(slot);
|
|
||||||
_previewId = _lastItemId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DrawList(float width, float itemHeight)
|
protected override void DrawList(float width, float itemHeight)
|
||||||
{
|
{
|
||||||
if (_previewId != _lastItemId)
|
|
||||||
{
|
|
||||||
_lastItemId = _previewId;
|
|
||||||
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _lastItemId);
|
|
||||||
CurrentSelection = Items[CurrentSelectionIdx];
|
|
||||||
}
|
|
||||||
base.DrawList(width, itemHeight);
|
base.DrawList(width, itemHeight);
|
||||||
if (NewSelection != null && Items.Count > NewSelection.Value)
|
if (NewSelection != null && Items.Count > NewSelection.Value)
|
||||||
CurrentSelection = Items[NewSelection.Value];
|
CurrentSelection = Items[NewSelection.Value];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override int UpdateCurrentSelected(int currentSelected)
|
||||||
|
{
|
||||||
|
if (CurrentSelection.ItemId != _currentItem)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
|
||||||
|
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
|
||||||
|
return base.UpdateCurrentSelected(CurrentSelectionIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSelected;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Draw(string previewName, uint previewIdx, float width)
|
public bool Draw(string previewName, uint previewIdx, float width)
|
||||||
{
|
{
|
||||||
_previewId = previewIdx;
|
_currentItem = previewIdx;
|
||||||
return Draw(Label, previewName, width, ImGui.GetTextLineHeightWithSpacing());
|
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
{
|
{
|
||||||
var obj = Items[globalIdx];
|
var obj = Items[globalIdx];
|
||||||
var name = ToString(obj);
|
var name = ToString(obj);
|
||||||
var ret = ImGui.Selectable(name, selected);
|
var ret = ImGui.Selectable(name, selected);
|
||||||
|
ImGui.SameLine();
|
||||||
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
||||||
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.Variant})");
|
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.Variant})");
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -89,12 +86,18 @@ public sealed class ItemCombo : FilterComboCache<Item>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Item> GetItems(ItemManager items, EquipSlot slot)
|
private static IReadOnlyList<Item> GetItems(ItemManager items, EquipSlot slot)
|
||||||
{
|
{
|
||||||
var nothing = ItemManager.NothingItem(slot);
|
var nothing = ItemManager.NothingItem(slot);
|
||||||
if (!items.Items.TryGetValue(slot.ToEquipType(), out var list))
|
if (!items.Items.TryGetValue(slot.ToEquipType(), out var list))
|
||||||
return new[] { nothing };
|
return new[]
|
||||||
|
{
|
||||||
|
nothing,
|
||||||
|
};
|
||||||
|
|
||||||
return list.Select(i => new Item(i)).Append(ItemManager.SmallClothesItem(slot)).OrderBy(i => i.Name).Prepend(nothing);
|
var enumerable = list.Select(i => new Item(i));
|
||||||
|
if (slot.IsEquipment())
|
||||||
|
enumerable = enumerable.Append(ItemManager.SmallClothesItem(slot));
|
||||||
|
return enumerable.OrderBy(i => i.Name).Prepend(nothing).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
107
Glamourer/Gui/Equipment/WeaponCombo.cs
Normal file
107
Glamourer/Gui/Equipment/WeaponCombo.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Glamourer.Util;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
using OtterGui.Widgets;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui.Equipment;
|
||||||
|
|
||||||
|
public sealed class WeaponCombo : FilterComboCache<Weapon>
|
||||||
|
{
|
||||||
|
public readonly string Label;
|
||||||
|
private uint _currentItem;
|
||||||
|
|
||||||
|
public WeaponCombo(ItemManager items, FullEquipType type, EquipSlot offhand)
|
||||||
|
: base(offhand is EquipSlot.OffHand ? () => GetOff(items, type) : () => GetMain(items, type))
|
||||||
|
=> Label = GetLabel(offhand);
|
||||||
|
|
||||||
|
protected override void DrawList(float width, float itemHeight)
|
||||||
|
{
|
||||||
|
base.DrawList(width, itemHeight);
|
||||||
|
if (NewSelection != null && Items.Count > NewSelection.Value)
|
||||||
|
CurrentSelection = Items[NewSelection.Value];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override int UpdateCurrentSelected(int currentSelected)
|
||||||
|
{
|
||||||
|
if (CurrentSelection.ItemId != _currentItem)
|
||||||
|
{
|
||||||
|
CurrentSelectionIdx = Items.IndexOf(i => i.ItemId == _currentItem);
|
||||||
|
CurrentSelection = CurrentSelectionIdx >= 0 ? Items[CurrentSelectionIdx] : default;
|
||||||
|
return base.UpdateCurrentSelected(CurrentSelectionIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(string previewName, uint previewIdx, float width)
|
||||||
|
{
|
||||||
|
_currentItem = previewIdx;
|
||||||
|
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
|
{
|
||||||
|
var obj = Items[globalIdx];
|
||||||
|
var name = ToString(obj);
|
||||||
|
var ret = ImGui.Selectable(name, selected);
|
||||||
|
ImGui.SameLine();
|
||||||
|
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
|
||||||
|
ImGuiUtil.RightAlign($"({obj.ModelBase.Value}-{obj.WeaponBase.Value}-{obj.Variant})");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||||
|
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelBase.ToString());
|
||||||
|
|
||||||
|
protected override string ToString(Weapon obj)
|
||||||
|
=> obj.Name;
|
||||||
|
|
||||||
|
private static string GetLabel(EquipSlot offhand)
|
||||||
|
{
|
||||||
|
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
|
||||||
|
return offhand is EquipSlot.OffHand
|
||||||
|
? sheet.GetRow(739)?.Text.ToString() ?? "Off Hand"
|
||||||
|
: sheet.GetRow(738)?.Text.ToString() ?? "Main Hand";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Weapon> GetMain(ItemManager items, FullEquipType type)
|
||||||
|
{
|
||||||
|
var list = new List<Weapon>();
|
||||||
|
if (type is FullEquipType.Unknown)
|
||||||
|
foreach (var t in Enum.GetValues<FullEquipType>().Where(t => t.ToSlot() == EquipSlot.MainHand))
|
||||||
|
list.AddRange(items.Items[t].Select(w => new Weapon(w, false)));
|
||||||
|
else if (type.ToSlot() is EquipSlot.MainHand)
|
||||||
|
list.AddRange(items.Items[type].Select(w => new Weapon(w, false)));
|
||||||
|
list.Sort((w1, w2) => string.CompareOrdinal(w1.Name, w2.Name));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Weapon> GetOff(ItemManager items, FullEquipType type)
|
||||||
|
{
|
||||||
|
if (type.ToSlot() == EquipSlot.OffHand)
|
||||||
|
{
|
||||||
|
var nothing = ItemManager.NothingItem(type);
|
||||||
|
if (!items.Items.TryGetValue(type, out var list))
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
nothing,
|
||||||
|
};
|
||||||
|
|
||||||
|
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).Prepend(nothing).ToList();
|
||||||
|
}
|
||||||
|
else if (items.Items.TryGetValue(type, out var list))
|
||||||
|
{
|
||||||
|
return list.Select(w => new Weapon(w, true)).OrderBy(w => w.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.Empty<Weapon>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Glamourer.Gui.Customization;
|
|
||||||
using Glamourer.Gui.Equipment;
|
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
@ -20,18 +19,20 @@ internal partial class Interface
|
||||||
private class ActorTab
|
private class ActorTab
|
||||||
{
|
{
|
||||||
private readonly Interface _main;
|
private readonly Interface _main;
|
||||||
private readonly CurrentManipulations _manipulations;
|
private readonly ActiveDesign.Manager _activeDesigns;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
|
||||||
public ActorTab(Interface main, CurrentManipulations manipulations)
|
public ActorTab(Interface main, ActiveDesign.Manager activeDesigns, ObjectManager objects)
|
||||||
{
|
{
|
||||||
_main = main;
|
_main = main;
|
||||||
_manipulations = manipulations;
|
_activeDesigns = activeDesigns;
|
||||||
|
_objects = objects;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||||
private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid;
|
private ObjectManager.ActorData _currentData = ObjectManager.ActorData.Invalid;
|
||||||
private string _currentLabel = string.Empty;
|
private string _currentLabel = string.Empty;
|
||||||
private ActiveDesign? _currentSave;
|
private ActiveDesign? _currentSave;
|
||||||
|
|
||||||
public void Draw()
|
public void Draw()
|
||||||
{
|
{
|
||||||
|
|
@ -40,7 +41,7 @@ internal partial class Interface
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DrawActorSelector();
|
DrawActorSelector();
|
||||||
if (!ObjectManager.Actors.TryGetValue(_identifier, out _currentData))
|
if (!_objects.TryGetValue(_identifier, out _currentData))
|
||||||
_currentData = ObjectManager.ActorData.Invalid;
|
_currentData = ObjectManager.ActorData.Invalid;
|
||||||
else
|
else
|
||||||
_currentLabel = _currentData.Label;
|
_currentLabel = _currentData.Label;
|
||||||
|
|
@ -66,14 +67,31 @@ internal partial class Interface
|
||||||
_currentSave.Update(_currentData.Objects[0]);
|
_currentSave.Update(_currentData.Objects[0]);
|
||||||
|
|
||||||
RevertButton();
|
RevertButton();
|
||||||
CustomizationDrawer.Draw(_currentSave.Customize(), _currentSave.Equipment(), _currentData.Objects,
|
if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special))
|
||||||
_identifier.Type == IdentifierType.Special);
|
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false);
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
_main._equipmentDrawer.DrawStain(_currentSave, slot, out var stain);
|
var current = _currentSave.Armor(slot);
|
||||||
|
if (_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain))
|
||||||
|
_activeDesigns.ChangeStain(_currentSave, slot, stain.RowIndex, false);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_main._equipmentDrawer.DrawArmor(_currentSave, slot, out var armor);
|
if (_main._equipmentDrawer.DrawArmor(current, slot, out var armor, _currentSave.Customize().Gender, _currentSave.Customize().Race))
|
||||||
|
_activeDesigns.ChangeEquipment(_currentSave, slot, armor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMain = _currentSave.WeaponMain;
|
||||||
|
if (_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain))
|
||||||
|
_activeDesigns.ChangeStain(_currentSave, EquipSlot.MainHand, stainMain.RowIndex, false);
|
||||||
|
ImGui.SameLine();
|
||||||
|
_main._equipmentDrawer.DrawMainhand(currentMain, true, out var main);
|
||||||
|
if (currentMain.Type.Offhand() != FullEquipType.Unknown)
|
||||||
|
{
|
||||||
|
var currentOff = _currentSave.WeaponOff;
|
||||||
|
if (_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff))
|
||||||
|
_activeDesigns.ChangeStain(_currentSave, EquipSlot.OffHand, stainOff.RowIndex, false);
|
||||||
|
ImGui.SameLine();
|
||||||
|
_main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,18 +101,14 @@ internal partial class Interface
|
||||||
private unsafe void RevertButton()
|
private unsafe void RevertButton()
|
||||||
{
|
{
|
||||||
if (ImGui.Button("Revert"))
|
if (ImGui.Button("Revert"))
|
||||||
{
|
_activeDesigns.RevertDesign(_currentSave!);
|
||||||
_manipulations.DeleteSave(_identifier);
|
//foreach (var actor in _currentData.Objects)
|
||||||
|
// _currentSave!.ApplyToActor(actor);
|
||||||
//foreach (var actor in _currentData.Objects)
|
//
|
||||||
// _currentSave!.ApplyToActor(actor);
|
//if (_currentData.Objects.Count > 0)
|
||||||
//
|
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
||||||
//if (_currentData.Objects.Count > 0)
|
//
|
||||||
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
//_currentSave!.Reset();
|
||||||
//
|
|
||||||
//_currentSave!.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentData.Objects.Count > 0)
|
if (_currentData.Objects.Count > 0)
|
||||||
ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
|
ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
|
||||||
//VisorBox();
|
//VisorBox();
|
||||||
|
|
@ -215,24 +229,24 @@ internal partial class Interface
|
||||||
if (!child)
|
if (!child)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ObjectManager.Update();
|
_objects.Update();
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
||||||
var remainder = ImGuiClip.FilteredClippedDraw(ObjectManager.List, skips, CheckFilter, DrawSelectable);
|
var remainder = ImGuiClip.FilteredClippedDraw(_objects, skips, CheckFilter, DrawSelectable);
|
||||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair)
|
private bool CheckFilter(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
|
||||||
=> _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
|
=> _actorFilter.IsEmpty || pair.Value.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private void DrawSelectable((ActorIdentifier, ObjectManager.ActorData) pair)
|
private void DrawSelectable(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
|
||||||
{
|
{
|
||||||
var equal = pair.Item1.Equals(_identifier);
|
var equal = pair.Key.Equals(_identifier);
|
||||||
if (ImGui.Selectable(pair.Item2.Label, equal) && !equal)
|
if (ImGui.Selectable(pair.Value.Label, equal) && !equal)
|
||||||
{
|
{
|
||||||
_identifier = pair.Item1.CreatePermanent();
|
_identifier = pair.Key.CreatePermanent();
|
||||||
_currentData = pair.Item2;
|
_currentData = pair.Value;
|
||||||
_currentSave = _currentData.Valid ? _manipulations.GetOrCreateSave(_currentData.Objects[0]) : null;
|
_currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,325 +257,14 @@ internal partial class Interface
|
||||||
var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0);
|
var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0);
|
||||||
|
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
||||||
, "Select the local player character.", !ObjectManager.Player, true))
|
, "Select the local player character.", !_objects.Player, true))
|
||||||
_identifier = ObjectManager.Player.GetIdentifier();
|
_identifier = _objects.Player.GetIdentifier();
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
Actor targetActor = Dalamud.Targets.Target?.Address;
|
Actor targetActor = Dalamud.Targets.Target?.Address;
|
||||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth,
|
||||||
"Select the current target, if it is in the list.", ObjectManager.IsInGPose || !targetActor, true))
|
"Select the current target, if it is in the list.", _objects.IsInGPose || !targetActor, true))
|
||||||
_identifier = targetActor.GetIdentifier();
|
_identifier = targetActor.GetIdentifier();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//internal partial class Interface
|
|
||||||
//{
|
|
||||||
// private readonly CharacterSave _currentSave = new();
|
|
||||||
// private string _newDesignName = string.Empty;
|
|
||||||
// private bool _keyboardFocus;
|
|
||||||
// private bool _holdShift;
|
|
||||||
// private bool _holdCtrl;
|
|
||||||
// private const string DesignNamePopupLabel = "Save Design As...";
|
|
||||||
// private const uint RedHeaderColor = 0xFF1818C0;
|
|
||||||
// private const uint GreenHeaderColor = 0xFF18C018;
|
|
||||||
//
|
|
||||||
// private void DrawPlayerHeader()
|
|
||||||
// {
|
|
||||||
// var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
|
||||||
// var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
|
||||||
// using var c = ImRaii.PushColor(ImGuiCol.Text, color)
|
|
||||||
// .Push(ImGuiCol.Button, buttonColor)
|
|
||||||
// .Push(ImGuiCol.ButtonHovered, buttonColor)
|
|
||||||
// .Push(ImGuiCol.ButtonActive, buttonColor);
|
|
||||||
// using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
|
||||||
// .Push(ImGuiStyleVar.FrameRounding, 0);
|
|
||||||
// ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static void DrawCopyClipboardButton(CharacterSave save)
|
|
||||||
// {
|
|
||||||
// ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
// if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
|
||||||
// ImGui.SetClipboardText(save.ToBase64());
|
|
||||||
// ImGui.PopFont();
|
|
||||||
// ImGuiUtil.HoverTooltip("Copy customization code to clipboard.");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static unsafe void ConditionalApply(CharacterSave save, FFXIVClientStructs.FFXIV.Client.Game.Character.Character* player)
|
|
||||||
// {
|
|
||||||
// //if (ImGui.GetIO().KeyShift)
|
|
||||||
// // save.ApplyOnlyCustomizations(player);
|
|
||||||
// //else if (ImGui.GetIO().KeyCtrl)
|
|
||||||
// // save.ApplyOnlyEquipment(player);
|
|
||||||
// //else
|
|
||||||
// // save.Apply(player);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static unsafe void ConditionalApply(CharacterSave save, Character player)
|
|
||||||
// => ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
|
|
||||||
//
|
|
||||||
// private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
|
|
||||||
// {
|
|
||||||
// var copy = save.Copy();
|
|
||||||
// if (shift)
|
|
||||||
// {
|
|
||||||
// copy.Load(new CharacterEquipment());
|
|
||||||
// copy.SetHatState = false;
|
|
||||||
// copy.SetVisorState = false;
|
|
||||||
// copy.SetWeaponState = false;
|
|
||||||
// copy.WriteEquipment = CharacterEquipMask.None;
|
|
||||||
// }
|
|
||||||
// else if (ctrl)
|
|
||||||
// {
|
|
||||||
// copy.Load(CharacterCustomization.Default);
|
|
||||||
// copy.SetHatState = false;
|
|
||||||
// copy.SetVisorState = false;
|
|
||||||
// copy.SetWeaponState = false;
|
|
||||||
// copy.WriteCustomizations = false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return copy;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private bool DrawApplyClipboardButton()
|
|
||||||
// {
|
|
||||||
// ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
// var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString()) && _player != null;
|
|
||||||
// ImGui.PopFont();
|
|
||||||
// ImGuiUtil.HoverTooltip(
|
|
||||||
// "Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
|
|
||||||
//
|
|
||||||
// if (!applyButton)
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var text = ImGui.GetClipboardText();
|
|
||||||
// if (!text.Any())
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// var save = CharacterSave.FromString(text);
|
|
||||||
// ConditionalApply(save, _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();
|
|
||||||
// ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
|
||||||
//
|
|
||||||
// DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void DrawTargetPlayerButton()
|
|
||||||
// {
|
|
||||||
// if (ImGui.Button("Target Player"))
|
|
||||||
// Dalamud.Targets.SetTarget(_player);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private unsafe void DrawApplyToPlayerButton(CharacterSave save)
|
|
||||||
// {
|
|
||||||
// if (!ImGui.Button("Apply to Self"))
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// var player = _inGPose
|
|
||||||
// ? (Character?)Dalamud.Objects[GPoseObjectId]
|
|
||||||
// : Dalamud.ClientState.LocalPlayer;
|
|
||||||
// var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
|
||||||
// if (player == null)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)player.Address);
|
|
||||||
// if (_inGPose)
|
|
||||||
// ConditionalApply(save, (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)fallback!.Address);
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* TransformToCustomizable(
|
|
||||||
// FFXIVClientStructs.FFXIV.Client.Game.Character.Character* actor)
|
|
||||||
// {
|
|
||||||
// if (actor == null)
|
|
||||||
// return null;
|
|
||||||
//
|
|
||||||
// if (actor->ModelCharaId == 0)
|
|
||||||
// return actor;
|
|
||||||
//
|
|
||||||
// actor->ModelCharaId = 0;
|
|
||||||
// CharacterCustomization.Default.Write(actor);
|
|
||||||
// return actor;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private static unsafe FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Convert(GameObject? actor)
|
|
||||||
// {
|
|
||||||
// return actor switch
|
|
||||||
// {
|
|
||||||
// null => null,
|
|
||||||
// PlayerCharacter p => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)p.Address,
|
|
||||||
// BattleChara b => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)b.Address,
|
|
||||||
// _ => actor.ObjectKind switch
|
|
||||||
// {
|
|
||||||
// ObjectKind.BattleNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
|
||||||
// ObjectKind.Companion => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
|
||||||
// ObjectKind.Retainer => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
|
||||||
// ObjectKind.EventNpc => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor.Address,
|
|
||||||
// _ => null,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private unsafe void DrawApplyToTargetButton(CharacterSave save)
|
|
||||||
// {
|
|
||||||
// if (!ImGui.Button("Apply to Target"))
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// var player = TransformToCustomizable(Convert(Dalamud.Targets.Target));
|
|
||||||
// if (player == null)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// var fallBackCharacter = _gPoseActors.TryGetValue(new Utf8String(player->GameObject.Name).ToString(), out var f) ? f : null;
|
|
||||||
// ConditionalApply(save, player);
|
|
||||||
// if (fallBackCharacter != null)
|
|
||||||
// ConditionalApply(save, fallBackCharacter!);
|
|
||||||
// //Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void DrawRevertButton()
|
|
||||||
// {
|
|
||||||
// if (!ImGuiUtil.DrawDisabledButton("Revert", Vector2.Zero, string.Empty, _player == null))
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// Glamourer.RevertableDesigns.Revert(_player!);
|
|
||||||
// var fallBackCharacter = _gPoseActors.TryGetValue(_player!.Name.ToString(), out var f) ? f : null;
|
|
||||||
// if (fallBackCharacter != null)
|
|
||||||
// Glamourer.RevertableDesigns.Revert(fallBackCharacter);
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(_player, fallBackCharacter);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void SaveNewDesign(CharacterSave save)
|
|
||||||
// {
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
|
||||||
// if (!name.Any())
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// 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 unsafe void DrawMonsterPanel()
|
|
||||||
// {
|
|
||||||
// if (DrawApplyClipboardButton())
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
//
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// if (ImGui.Button("Convert to Character"))
|
|
||||||
// {
|
|
||||||
// //TransformToCustomizable(_player);
|
|
||||||
// _currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!_inGPose)
|
|
||||||
// {
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawTargetPlayerButton();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var currentModel = ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId;
|
|
||||||
// using var combo = ImRaii.Combo("Model Id", currentModel.ToString());
|
|
||||||
// if (!combo)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// foreach (var (id, _) in _models.Skip(1))
|
|
||||||
// {
|
|
||||||
// if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player!.Address)->ModelCharaId = 0;
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void DrawPlayerPanel()
|
|
||||||
// {
|
|
||||||
// DrawCopyClipboardButton(_currentSave);
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawSaveDesignButton();
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawApplyToPlayerButton(_currentSave);
|
|
||||||
// if (!_inGPose)
|
|
||||||
// {
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawApplyToTargetButton(_currentSave);
|
|
||||||
// if (_player != null && !_currentSave.WriteProtected)
|
|
||||||
// {
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawTargetPlayerButton();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var data = _currentSave;
|
|
||||||
// if (!_currentSave.WriteProtected)
|
|
||||||
// {
|
|
||||||
// ImGui.SameLine();
|
|
||||||
// DrawRevertButton();
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
|
||||||
// data = data.Copy();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (DrawCustomization(ref data.Customizations) && _player != null)
|
|
||||||
// {
|
|
||||||
// Glamourer.RevertableDesigns.Add(_player);
|
|
||||||
// _currentSave.Customizations.Write(_player.Address);
|
|
||||||
// changes = true;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// changes |= DrawEquip(data.Equipment);
|
|
||||||
// changes |= DrawMiscellaneous(data, _player);
|
|
||||||
//
|
|
||||||
// if (_player != null && changes)
|
|
||||||
// Glamourer.Penumbra.UpdateCharacters(_player);
|
|
||||||
// if (_currentSave.WriteProtected)
|
|
||||||
// ImGui.PopStyleVar();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private unsafe void DrawActorPanel()
|
|
||||||
// {
|
|
||||||
// using var group = ImRaii.Group();
|
|
||||||
// DrawPlayerHeader();
|
|
||||||
// using var child = ImRaii.Child("##playerData", -Vector2.One, true);
|
|
||||||
// if (!child)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// if (_player == null || ((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)_player.Address)->ModelCharaId == 0)
|
|
||||||
// DrawPlayerPanel();
|
|
||||||
// else
|
|
||||||
// DrawMonsterPanel();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Glamourer.Gui.Customization;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -17,15 +15,15 @@ internal partial class Interface
|
||||||
{
|
{
|
||||||
private class DebugStateTab
|
private class DebugStateTab
|
||||||
{
|
{
|
||||||
private readonly CurrentManipulations _currentManipulations;
|
private readonly ActiveDesign.Manager _activeDesigns;
|
||||||
|
|
||||||
private LowerString _manipulationFilter = LowerString.Empty;
|
private LowerString _manipulationFilter = LowerString.Empty;
|
||||||
private ActorIdentifier _selection = ActorIdentifier.Invalid;
|
private ActorIdentifier _selection = ActorIdentifier.Invalid;
|
||||||
private ActiveDesign? _save = null;
|
private ActiveDesign? _save = null;
|
||||||
private bool _delete = false;
|
private bool _delete = false;
|
||||||
|
|
||||||
public DebugStateTab(CurrentManipulations currentManipulations)
|
public DebugStateTab(ActiveDesign.Manager activeDesigns)
|
||||||
=> _currentManipulations = currentManipulations;
|
=> _activeDesigns = activeDesigns;
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
public void Draw()
|
public void Draw()
|
||||||
|
|
@ -43,7 +41,7 @@ internal partial class Interface
|
||||||
if (_delete)
|
if (_delete)
|
||||||
{
|
{
|
||||||
_delete = false;
|
_delete = false;
|
||||||
_currentManipulations.DeleteSave(_selection);
|
_activeDesigns.DeleteSave(_selection);
|
||||||
_selection = ActorIdentifier.Invalid;
|
_selection = ActorIdentifier.Invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +54,7 @@ internal partial class Interface
|
||||||
|
|
||||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||||
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
var skips = ImGuiClip.GetNecessarySkips(ImGui.GetTextLineHeight());
|
||||||
var remainder = ImGuiClip.FilteredClippedDraw(_currentManipulations, skips, CheckFilter, DrawSelectable);
|
var remainder = ImGuiClip.FilteredClippedDraw(_activeDesigns, skips, CheckFilter, DrawSelectable);
|
||||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
ImGuiClip.DrawEndDummy(remainder, ImGui.GetTextLineHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui.Customization;
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Designs;
|
using Glamourer.Gui.Designs;
|
||||||
|
|
@ -37,9 +38,7 @@ internal partial class Interface
|
||||||
{
|
{
|
||||||
using var tab = ImRaii.TabItem("Designs");
|
using var tab = ImRaii.TabItem("Designs");
|
||||||
if (!tab)
|
if (!tab)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Selector.Draw(GetDesignSelectorSize());
|
Selector.Draw(GetDesignSelectorSize());
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -55,12 +54,25 @@ internal partial class Interface
|
||||||
if (!child || Selector.Selected == null)
|
if (!child || Selector.Selected == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CustomizationDrawer.Draw(Selector.Selected.Customize(), Selector.Selected.Equipment(), true);
|
_main._customizationDrawer.Draw(Selector.Selected.Customize(), CustomizeFlagExtensions.All, true);
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
_main._equipmentDrawer.DrawStain(Selector.Selected, slot, out var stain);
|
var current = Selector.Selected.Armor(slot);
|
||||||
|
_main._equipmentDrawer.DrawStain(current.Stain, slot, out var stain);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_main._equipmentDrawer.DrawArmor(Selector.Selected, slot, out var armor);
|
_main._equipmentDrawer.DrawArmor(current, slot, out var armor);
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMain = Selector.Selected.WeaponMain;
|
||||||
|
_main._equipmentDrawer.DrawStain(currentMain.Stain, EquipSlot.MainHand, out var stainMain);
|
||||||
|
ImGui.SameLine();
|
||||||
|
_main._equipmentDrawer.DrawMainhand(currentMain, true, out var main);
|
||||||
|
if (currentMain.Type.Offhand() != FullEquipType.Unknown)
|
||||||
|
{
|
||||||
|
var currentOff = Selector.Selected.WeaponOff;
|
||||||
|
_main._equipmentDrawer.DrawStain(currentOff.Stain, EquipSlot.OffHand, out var stainOff);
|
||||||
|
ImGui.SameLine();
|
||||||
|
_main._equipmentDrawer.DrawOffhand(currentOff, main.Type, out var off);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,9 @@ internal partial class Interface
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.Button(buttonLabel))
|
// TODO
|
||||||
Glamourer.Penumbra.Reattach(true);
|
//if (ImGui.Button(buttonLabel))
|
||||||
|
// Glamourer.Penumbra.Reattach(true);
|
||||||
|
|
||||||
ImGuiUtil.HoverTooltip(
|
ImGuiUtil.HoverTooltip(
|
||||||
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
||||||
|
|
@ -73,18 +74,6 @@ internal partial class Interface
|
||||||
v => cfg.ColorDesigns = v);
|
v => cfg.ColorDesigns = v);
|
||||||
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
|
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
|
||||||
v => cfg.ShowLocks = v);
|
v => cfg.ShowLocks = v);
|
||||||
Checkmark("Attach to Penumbra",
|
|
||||||
"Allows you to right-click items in the Changed Items tab of a mod in Penumbra to apply them to your player character.",
|
|
||||||
cfg.AttachToPenumbra,
|
|
||||||
v =>
|
|
||||||
{
|
|
||||||
cfg.AttachToPenumbra = v;
|
|
||||||
if (v)
|
|
||||||
Glamourer.Penumbra.Reattach(true);
|
|
||||||
else
|
|
||||||
Glamourer.Penumbra.Unattach();
|
|
||||||
});
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawRestorePenumbraButton();
|
DrawRestorePenumbraButton();
|
||||||
|
|
||||||
Checkmark("Apply Fixed Designs",
|
Checkmark("Apply Fixed Designs",
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,38 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Gui.Customization;
|
using Glamourer.Gui.Customization;
|
||||||
using Glamourer.Gui.Equipment;
|
using Glamourer.Gui.Equipment;
|
||||||
|
using Glamourer.Interop;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Util;
|
using Glamourer.Util;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Data;
|
|
||||||
|
|
||||||
namespace Glamourer.Gui;
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
internal partial class Interface : Window, IDisposable
|
internal partial class Interface : Window, IDisposable
|
||||||
{
|
{
|
||||||
private readonly EquipmentDrawer _equipmentDrawer;
|
private readonly DalamudPluginInterface _pi;
|
||||||
|
|
||||||
|
private readonly EquipmentDrawer _equipmentDrawer;
|
||||||
|
private readonly CustomizationDrawer _customizationDrawer;
|
||||||
|
|
||||||
private readonly ActorTab _actorTab;
|
private readonly ActorTab _actorTab;
|
||||||
private readonly DesignTab _designTab;
|
private readonly DesignTab _designTab;
|
||||||
private readonly DebugStateTab _debugStateTab;
|
private readonly DebugStateTab _debugStateTab;
|
||||||
private readonly DebugDataTab _debugDataTab;
|
private readonly DebugDataTab _debugDataTab;
|
||||||
|
|
||||||
public Interface(ItemManager items, CurrentManipulations manipulations, Design.Manager manager, DesignFileSystem fileSystem)
|
public Interface(DalamudPluginInterface pi, ItemManager items, ActiveDesign.Manager activeDesigns, Design.Manager manager,
|
||||||
|
DesignFileSystem fileSystem, ObjectManager objects)
|
||||||
: base(GetLabel())
|
: base(GetLabel())
|
||||||
{
|
{
|
||||||
|
_pi = pi;
|
||||||
_equipmentDrawer = new EquipmentDrawer(items);
|
_equipmentDrawer = new EquipmentDrawer(items);
|
||||||
|
_customizationDrawer = new CustomizationDrawer(pi);
|
||||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
|
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
|
||||||
SizeConstraints = new WindowSizeConstraints()
|
SizeConstraints = new WindowSizeConstraints()
|
||||||
|
|
@ -34,8 +40,8 @@ internal partial class Interface : Window, IDisposable
|
||||||
MinimumSize = new Vector2(675, 675),
|
MinimumSize = new Vector2(675, 675),
|
||||||
MaximumSize = ImGui.GetIO().DisplaySize,
|
MaximumSize = ImGui.GetIO().DisplaySize,
|
||||||
};
|
};
|
||||||
_actorTab = new ActorTab(this, manipulations);
|
_actorTab = new ActorTab(this, activeDesigns, objects);
|
||||||
_debugStateTab = new DebugStateTab(manipulations);
|
_debugStateTab = new DebugStateTab(activeDesigns);
|
||||||
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
||||||
_designTab = new DesignTab(this, manager, fileSystem);
|
_designTab = new DesignTab(this, manager, fileSystem);
|
||||||
}
|
}
|
||||||
|
|
@ -67,8 +73,8 @@ internal partial class Interface : Window, IDisposable
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
|
_pi.UiBuilder.OpenConfigUi -= Toggle;
|
||||||
CustomizationDrawer.Dispose();
|
_customizationDrawer.Dispose();
|
||||||
_designTab.Dispose();
|
_designTab.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,90 +83,3 @@ internal partial class Interface : Window, IDisposable
|
||||||
? "Glamourer###GlamourerConfigWindow"
|
? "Glamourer###GlamourerConfigWindow"
|
||||||
: $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow";
|
: $"Glamourer v{Glamourer.Version}###GlamourerConfigWindow";
|
||||||
}
|
}
|
||||||
|
|
||||||
//public const float SelectorWidth = 200;
|
|
||||||
//public const float MinWindowWidth = 675;
|
|
||||||
//public const int GPoseObjectId = 201;
|
|
||||||
//private const string PluginName = "Glamourer";
|
|
||||||
//private readonly string _glamourerHeader;
|
|
||||||
//
|
|
||||||
//private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
|
||||||
//private readonly IReadOnlyDictionary<uint, ModelCeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeehara> _models;
|
|
||||||
//private readonly IObjectIdentifier _identifier;
|
|
||||||
//private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
|
|
||||||
//private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
|
|
||||||
//private readonly Dictionary<EquipSlot, string> _equipSlotNames;
|
|
||||||
//private readonly DesignManager _designs;
|
|
||||||
//private readonly Glamourer _plugin;
|
|
||||||
//
|
|
||||||
//private bool _visible;
|
|
||||||
//private bool _inGPose;
|
|
||||||
//
|
|
||||||
//public Interface(Glamourer plugin)
|
|
||||||
//{
|
|
||||||
// _plugin = plugin;
|
|
||||||
// _designs = plugin.Designs;
|
|
||||||
// _glamourerHeader = Glamourer.Version.Length > 0
|
|
||||||
// ? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
|
|
||||||
// : $"{PluginName}###{PluginName}Main";
|
|
||||||
// Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
|
||||||
// Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
|
||||||
// Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
|
|
||||||
//
|
|
||||||
// _equipSlotNames = GetEquipSlotNames();
|
|
||||||
//
|
|
||||||
// _stains = GameData.Stains(Dalamud.GameData);
|
|
||||||
// _models = GameData.Models(Dalamud.GameData);
|
|
||||||
// _identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// var stainCombo = CreateDefaultStainCombo(_stains.Values.ToArray());
|
|
||||||
//
|
|
||||||
// var equip = GameData.ItemsBySlot(Dalamud.GameData);
|
|
||||||
// _combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
|
|
||||||
// _legacyTattooIcon = GetLegacyTattooIcon();
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public void ToggleVisibility()
|
|
||||||
// => _visible = !_visible;
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//private void Draw()
|
|
||||||
//{
|
|
||||||
// if (!_visible)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
|
|
||||||
// Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
|
|
||||||
// if (!ImGui.Begin(_glamourerHeader, ref _visible))
|
|
||||||
// {
|
|
||||||
// ImGui.End();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// using var tabBar = ImRaii.TabBar("##tabBar");
|
|
||||||
// if (!tabBar)
|
|
||||||
// return;
|
|
||||||
//
|
|
||||||
// _inGPose = Dalamud.Objects[GPoseObjectId] != 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;
|
|
||||||
//
|
|
||||||
// DrawPlayerTab();
|
|
||||||
// DrawSaves();
|
|
||||||
// DrawFixedDesignsTab();
|
|
||||||
// DrawConfigTab();
|
|
||||||
// DrawRevertablesTab();
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// ImGui.End();
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||||
public bool Valid
|
public bool Valid
|
||||||
=> Pointer != null;
|
=> Pointer != null;
|
||||||
|
|
||||||
|
public int Index
|
||||||
|
=> Pointer->GameObject.ObjectIndex;
|
||||||
|
|
||||||
public uint ModelId
|
public uint ModelId
|
||||||
{
|
{
|
||||||
get => (uint)Pointer->ModelCharaId;
|
get => (uint)Pointer->ModelCharaId;
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,15 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Text;
|
using System.Collections;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using System.Collections.Generic;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Penumbra.GameData.Actors;
|
using Penumbra.GameData.Actors;
|
||||||
using static Glamourer.Interop.Actor;
|
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
public static class ObjectManager
|
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ObjectManager.ActorData>
|
||||||
{
|
{
|
||||||
private const int CutsceneIndex = 200;
|
|
||||||
private const int GPosePlayerIndex = 201;
|
|
||||||
private const int CharacterScreenIndex = 240;
|
|
||||||
private const int ExamineScreenIndex = 241;
|
|
||||||
private const int FittingRoomIndex = 242;
|
|
||||||
private const int DyePreviewIndex = 243;
|
|
||||||
private const int PortraitIndex = 244;
|
|
||||||
|
|
||||||
public readonly struct ActorData
|
public readonly struct ActorData
|
||||||
{
|
{
|
||||||
public readonly List<Actor> Objects;
|
public readonly List<Actor> Objects;
|
||||||
|
|
@ -40,28 +33,22 @@ public static class ObjectManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsInGPose { get; private set; }
|
public DateTime LastUpdate { get; private set; }
|
||||||
public static ushort World { get; private set; }
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<ActorIdentifier, ActorData> Actors
|
public bool IsInGPose { get; private set; }
|
||||||
=> Identifiers;
|
public ushort World { get; private set; }
|
||||||
|
|
||||||
public static IReadOnlyList<(ActorIdentifier, ActorData)> List
|
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||||
=> ListData;
|
|
||||||
|
|
||||||
private static readonly Dictionary<ActorIdentifier, ActorData> Identifiers = new(200);
|
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
||||||
private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length);
|
|
||||||
|
|
||||||
private static void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
|
||||||
{
|
{
|
||||||
if (!character.DrawObject || !identifier.IsValid)
|
if (!character.DrawObject || !identifier.IsValid)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!Identifiers.TryGetValue(identifier, out var data))
|
if (!_identifiers.TryGetValue(identifier, out var data))
|
||||||
{
|
{
|
||||||
data = new ActorData(character, identifier.ToString());
|
data = new ActorData(character, identifier.ToString());
|
||||||
Identifiers[identifier] = data;
|
_identifiers[identifier] = data;
|
||||||
ListData.Add((identifier, data));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -69,88 +56,100 @@ public static class ObjectManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Update()
|
private readonly Framework _framework;
|
||||||
{
|
private readonly ClientState _clientState;
|
||||||
World = (ushort)(Dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
private readonly ObjectTable _objects;
|
||||||
Identifiers.Clear();
|
|
||||||
ListData.Clear();
|
|
||||||
|
|
||||||
for (var i = 0; i < CutsceneIndex; ++i)
|
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects)
|
||||||
|
{
|
||||||
|
_framework = framework;
|
||||||
|
_clientState = clientState;
|
||||||
|
_objects = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
var lastUpdate = _framework.LastUpdate;
|
||||||
|
if (lastUpdate <= LastUpdate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
LastUpdate = lastUpdate;
|
||||||
|
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||||
|
_identifiers.Clear();
|
||||||
|
|
||||||
|
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||||
{
|
{
|
||||||
Actor character = Dalamud.Objects.GetObjectAddress(i);
|
Actor character = _objects.GetObjectAddress(i);
|
||||||
if (character.Identifier(out var identifier))
|
if (character.Identifier(out var identifier))
|
||||||
HandleIdentifier(identifier, character);
|
HandleIdentifier(identifier, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = CutsceneIndex; i < CharacterScreenIndex; ++i)
|
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
|
||||||
{
|
{
|
||||||
Actor character = Dalamud.Objects.GetObjectAddress(i);
|
Actor character = _objects.GetObjectAddress(i);
|
||||||
if (!character.Identifier(out var identifier))
|
if (!character.Identifier(out var identifier))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
HandleIdentifier(identifier, character);
|
HandleIdentifier(identifier, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddSpecial(int idx, string label)
|
void AddSpecial(ScreenActor idx, string label)
|
||||||
{
|
{
|
||||||
Actor actor = Dalamud.Objects.GetObjectAddress(idx);
|
Actor actor = _objects.GetObjectAddress((int)idx);
|
||||||
if (actor.Identifier(out var ident))
|
if (actor.Identifier(out var ident))
|
||||||
{
|
{
|
||||||
var data = new ActorData(actor, label);
|
var data = new ActorData(actor, label);
|
||||||
Identifiers.Add(ident, data);
|
_identifiers.Add(ident, data);
|
||||||
ListData.Add((ident, data));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddSpecial(CharacterScreenIndex, "Character Screen Actor");
|
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
|
||||||
AddSpecial(ExamineScreenIndex, "Examine Screen Actor");
|
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
|
||||||
AddSpecial(FittingRoomIndex, "Fitting Room Actor");
|
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
|
||||||
AddSpecial(DyePreviewIndex, "Dye Preview Actor");
|
AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor");
|
||||||
AddSpecial(PortraitIndex, "Portrait Actor");
|
AddSpecial(ScreenActor.Portrait, "Portrait Actor");
|
||||||
|
AddSpecial(ScreenActor.Card6, "Card Actor 6");
|
||||||
|
AddSpecial(ScreenActor.Card7, "Card Actor 7");
|
||||||
|
AddSpecial(ScreenActor.Card8, "Card Actor 8");
|
||||||
|
|
||||||
for (var i = PortraitIndex + 1; i < Dalamud.Objects.Length; ++i)
|
for (var i = (int)ScreenActor.ScreenEnd; i < Dalamud.Objects.Length; ++i)
|
||||||
{
|
{
|
||||||
Actor character = Dalamud.Objects.GetObjectAddress(i);
|
Actor character = _objects.GetObjectAddress(i);
|
||||||
if (character.Identifier(out var identifier))
|
if (character.Identifier(out var identifier))
|
||||||
HandleIdentifier(identifier, character);
|
HandleIdentifier(identifier, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gPose = GPosePlayer;
|
||||||
Actor gPose = Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
|
|
||||||
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
|
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Actor GPosePlayer
|
public Actor GPosePlayer
|
||||||
=> Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
|
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
|
||||||
|
|
||||||
public static Actor Player
|
public Actor Player
|
||||||
=> Dalamud.Objects.GetObjectAddress(0);
|
=> _objects.GetObjectAddress(0);
|
||||||
|
|
||||||
private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose)
|
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
||||||
{
|
=> _identifiers.GetEnumerator();
|
||||||
var sb = new StringBuilder(64);
|
|
||||||
sb.Append(playerName);
|
|
||||||
|
|
||||||
if (gPose)
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
{
|
=> GetEnumerator();
|
||||||
sb.Append(" (GPose");
|
|
||||||
|
|
||||||
if (player.ObjectKind == ObjectKind.Player)
|
public int Count
|
||||||
sb.Append(')');
|
=> _identifiers.Count;
|
||||||
else
|
|
||||||
sb.Append(player.ModelId == 0 ? ", NPC)" : ", Monster)");
|
|
||||||
}
|
|
||||||
else if (player.ObjectKind != ObjectKind.Player)
|
|
||||||
{
|
|
||||||
sb.Append(player.ModelId == 0 ? " (NPC)" : " (Monster)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (num > 1)
|
public bool ContainsKey(ActorIdentifier key)
|
||||||
{
|
=> _identifiers.ContainsKey(key);
|
||||||
sb.Append(" #");
|
|
||||||
sb.Append(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.ToString();
|
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||||
}
|
=> _identifiers.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
public ActorData this[ActorIdentifier key]
|
||||||
|
=> _identifiers[key];
|
||||||
|
|
||||||
|
public IEnumerable<ActorIdentifier> Keys
|
||||||
|
=> _identifiers.Keys;
|
||||||
|
|
||||||
|
public IEnumerable<ActorData> Values
|
||||||
|
=> _identifiers.Values;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,35 +8,6 @@ namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe partial class RedrawManager
|
public unsafe partial class RedrawManager
|
||||||
{
|
{
|
||||||
// Update
|
|
||||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
|
||||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
|
||||||
{
|
|
||||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var d = actor.DrawObject;
|
|
||||||
if (NeedsRedraw(d.Customize, customize))
|
|
||||||
{
|
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
|
||||||
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.BodyType != rhs.BodyType
|
|
||||||
|| lhs.Face != rhs.Face
|
|
||||||
|| lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
|
||||||
|
|
||||||
|
|
||||||
public static void SetVisor(Human* data, bool on)
|
public static void SetVisor(Human* data, bool on)
|
||||||
|
|
|
||||||
|
|
@ -69,16 +69,4 @@ public unsafe partial class RedrawManager
|
||||||
//
|
//
|
||||||
//return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
//return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleEquipUpdate(nint drawObject, EquipSlot slot, ref CharacterArmor data, bool manual)
|
|
||||||
{
|
|
||||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject(drawObject);
|
|
||||||
var identifier = actor.GetIdentifier();
|
|
||||||
|
|
||||||
if (!_currentManipulations.TryGetDesign(identifier, out var design))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var flag = slot.ToFlag();
|
|
||||||
var stainFlag = slot.ToStainFlag();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ public unsafe partial class RedrawManager
|
||||||
_ => weapon,
|
_ => weapon,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
|
||||||
{
|
{
|
||||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||||
//switch (slot)
|
//switch (slot)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,104 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
using Glamourer.Structs;
|
using Glamourer.Structs;
|
||||||
using Penumbra.GameData.Data;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
public class DesignBaseValidator
|
public partial class Interop : IDisposable
|
||||||
{
|
{
|
||||||
private readonly CustomizationManager _manager;
|
public Interop()
|
||||||
private readonly RestrictedGear _restrictedGear;
|
|
||||||
|
|
||||||
public DesignBaseValidator(CustomizationManager manager, RestrictedGear restrictedGear)
|
|
||||||
{
|
{
|
||||||
_manager = manager;
|
SignatureHelper.Initialise(this);
|
||||||
_restrictedGear = restrictedGear;
|
_changeJobHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_changeJobHook.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Interop
|
||||||
|
{
|
||||||
|
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
||||||
|
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
|
||||||
|
|
||||||
public unsafe partial class RedrawManager
|
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||||
|
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||||
|
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||||
|
|
||||||
|
public event FlagSlotForUpdateDelegate? EquipUpdate;
|
||||||
|
|
||||||
|
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
|
||||||
|
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||||
|
|
||||||
|
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
|
{
|
||||||
|
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||||
|
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||||
|
{
|
||||||
|
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||||
|
UpdateSlot(drawObject, slot, armor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||||
|
{
|
||||||
|
var slot = slotIdx.ToEquipSlot();
|
||||||
|
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||||
|
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
||||||
|
{
|
||||||
|
if (EquipUpdate == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
del(drawObject, slot, ref armor);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe partial class Interop
|
||||||
|
{
|
||||||
|
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||||
|
|
||||||
|
[Signature(Sigs.ChangeCustomize)]
|
||||||
|
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||||
|
|
||||||
|
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||||
|
{
|
||||||
|
Debug.Assert(customize.Data != null, "Customize was invalid.");
|
||||||
|
|
||||||
|
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class Interop
|
||||||
{
|
{
|
||||||
private delegate void ChangeJobDelegate(IntPtr data, uint job);
|
private delegate void ChangeJobDelegate(IntPtr data, uint job);
|
||||||
|
|
||||||
|
|
@ -44,27 +117,21 @@ public unsafe partial class RedrawManager
|
||||||
public unsafe partial class RedrawManager : IDisposable
|
public unsafe partial class RedrawManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FixedDesigns _fixedDesigns;
|
private readonly FixedDesigns _fixedDesigns;
|
||||||
private readonly CurrentManipulations _currentManipulations;
|
private readonly ActiveDesign.Manager _stateManager;
|
||||||
|
|
||||||
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
|
public RedrawManager(FixedDesigns fixedDesigns, ActiveDesign.Manager stateManager)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
|
_fixedDesigns = fixedDesigns;
|
||||||
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
|
_stateManager = stateManager;
|
||||||
_fixedDesigns = fixedDesigns;
|
|
||||||
_currentManipulations = currentManipulations;
|
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
_loadWeaponHook.Enable();
|
_loadWeaponHook.Enable();
|
||||||
_changeJobHook.Enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_flagSlotForUpdateHook.Dispose();
|
_flagSlotForUpdateHook.Dispose();
|
||||||
_loadWeaponHook.Dispose();
|
_loadWeaponHook.Dispose();
|
||||||
_changeJobHook.Dispose();
|
|
||||||
Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw;
|
|
||||||
Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||||
|
|
@ -76,7 +143,7 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
|
|
||||||
// Check if we have a current design in use, or if not if the actor has a fixed design.
|
// Check if we have a current design in use, or if not if the actor has a fixed design.
|
||||||
var identifier = actor.GetIdentifier();
|
var identifier = actor.GetIdentifier();
|
||||||
if (!(_currentManipulations.TryGetDesign(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2)))
|
if (!(_stateManager.TryGetValue(identifier, out var save) || _fixedDesigns.TryGetDesign(identifier, out var save2)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Compare game object customize data against draw object customize data for transformations.
|
// Compare game object customize data against draw object customize data for transformations.
|
||||||
|
|
@ -116,4 +183,4 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
{
|
{
|
||||||
//SetVisor((Human*)drawObject, true);
|
//SetVisor((Human*)drawObject, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
211
Glamourer/State/ActiveDesign.Manager.cs
Normal file
211
Glamourer/State/ActiveDesign.Manager.cs
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using Glamourer.Interop;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
|
using Glamourer.Api;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Designs;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
using Item = Glamourer.Designs.Item;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
public sealed partial class ActiveDesign
|
||||||
|
{
|
||||||
|
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
|
||||||
|
{
|
||||||
|
private readonly ActorManager _actors;
|
||||||
|
private readonly ObjectManager _objects;
|
||||||
|
private readonly Interop.Interop _interop;
|
||||||
|
private readonly PenumbraAttach _penumbra;
|
||||||
|
|
||||||
|
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
||||||
|
|
||||||
|
public Manager(ActorManager actors, ObjectManager objects, Interop.Interop interop, PenumbraAttach penumbra)
|
||||||
|
{
|
||||||
|
_actors = actors;
|
||||||
|
_objects = objects;
|
||||||
|
_interop = interop;
|
||||||
|
_penumbra = penumbra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
||||||
|
=> _characterSaves.GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
=> GetEnumerator();
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
=> _characterSaves.Count;
|
||||||
|
|
||||||
|
public bool ContainsKey(ActorIdentifier key)
|
||||||
|
=> _characterSaves.ContainsKey(key);
|
||||||
|
|
||||||
|
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
|
||||||
|
=> _characterSaves.TryGetValue(key, out value);
|
||||||
|
|
||||||
|
public ActiveDesign this[ActorIdentifier key]
|
||||||
|
=> _characterSaves[key];
|
||||||
|
|
||||||
|
public IEnumerable<ActorIdentifier> Keys
|
||||||
|
=> _characterSaves.Keys;
|
||||||
|
|
||||||
|
public IEnumerable<ActiveDesign> Values
|
||||||
|
=> _characterSaves.Values;
|
||||||
|
|
||||||
|
public void DeleteSave(ActorIdentifier identifier)
|
||||||
|
=> _characterSaves.Remove(identifier);
|
||||||
|
|
||||||
|
public unsafe ActiveDesign GetOrCreateSave(Actor actor)
|
||||||
|
{
|
||||||
|
var id = _actors.FromObject((GameObject*)actor.Pointer, out _, false, false);
|
||||||
|
if (_characterSaves.TryGetValue(id, out var save))
|
||||||
|
{
|
||||||
|
save.Update(actor);
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
id = id.CreatePermanent();
|
||||||
|
save = new ActiveDesign(id, actor);
|
||||||
|
save.Update(actor);
|
||||||
|
_characterSaves.Add(id, save);
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RevertDesign(ActiveDesign design)
|
||||||
|
{
|
||||||
|
RevertCustomize(design, design.ChangedCustomize);
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
RevertEquipment(design, slot, design.ChangedEquip.HasFlag(slot.ToFlag()), design.ChangedEquip.HasFlag(slot.ToStainFlag()));
|
||||||
|
|
||||||
|
RevertMainHand(design);
|
||||||
|
RevertOffHand(design);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RevertMainHand(ActiveDesign design)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void RevertOffHand(ActiveDesign design)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void RevertCustomize(ActiveDesign design, CustomizeFlag flags)
|
||||||
|
=> ChangeCustomize(design, flags, design._initialData.CustomizeData, false);
|
||||||
|
|
||||||
|
public void ChangeCustomize(ActiveDesign design, CustomizeFlag flags, CustomizeData newValue, bool fromFixed)
|
||||||
|
{
|
||||||
|
var customize = new Customize(ref newValue);
|
||||||
|
var anyChanges = false;
|
||||||
|
foreach (var option in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
var flag = option.ToFlag();
|
||||||
|
var apply = flags.HasFlag(flag);
|
||||||
|
anyChanges |= apply && design.SetCustomize(option, customize[option]);
|
||||||
|
if (design.GetCustomize(option).Value != design._initialData.Customize[option].Value)
|
||||||
|
design.ChangedCustomize |= flag;
|
||||||
|
else
|
||||||
|
design.ChangedCustomize &= ~flag;
|
||||||
|
|
||||||
|
if (fromFixed)
|
||||||
|
design.FixedCustomize |= flag;
|
||||||
|
else
|
||||||
|
design.FixedCustomize &= ~flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyChanges)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_objects.Update();
|
||||||
|
if (!_objects.TryGetValue(design.Identifier, out var data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var redraw = flags.RequiresRedraw();
|
||||||
|
foreach (var obj in data.Objects)
|
||||||
|
{
|
||||||
|
if (redraw)
|
||||||
|
_penumbra.RedrawObject(obj, RedrawType.Redraw);
|
||||||
|
else
|
||||||
|
_interop.UpdateCustomize(obj, design.CharacterData.CustomizeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RevertEquipment(ActiveDesign design, EquipSlot slot, bool equip, bool stain)
|
||||||
|
{
|
||||||
|
var item = design._initialData.Equipment[slot];
|
||||||
|
if (equip)
|
||||||
|
{
|
||||||
|
var flag = slot.ToFlag();
|
||||||
|
design.UpdateArmor(slot, item, true);
|
||||||
|
design.ChangedEquip &= ~flag;
|
||||||
|
design.FixedEquip &= ~flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stain)
|
||||||
|
{
|
||||||
|
var flag = slot.ToStainFlag();
|
||||||
|
design.SetStain(slot, item.Stain);
|
||||||
|
design.ChangedEquip &= ~flag;
|
||||||
|
design.FixedEquip &= ~flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
_objects.Update();
|
||||||
|
if (!_objects.TryGetValue(design.Identifier, out var data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var obj in data.Objects)
|
||||||
|
_interop.UpdateSlot(obj.DrawObject, slot, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeEquipment(ActiveDesign design, EquipSlot slot, Item item, bool fromFixed)
|
||||||
|
{
|
||||||
|
var flag = slot.ToFlag();
|
||||||
|
design.SetArmor(slot, item);
|
||||||
|
var current = design.Armor(slot);
|
||||||
|
var initial = design._initialData.Equipment[slot];
|
||||||
|
if (current.ModelBase.Value != initial.Set.Value || current.Variant != initial.Variant)
|
||||||
|
design.ChangedEquip |= flag;
|
||||||
|
else
|
||||||
|
design.ChangedEquip &= ~flag;
|
||||||
|
if (fromFixed)
|
||||||
|
design.FixedEquip |= flag;
|
||||||
|
else
|
||||||
|
design.FixedEquip &= ~flag;
|
||||||
|
|
||||||
|
_objects.Update();
|
||||||
|
if (!_objects.TryGetValue(design.Identifier, out var data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var obj in data.Objects)
|
||||||
|
_interop.UpdateSlot(obj.DrawObject, slot, item.Model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeStain(ActiveDesign design, EquipSlot slot, StainId stain, bool fromFixed)
|
||||||
|
{
|
||||||
|
var flag = slot.ToStainFlag();
|
||||||
|
design.SetStain(slot, stain);
|
||||||
|
var current = design.Armor(slot);
|
||||||
|
var initial = design._initialData.Equipment[slot];
|
||||||
|
if (current.Stain.Value != initial.Stain.Value)
|
||||||
|
design.ChangedEquip |= flag;
|
||||||
|
else
|
||||||
|
design.ChangedEquip &= ~flag;
|
||||||
|
if (fromFixed)
|
||||||
|
design.FixedEquip |= flag;
|
||||||
|
else
|
||||||
|
design.FixedEquip &= ~flag;
|
||||||
|
|
||||||
|
_objects.Update();
|
||||||
|
if (!_objects.TryGetValue(design.Identifier, out var data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var obj in data.Objects)
|
||||||
|
_interop.UpdateStain(obj.DrawObject, slot, stain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Glamourer.Designs;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.State;
|
|
||||||
|
|
||||||
public sealed partial class ActiveDesign
|
|
||||||
{
|
|
||||||
public partial class Manager : IReadOnlyDictionary<ActorIdentifier, ActiveDesign>
|
|
||||||
{
|
|
||||||
private readonly ActorManager _actors;
|
|
||||||
|
|
||||||
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
|
||||||
|
|
||||||
public Manager(ActorManager actors)
|
|
||||||
=> _actors = actors;
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
|
||||||
=> _characterSaves.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
=> GetEnumerator();
|
|
||||||
|
|
||||||
public int Count
|
|
||||||
=> _characterSaves.Count;
|
|
||||||
|
|
||||||
public bool ContainsKey(ActorIdentifier key)
|
|
||||||
=> _characterSaves.ContainsKey(key);
|
|
||||||
|
|
||||||
public bool TryGetValue(ActorIdentifier key, [NotNullWhen(true)] out ActiveDesign? value)
|
|
||||||
=> _characterSaves.TryGetValue(key, out value);
|
|
||||||
|
|
||||||
public ActiveDesign this[ActorIdentifier key]
|
|
||||||
=> _characterSaves[key];
|
|
||||||
|
|
||||||
public IEnumerable<ActorIdentifier> Keys
|
|
||||||
=> _characterSaves.Keys;
|
|
||||||
|
|
||||||
public IEnumerable<ActiveDesign> Values
|
|
||||||
=> _characterSaves.Values;
|
|
||||||
|
|
||||||
public void DeleteSave(ActorIdentifier identifier)
|
|
||||||
=> _characterSaves.Remove(identifier);
|
|
||||||
|
|
||||||
public ActiveDesign GetOrCreateSave(Actor actor)
|
|
||||||
{
|
|
||||||
var id = actor.GetIdentifier();
|
|
||||||
if (_characterSaves.TryGetValue(id, out var save))
|
|
||||||
{
|
|
||||||
save.Update(actor);
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
|
|
||||||
save = new ActiveDesign();
|
|
||||||
save.Update(actor);
|
|
||||||
_characterSaves.Add(id.CreatePermanent(), save);
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,33 +2,38 @@
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
public sealed partial class ActiveDesign : DesignBase
|
public sealed partial class ActiveDesign : DesignBase
|
||||||
{
|
{
|
||||||
|
public readonly ActorIdentifier Identifier;
|
||||||
|
|
||||||
private CharacterData _initialData = new();
|
private CharacterData _initialData = new();
|
||||||
|
|
||||||
private CustomizeFlag _changedCustomize;
|
public CustomizeFlag ChangedCustomize { get; private set; } = 0;
|
||||||
private CustomizeFlag _fixedCustomize;
|
public CustomizeFlag FixedCustomize { get; private set; } = 0;
|
||||||
|
|
||||||
private EquipFlag _changedEquip;
|
public EquipFlag ChangedEquip { get; private set; } = 0;
|
||||||
private EquipFlag _fixedEquip;
|
public EquipFlag FixedEquip { get; private set; } = 0;
|
||||||
|
|
||||||
public bool IsHatVisible { get; private set; } = false;
|
public bool IsHatVisible { get; private set; } = false;
|
||||||
public bool IsWeaponVisible { get; private set; } = false;
|
public bool IsWeaponVisible { get; private set; } = false;
|
||||||
public bool IsVisorToggled { get; private set; } = false;
|
public bool IsVisorToggled { get; private set; } = false;
|
||||||
public bool IsWet { get; private set; } = false;
|
public bool IsWet { get; private set; } = false;
|
||||||
|
|
||||||
private ActiveDesign()
|
private ActiveDesign(ActorIdentifier identifier)
|
||||||
{ }
|
=> Identifier = identifier;
|
||||||
|
|
||||||
public ActiveDesign(Actor actor)
|
private ActiveDesign(ActorIdentifier identifier, Actor actor)
|
||||||
{
|
{
|
||||||
|
Identifier = identifier;
|
||||||
Update(actor);
|
Update(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//public void ApplyToActor(Actor actor)
|
//public void ApplyToActor(Actor actor)
|
||||||
//{
|
//{
|
||||||
// if (!actor)
|
// if (!actor)
|
||||||
|
|
@ -72,14 +77,14 @@ public sealed partial class ActiveDesign : DesignBase
|
||||||
|
|
||||||
var initialEquip = _initialData.Equipment;
|
var initialEquip = _initialData.Equipment;
|
||||||
var currentEquip = actor.Equip;
|
var currentEquip = actor.Equip;
|
||||||
var equipment = Equipment();
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
var current = currentEquip[slot];
|
var current = currentEquip[slot];
|
||||||
if (initialEquip[slot] != current)
|
if (initialEquip[slot] != current)
|
||||||
{
|
{
|
||||||
initialEquip[slot] = current;
|
initialEquip[slot] = current;
|
||||||
equipment[slot] = current;
|
UpdateArmor(slot, current, true);
|
||||||
|
SetStain(slot, current.Stain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,12 +92,14 @@ public sealed partial class ActiveDesign : DesignBase
|
||||||
{
|
{
|
||||||
_initialData.MainHand = actor.MainHand;
|
_initialData.MainHand = actor.MainHand;
|
||||||
UpdateMainhand(actor.MainHand);
|
UpdateMainhand(actor.MainHand);
|
||||||
|
SetStain(EquipSlot.MainHand, actor.MainHand.Stain);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_initialData.OffHand != actor.OffHand)
|
if (_initialData.OffHand != actor.OffHand)
|
||||||
{
|
{
|
||||||
_initialData.OffHand = actor.OffHand;
|
_initialData.OffHand = actor.OffHand;
|
||||||
UpdateMainhand(actor.OffHand);
|
UpdateOffhand(actor.OffHand);
|
||||||
|
SetStain(EquipSlot.OffHand, actor.OffHand.Stain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using Penumbra.GameData.Actors;
|
|
||||||
|
|
||||||
namespace Glamourer.State;
|
|
||||||
|
|
||||||
public class CurrentManipulations : IReadOnlyCollection<KeyValuePair<ActorIdentifier, ActiveDesign>>
|
|
||||||
{
|
|
||||||
private readonly Dictionary<ActorIdentifier, ActiveDesign> _characterSaves = new();
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActiveDesign>> GetEnumerator()
|
|
||||||
=> _characterSaves.GetEnumerator();
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
=> GetEnumerator();
|
|
||||||
|
|
||||||
public int Count
|
|
||||||
=> _characterSaves.Count;
|
|
||||||
|
|
||||||
public ActiveDesign GetOrCreateSave(Actor actor)
|
|
||||||
{
|
|
||||||
var id = actor.GetIdentifier();
|
|
||||||
if (_characterSaves.TryGetValue(id, out var save))
|
|
||||||
{
|
|
||||||
save.Update(actor);
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
|
|
||||||
save = new ActiveDesign(actor);
|
|
||||||
_characterSaves.Add(id.CreatePermanent(), save);
|
|
||||||
return save;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteSave(ActorIdentifier identifier)
|
|
||||||
=> _characterSaves.Remove(identifier);
|
|
||||||
|
|
||||||
public bool TryGetDesign(ActorIdentifier identifier, [NotNullWhen(true)] out ActiveDesign? save)
|
|
||||||
=> _characterSaves.TryGetValue(identifier, out save);
|
|
||||||
|
|
||||||
//public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
|
||||||
//{
|
|
||||||
// var save = CreateSave(actor);
|
|
||||||
// (_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customize.Race, save.Customize.Gender);
|
|
||||||
// if (save.Equipment[slot] == data)
|
|
||||||
// return null;
|
|
||||||
//
|
|
||||||
// save.Equipment[slot] = data;
|
|
||||||
// return data;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public bool ChangeWeapon(Actor actor, CharacterWeapon main)
|
|
||||||
//{
|
|
||||||
// var save = CreateSave(actor);
|
|
||||||
// if (save.MainHand == main)
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// save.MainHand = main;
|
|
||||||
// return true;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//public bool ChangeWeapon(Actor actor, CharacterWeapon main, CharacterWeapon off)
|
|
||||||
//{
|
|
||||||
// var save = CreateSave(actor);
|
|
||||||
// if (main == save.MainHand && off == save.OffHand)
|
|
||||||
// return false;
|
|
||||||
//
|
|
||||||
// save.MainHand = main;
|
|
||||||
// save.OffHand = off;
|
|
||||||
// return true;
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@ using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Util;
|
namespace Glamourer.Util;
|
||||||
|
|
||||||
public static unsafe class CustomizeExtensions
|
public static class CustomizeExtensions
|
||||||
{
|
{
|
||||||
// In languages other than english the actual clan name may depend on gender.
|
// In languages other than english the actual clan name may depend on gender.
|
||||||
public static string ClanName(SubRace race, Gender gender)
|
public static string ClanName(SubRace race, Gender gender)
|
||||||
|
|
@ -57,58 +57,39 @@ public static unsafe class CustomizeExtensions
|
||||||
|
|
||||||
|
|
||||||
// Change a gender and fix up all required customizations afterwards.
|
// Change a gender and fix up all required customizations afterwards.
|
||||||
public static bool ChangeGender(this Customize customize, CharacterEquip equip, Gender gender)
|
public static CustomizeFlag ChangeGender(this Customize customize, CharacterEquip equip, Gender gender)
|
||||||
{
|
{
|
||||||
if (customize.Gender == gender)
|
if (customize.Gender == gender)
|
||||||
return false;
|
return 0;
|
||||||
|
|
||||||
FixRestrictedGear(customize, equip, gender, customize.Race);
|
FixRestrictedGear(customize, equip, gender, customize.Race);
|
||||||
customize.Gender = gender;
|
customize.Gender = gender;
|
||||||
FixUpAttributes(customize);
|
return CustomizeFlag.Gender | FixUpAttributes(customize);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change a race and fix up all required customizations afterwards.
|
// Change a race and fix up all required customizations afterwards.
|
||||||
public static bool ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan)
|
public static CustomizeFlag ChangeRace(this Customize customize, CharacterEquip equip, SubRace clan)
|
||||||
{
|
{
|
||||||
if (customize.Clan == clan)
|
if (customize.Clan == clan)
|
||||||
return false;
|
return 0;
|
||||||
|
|
||||||
var race = clan.ToRace();
|
var race = clan.ToRace();
|
||||||
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
|
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
|
||||||
FixRestrictedGear(customize, equip, gender, race);
|
FixRestrictedGear(customize, equip, gender, race);
|
||||||
|
var flags = CustomizeFlag.Race | CustomizeFlag.Clan;
|
||||||
|
if (gender != customize.Gender)
|
||||||
|
flags |= CustomizeFlag.Gender;
|
||||||
customize.Gender = gender;
|
customize.Gender = gender;
|
||||||
customize.Race = race;
|
customize.Race = race;
|
||||||
customize.Clan = clan;
|
customize.Clan = clan;
|
||||||
FixUpAttributes(customize);
|
return flags | FixUpAttributes(customize);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void ChangeCustomization(this Customize customize, CharacterEquip equip, Customize newCustomize)
|
|
||||||
{
|
|
||||||
FixRestrictedGear(customize, equip, newCustomize.Gender, newCustomize.Race);
|
|
||||||
customize.Load(newCustomize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value)
|
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
|
||||||
case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customize[index] == value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
customize[index] = value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go through a whole customization struct and fix up all settings that need fixing.
|
// Go through a whole customization struct and fix up all settings that need fixing.
|
||||||
private static void FixUpAttributes(Customize customize)
|
private static CustomizeFlag FixUpAttributes(Customize customize)
|
||||||
{
|
{
|
||||||
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||||
|
CustomizeFlag flags = 0;
|
||||||
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
|
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
|
||||||
{
|
{
|
||||||
switch (id)
|
switch (id)
|
||||||
|
|
@ -122,10 +103,16 @@ public static unsafe class CustomizeExtensions
|
||||||
default:
|
default:
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
|
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
|
||||||
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
|
{
|
||||||
|
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
|
||||||
|
flags |= id.ToFlag();
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race)
|
private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race)
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,19 @@ public class ItemManager : IDisposable
|
||||||
|
|
||||||
public static Designs.Item NothingItem(EquipSlot slot)
|
public static Designs.Item NothingItem(EquipSlot slot)
|
||||||
{
|
{
|
||||||
Debug.Assert(slot.IsEquipment() || slot.IsAccessory());
|
Debug.Assert(slot.IsEquipment() || slot.IsAccessory(), $"Called {nameof(NothingItem)} on {slot}.");
|
||||||
return new Designs.Item(Nothing, NothingId(slot), CharacterArmor.Empty);
|
return new Designs.Item(Nothing, NothingId(slot), CharacterArmor.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Designs.Weapon NothingItem(FullEquipType type)
|
||||||
|
{
|
||||||
|
Debug.Assert(type.ToSlot() == EquipSlot.OffHand, $"Called {nameof(NothingItem)} on {type}.");
|
||||||
|
return new Designs.Weapon(Nothing, NothingId(type), CharacterWeapon.Empty, type);
|
||||||
|
}
|
||||||
|
|
||||||
public static Designs.Item SmallClothesItem(EquipSlot slot)
|
public static Designs.Item SmallClothesItem(EquipSlot slot)
|
||||||
{
|
{
|
||||||
Debug.Assert(slot.IsEquipment());
|
Debug.Assert(slot.IsEquipment(), $"Called {nameof(SmallClothesItem)} on {slot}.");
|
||||||
return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0));
|
return new Designs.Item(SmallClothesNpc, SmallclothesId(slot), new CharacterArmor(SmallClothesNpcModel, 1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue