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
|
||||
{
|
||||
internal readonly bool Valid;
|
||||
private readonly bool _valid;
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
|
@ -68,7 +68,7 @@ public partial class CustomizationOptions
|
|||
{
|
||||
var tmp = new TemporaryData(gameData, this);
|
||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||
Valid = tmp.Valid;
|
||||
_valid = tmp.Valid;
|
||||
SetNames(gameData, tmp);
|
||||
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.
|
||||
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.
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
||||
(ushort)hairRow.RowId)
|
||||
: new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||
if (hairRow == null)
|
||||
{
|
||||
hairList.Add(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();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ public unsafe struct Customize
|
|||
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
||||
=> Data = data;
|
||||
|
||||
public Customize(ref Penumbra.GameData.Structs.CustomizeData data)
|
||||
{
|
||||
fixed (Penumbra.GameData.Structs.CustomizeData* ptr = &data)
|
||||
{
|
||||
Data = ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public Race Race
|
||||
{
|
||||
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
||||
|
|
@ -96,4 +104,18 @@ public unsafe struct Customize
|
|||
|
||||
public string 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ public enum CustomizeFlag : ulong
|
|||
public static class CustomizeFlagExtensions
|
||||
{
|
||||
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)]
|
||||
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 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;
|
||||
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)
|
||||
{
|
||||
var abbr = job.Abbreviation.ToString();
|
||||
|
|
@ -29,7 +29,7 @@ public readonly struct JobGroup
|
|||
continue;
|
||||
|
||||
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)!)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,9 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Api;
|
||||
|
||||
|
|
@ -17,33 +13,76 @@ public unsafe class PenumbraAttach : IDisposable
|
|||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private ActionSubscriber<GameObject, RedrawType> _redrawSubscriber;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
public EventSubscriber<nint, string, nint, nint, nint> CreatingCharacterBase;
|
||||
public EventSubscriber<nint, string, nint> CreatedCharacterBase;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
public bool Available { get; private set; }
|
||||
|
||||
public PenumbraAttach(bool attach)
|
||||
public PenumbraAttach()
|
||||
{
|
||||
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
|
||||
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
|
||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip);
|
||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick);
|
||||
CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
Reattach(attach);
|
||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface);
|
||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface);
|
||||
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||
Reattach();
|
||||
}
|
||||
|
||||
private void Reattach()
|
||||
=> Reattach(Glamourer.Config.AttachToPenumbra);
|
||||
public event Action<MouseButton, ChangedItemType, uint> Click
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -54,22 +93,19 @@ public unsafe class PenumbraAttach : IDisposable
|
|||
throw new Exception(
|
||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||
|
||||
if (!attach)
|
||||
return;
|
||||
|
||||
_tooltipSubscriber.Enable();
|
||||
_clickSubscriber.Enable();
|
||||
CreatingCharacterBase.Enable();
|
||||
CreatedCharacterBase.Enable();
|
||||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(Dalamud.PluginInterface);
|
||||
Available = true;
|
||||
PluginLog.Debug("Glamourer attached to Penumbra.");
|
||||
Glamourer.Log.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
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();
|
||||
_clickSubscriber.Disable();
|
||||
CreatingCharacterBase.Disable();
|
||||
CreatedCharacterBase.Disable();
|
||||
_creatingCharacterBase.Disable();
|
||||
_createdCharacterBase.Disable();
|
||||
if (Available)
|
||||
{
|
||||
Available = false;
|
||||
|
|
@ -91,85 +127,66 @@ public unsafe class PenumbraAttach : IDisposable
|
|||
Unattach();
|
||||
_tooltipSubscriber.Dispose();
|
||||
_clickSubscriber.Dispose();
|
||||
CreatingCharacterBase.Dispose();
|
||||
CreatedCharacterBase.Dispose();
|
||||
_creatingCharacterBase.Dispose();
|
||||
_createdCharacterBase.Dispose();
|
||||
_initializedEvent.Dispose();
|
||||
_disposedEvent.Dispose();
|
||||
}
|
||||
|
||||
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||
{
|
||||
if (type == ChangedItemType.Item)
|
||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||
}
|
||||
//private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||
//{
|
||||
// if (type == ChangedItemType.Item)
|
||||
// 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)
|
||||
{
|
||||
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(ObjectManager.GPosePlayer, writeItem);
|
||||
UpdateItem(ObjectManager.Player, writeItem);
|
||||
}
|
||||
|
||||
private static void UpdateItem(Actor actor, Item2 item2)
|
||||
{
|
||||
if (!actor || !actor.DrawObject)
|
||||
return;
|
||||
|
||||
switch (item2.EquippableTo)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
{
|
||||
var off = item2.HasSubModel
|
||||
? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
||||
: item2.IsBothHand
|
||||
? CharacterWeapon.Empty
|
||||
: actor.OffHand;
|
||||
var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.MainHand.Stain);
|
||||
Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||
return;
|
||||
}
|
||||
case EquipSlot.OffHand:
|
||||
{
|
||||
var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant, actor.DrawObject.OffHand.Stain);
|
||||
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}");
|
||||
}
|
||||
}
|
||||
//private static void UpdateItem(Actor actor, Item2 item2)
|
||||
//{
|
||||
// if (!actor || !actor.DrawObject)
|
||||
// return;
|
||||
//
|
||||
// switch (item2.EquippableTo)
|
||||
// {
|
||||
// case EquipSlot.MainHand:
|
||||
// {
|
||||
// var off = item2.HasSubModel
|
||||
// ? new CharacterWeapon(item2.SubModel.id, item2.SubModel.type, item2.SubModel.variant, actor.DrawObject.OffHand.Stain)
|
||||
// : item2.IsBothHand
|
||||
// ? CharacterWeapon.Empty
|
||||
// : actor.OffHand;
|
||||
// var main = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
|
||||
// actor.DrawObject.MainHand.Stain);
|
||||
// Glamourer.RedrawManager.LoadWeapon(actor, main, off);
|
||||
// return;
|
||||
// }
|
||||
// case EquipSlot.OffHand:
|
||||
// {
|
||||
// var off = new CharacterWeapon(item2.MainModel.id, item2.MainModel.type, item2.MainModel.variant,
|
||||
// actor.DrawObject.OffHand.Stain);
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
// Update objects without triggering PlayerWatcher Events,
|
||||
// then manually redraw using Penumbra.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Util;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -103,6 +102,9 @@ public class DesignBase
|
|||
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)
|
||||
{
|
||||
if (!force)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Dalamud.Utility;
|
||||
using Glamourer.Util;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -62,6 +61,9 @@ public readonly struct Weapon
|
|||
=> 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)
|
||||
{
|
||||
Name = name;
|
||||
|
|
@ -81,4 +83,23 @@ public readonly struct Weapon
|
|||
? new Weapon()
|
||||
: 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.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Plugin;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
|
|
@ -13,7 +12,6 @@ using Glamourer.Gui;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Util;
|
||||
using ImGuizmoNET;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -39,14 +37,17 @@ public class Glamourer : IDalamudPlugin
|
|||
public static GlamourerConfig Config = null!;
|
||||
public static Logger Log = null!;
|
||||
public static ActorManager Actors = null!;
|
||||
public static PenumbraAttach Penumbra = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public static RedrawManager RedrawManager = null!;
|
||||
public static ItemManager Items = null!;
|
||||
public readonly FixedDesigns FixedDesigns;
|
||||
public readonly CurrentManipulations CurrentManipulations;
|
||||
|
||||
private readonly Interop.Interop _interop;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
private readonly ObjectManager _objectManager;
|
||||
private readonly Design.Manager _designManager;
|
||||
private readonly ActiveDesign.Manager _stateManager;
|
||||
private readonly DrawObjectManager _drawObjectManager;
|
||||
private readonly DesignFileSystem _fileSystem;
|
||||
private readonly FrameworkManager _framework;
|
||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||
|
|
@ -65,6 +66,8 @@ public class Glamourer : IDalamudPlugin
|
|||
Log = new Logger();
|
||||
|
||||
_framework = new FrameworkManager(Dalamud.Framework, Log);
|
||||
_interop = new Interop.Interop();
|
||||
_penumbra = new PenumbraAttach();
|
||||
|
||||
Items = new ItemManager(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||
|
|
@ -72,17 +75,19 @@ public class Glamourer : IDalamudPlugin
|
|||
Backup.CreateBackup(pluginInterface.ConfigDirectory, BackupFiles(Dalamud.PluginInterface));
|
||||
Config = GlamourerConfig.Load();
|
||||
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, Dalamud.GameGui,
|
||||
i => (short)Penumbra.CutsceneParent(i));
|
||||
_objectManager = new ObjectManager(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
|
||||
Actors = new ActorManager(Dalamud.PluginInterface, Dalamud.Objects, Dalamud.ClientState, Dalamud.Framework, Dalamud.GameData,
|
||||
Dalamud.GameGui, i => (short)_penumbra.CutsceneParent(i));
|
||||
|
||||
|
||||
_designManager = new Design.Manager(Dalamud.PluginInterface, _framework);
|
||||
_fileSystem = new DesignFileSystem(_designManager, Dalamud.PluginInterface, _framework);
|
||||
FixedDesigns = new FixedDesigns();
|
||||
CurrentManipulations = new CurrentManipulations();
|
||||
_stateManager = new ActiveDesign.Manager(Actors, _objectManager, _interop, _penumbra);
|
||||
|
||||
//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)
|
||||
{
|
||||
|
|
@ -93,7 +98,7 @@ public class Glamourer : IDalamudPlugin
|
|||
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);
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
||||
|
|
@ -108,13 +113,15 @@ public class Glamourer : IDalamudPlugin
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_drawObjectManager?.Dispose();
|
||||
RedrawManager?.Dispose();
|
||||
Penumbra?.Dispose();
|
||||
if (_windowSystem != null)
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||
_interface?.Dispose();
|
||||
_fileSystem?.Dispose();
|
||||
//GlamourerIpc.Dispose();
|
||||
_interop?.Dispose();
|
||||
_penumbra?.Dispose();
|
||||
_framework?.Dispose();
|
||||
Items?.Dispose();
|
||||
Dalamud.Commands.RemoveHandler(ApplyCommandString);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
public partial class CustomizationDrawer
|
||||
{
|
||||
private const string ColorPickerPopupName = "ColorPicker";
|
||||
|
||||
|
|
@ -26,7 +27,6 @@ internal partial class CustomizationDrawer
|
|||
DataInputInt(current);
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
DrawColorPickerPopup();
|
||||
}
|
||||
|
||||
|
|
@ -56,10 +56,9 @@ internal partial class CustomizationDrawer
|
|||
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
||||
{
|
||||
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||
if (!_set.IsAvailable(index) || current >= 0)
|
||||
return (current, custom!.Value);
|
||||
if (_set.IsAvailable(index) && current < 0)
|
||||
throw new Exception($"Read invalid customization value {_customize[index]} for {index}.");
|
||||
|
||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}.");
|
||||
return (0, _set.Data(index, 0));
|
||||
return (current, custom!.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,12 @@ using Glamourer.Util;
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
public partial class CustomizationDrawer
|
||||
{
|
||||
private void DrawRaceGenderSelector()
|
||||
{
|
||||
|
|
@ -27,20 +27,19 @@ internal partial class CustomizationDrawer
|
|||
private void DrawGenderSelector()
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||
var restricted = _customize.Race == Race.Hrothgar;
|
||||
if (restricted)
|
||||
icon = FontAwesomeIcon.MarsDouble;
|
||||
var icon = _customize.Gender switch
|
||||
{
|
||||
Gender.Male when _customize.Race is Race.Hrothgar => 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;
|
||||
|
||||
var gender = _customize.Gender == 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);
|
||||
Changed |= _customize.ChangeGender(CharacterEquip.Null, _customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
|
||||
}
|
||||
|
||||
private void DrawRaceCombo()
|
||||
|
|
@ -52,12 +51,8 @@ internal partial class CustomizationDrawer
|
|||
|
||||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||
{
|
||||
if (!ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)
|
||||
|| !_customize.ChangeRace(_equip, subRace))
|
||||
continue;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan))
|
||||
Changed |= _customize.ChangeRace(CharacterEquip.Null, subRace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
public partial class CustomizationDrawer
|
||||
{
|
||||
private const string IconSelectorPopup = "Style Picker";
|
||||
|
||||
|
|
@ -40,22 +38,23 @@ internal partial class CustomizationDrawer
|
|||
FaceInputInt(current);
|
||||
else
|
||||
DataInputInt(current);
|
||||
|
||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||
}
|
||||
|
||||
DrawIconPickerPopup();
|
||||
}
|
||||
|
||||
private void UpdateFace(CustomizeData data)
|
||||
private bool UpdateFace(CustomizeData data)
|
||||
{
|
||||
// Hrothgar Hack
|
||||
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||
if (_customize.Face == value)
|
||||
return;
|
||||
return false;
|
||||
|
||||
_customize.Face = value;
|
||||
foreach (var actor in _actors)
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
Changed |= CustomizeFlag.Face;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void FaceInputInt(int currentIndex)
|
||||
|
|
@ -78,6 +77,7 @@ internal partial class CustomizationDrawer
|
|||
if (!popup)
|
||||
return;
|
||||
|
||||
var ret = false;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
|
|
@ -107,4 +107,45 @@ internal partial class CustomizationDrawer
|
|||
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,8 +6,52 @@ using OtterGui.Raii;
|
|||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
public partial class CustomizationDrawer
|
||||
{
|
||||
private void PercentageSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawPercentageSlider();
|
||||
ImGui.SameLine();
|
||||
PercentageInputInt();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
private void DrawPercentageSlider()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||
UpdateValue((CustomizeValue)tmp);
|
||||
}
|
||||
|
||||
private void PercentageInputInt()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
||||
}
|
||||
|
||||
// Integral input for an icon- or color based item.
|
||||
private void DataInputInt(int currentIndex)
|
||||
{
|
||||
++currentIndex;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||
UpdateValue(data.Value);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
|
||||
private void DrawListSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
|
|
@ -44,67 +88,15 @@ internal partial class CustomizationDrawer
|
|||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
|
||||
private void PercentageSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawPercentageSlider();
|
||||
ImGui.SameLine();
|
||||
PercentageInputInt();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
private void DrawPercentageSlider()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||
UpdateValue((CustomizeValue)tmp);
|
||||
}
|
||||
|
||||
private void PercentageInputInt()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
UpdateValue((CustomizeValue)Math.Clamp(tmp, 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.
|
||||
private void DataInputInt(int currentIndex)
|
||||
var tmp = _currentByte != CustomizeValue.Zero;
|
||||
if (ImGui.Checkbox(_currentOption, ref tmp))
|
||||
{
|
||||
++currentIndex;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||
UpdateValue(data.Value);
|
||||
_customize.Set(idx, tmp ? CustomizeValue.Max : CustomizeValue.Zero);
|
||||
Changed |= _currentFlag;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
|
|
@ -13,26 +15,55 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Gui.Equipment;
|
||||
|
||||
public partial class EquipmentDrawer
|
||||
public class EquipmentDrawer
|
||||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly FilterComboColors _stainCombo;
|
||||
private readonly StainData _stainData;
|
||||
private readonly ItemCombo[] _itemCombo;
|
||||
private readonly Dictionary<(FullEquipType, EquipSlot), WeaponCombo> _weaponCombo;
|
||||
|
||||
public EquipmentDrawer(ItemManager items)
|
||||
{
|
||||
_items = items;
|
||||
_stainData = items.Stains;
|
||||
_stainCombo = new FilterComboColors(140,
|
||||
_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();
|
||||
_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));
|
||||
}
|
||||
|
||||
public bool DrawArmor(DesignBase design, EquipSlot slot, out Item armor)
|
||||
_weaponCombo.Add((FullEquipType.Unknown, EquipSlot.MainHand), new WeaponCombo(items, FullEquipType.Unknown, EquipSlot.MainHand));
|
||||
}
|
||||
|
||||
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()];
|
||||
armor = design.Armor(slot);
|
||||
var change = combo.Draw(armor.Name, armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
|
||||
armor = current;
|
||||
var change = combo.Draw(VerifyRestrictedGear(armor, slot, gender, race), armor.ItemId, 320 * ImGuiHelpers.GlobalScale);
|
||||
if (armor.ModelBase.Value != 0)
|
||||
{
|
||||
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
|
|
@ -41,6 +72,10 @@ public partial class EquipmentDrawer
|
|||
change = true;
|
||||
armor = ItemManager.NothingItem(slot);
|
||||
}
|
||||
else if (change)
|
||||
{
|
||||
armor = combo.CurrentSelection.WithStain(armor.Stain);
|
||||
}
|
||||
}
|
||||
else if (change)
|
||||
{
|
||||
|
|
@ -50,14 +85,77 @@ public partial class EquipmentDrawer
|
|||
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 armor = design.Armor(slot);
|
||||
var found = _stainData.TryGetValue(armor.Stain, out stain);
|
||||
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, found))
|
||||
var found = _stainData.TryGetValue(current, out stain);
|
||||
if (!_stainCombo.Draw($"##stain{slot}", stain.RgbaColor, stain.Name, found))
|
||||
return false;
|
||||
|
||||
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.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
|
@ -12,7 +8,6 @@ using OtterGui.Classes;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Item = Glamourer.Designs.Item;
|
||||
|
||||
namespace Glamourer.Gui.Equipment;
|
||||
|
|
@ -20,37 +15,38 @@ namespace Glamourer.Gui.Equipment;
|
|||
public sealed class ItemCombo : FilterComboCache<Item>
|
||||
{
|
||||
public readonly string Label;
|
||||
public readonly EquipSlot Slot;
|
||||
|
||||
private uint _lastItemId;
|
||||
private uint _previewId;
|
||||
private uint _currentItem;
|
||||
|
||||
public ItemCombo(ItemManager items, EquipSlot slot)
|
||||
: base(GetItems(items, slot))
|
||||
: base(() => GetItems(items, slot))
|
||||
{
|
||||
Label = GetLabel(slot);
|
||||
Slot = slot;
|
||||
_lastItemId = ItemManager.NothingId(slot);
|
||||
_previewId = _lastItemId;
|
||||
_currentItem = ItemManager.NothingId(slot);
|
||||
}
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
_previewId = previewIdx;
|
||||
return Draw(Label, previewName, width, ImGui.GetTextLineHeightWithSpacing());
|
||||
_currentItem = previewIdx;
|
||||
return Draw(Label, previewName, string.Empty, width, ImGui.GetTextLineHeightWithSpacing());
|
||||
}
|
||||
|
||||
protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||
|
|
@ -58,6 +54,7 @@ public sealed class ItemCombo : FilterComboCache<Item>
|
|||
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.Variant})");
|
||||
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);
|
||||
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.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
|
|
@ -20,12 +19,14 @@ internal partial class Interface
|
|||
private class ActorTab
|
||||
{
|
||||
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;
|
||||
_manipulations = manipulations;
|
||||
_activeDesigns = activeDesigns;
|
||||
_objects = objects;
|
||||
}
|
||||
|
||||
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||
|
|
@ -40,7 +41,7 @@ internal partial class Interface
|
|||
return;
|
||||
|
||||
DrawActorSelector();
|
||||
if (!ObjectManager.Actors.TryGetValue(_identifier, out _currentData))
|
||||
if (!_objects.TryGetValue(_identifier, out _currentData))
|
||||
_currentData = ObjectManager.ActorData.Invalid;
|
||||
else
|
||||
_currentLabel = _currentData.Label;
|
||||
|
|
@ -66,14 +67,31 @@ internal partial class Interface
|
|||
_currentSave.Update(_currentData.Objects[0]);
|
||||
|
||||
RevertButton();
|
||||
CustomizationDrawer.Draw(_currentSave.Customize(), _currentSave.Equipment(), _currentData.Objects,
|
||||
_identifier.Type == IdentifierType.Special);
|
||||
if (_main._customizationDrawer.Draw(_currentSave.Customize(), _identifier.Type == IdentifierType.Special))
|
||||
_activeDesigns.ChangeCustomize(_currentSave, _main._customizationDrawer.Changed, _main._customizationDrawer.CustomizeData, false);
|
||||
|
||||
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();
|
||||
_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,9 +101,7 @@ internal partial class Interface
|
|||
private unsafe void RevertButton()
|
||||
{
|
||||
if (ImGui.Button("Revert"))
|
||||
{
|
||||
_manipulations.DeleteSave(_identifier);
|
||||
|
||||
_activeDesigns.RevertDesign(_currentSave!);
|
||||
//foreach (var actor in _currentData.Objects)
|
||||
// _currentSave!.ApplyToActor(actor);
|
||||
//
|
||||
|
|
@ -93,8 +109,6 @@ internal partial class Interface
|
|||
// _currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
||||
//
|
||||
//_currentSave!.Reset();
|
||||
}
|
||||
|
||||
if (_currentData.Objects.Count > 0)
|
||||
ImGui.TextUnformatted(_currentData.Objects[0].Pointer->GameObject.DataID.ToString());
|
||||
//VisorBox();
|
||||
|
|
@ -215,24 +229,24 @@ internal partial class Interface
|
|||
if (!child)
|
||||
return;
|
||||
|
||||
ObjectManager.Update();
|
||||
_objects.Update();
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||
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());
|
||||
}
|
||||
|
||||
private bool CheckFilter((ActorIdentifier, ObjectManager.ActorData) pair)
|
||||
=> _actorFilter.IsEmpty || pair.Item2.Label.Contains(_actorFilter.Lower, StringComparison.OrdinalIgnoreCase);
|
||||
private bool CheckFilter(KeyValuePair<ActorIdentifier, ObjectManager.ActorData> pair)
|
||||
=> _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);
|
||||
if (ImGui.Selectable(pair.Item2.Label, equal) && !equal)
|
||||
var equal = pair.Key.Equals(_identifier);
|
||||
if (ImGui.Selectable(pair.Value.Label, equal) && !equal)
|
||||
{
|
||||
_identifier = pair.Item1.CreatePermanent();
|
||||
_currentData = pair.Item2;
|
||||
_currentSave = _currentData.Valid ? _manipulations.GetOrCreateSave(_currentData.Objects[0]) : null;
|
||||
_identifier = pair.Key.CreatePermanent();
|
||||
_currentData = pair.Value;
|
||||
_currentSave = _currentData.Valid ? _activeDesigns.GetOrCreateSave(_currentData.Objects[0]) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,325 +257,14 @@ internal partial class Interface
|
|||
var buttonWidth = new Vector2(_actorSelectorWidth / 2, 0);
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
||||
, "Select the local player character.", !ObjectManager.Player, true))
|
||||
_identifier = ObjectManager.Player.GetIdentifier();
|
||||
, "Select the local player character.", !_objects.Player, true))
|
||||
_identifier = _objects.Player.GetIdentifier();
|
||||
|
||||
ImGui.SameLine();
|
||||
Actor targetActor = Dalamud.Targets.Target?.Address;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -17,15 +15,15 @@ internal partial class Interface
|
|||
{
|
||||
private class DebugStateTab
|
||||
{
|
||||
private readonly CurrentManipulations _currentManipulations;
|
||||
private readonly ActiveDesign.Manager _activeDesigns;
|
||||
|
||||
private LowerString _manipulationFilter = LowerString.Empty;
|
||||
private ActorIdentifier _selection = ActorIdentifier.Invalid;
|
||||
private ActiveDesign? _save = null;
|
||||
private bool _delete = false;
|
||||
|
||||
public DebugStateTab(CurrentManipulations currentManipulations)
|
||||
=> _currentManipulations = currentManipulations;
|
||||
public DebugStateTab(ActiveDesign.Manager activeDesigns)
|
||||
=> _activeDesigns = activeDesigns;
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public void Draw()
|
||||
|
|
@ -43,7 +41,7 @@ internal partial class Interface
|
|||
if (_delete)
|
||||
{
|
||||
_delete = false;
|
||||
_currentManipulations.DeleteSave(_selection);
|
||||
_activeDesigns.DeleteSave(_selection);
|
||||
_selection = ActorIdentifier.Invalid;
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +54,7 @@ internal partial class Interface
|
|||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, oldSpacing);
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Designs;
|
||||
|
|
@ -37,9 +38,7 @@ internal partial class Interface
|
|||
{
|
||||
using var tab = ImRaii.TabItem("Designs");
|
||||
if (!tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Selector.Draw(GetDesignSelectorSize());
|
||||
ImGui.SameLine();
|
||||
|
|
@ -55,12 +54,25 @@ internal partial class Interface
|
|||
if (!child || Selector.Selected == null)
|
||||
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)
|
||||
{
|
||||
_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();
|
||||
_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;
|
||||
}
|
||||
|
||||
if (ImGui.Button(buttonLabel))
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
// TODO
|
||||
//if (ImGui.Button(buttonLabel))
|
||||
// Glamourer.Penumbra.Reattach(true);
|
||||
|
||||
ImGuiUtil.HoverTooltip(
|
||||
"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);
|
||||
Checkmark("Show Locks", "Write-protected Designs show a lock besides their name in the selector.", cfg.ShowLocks,
|
||||
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();
|
||||
|
||||
Checkmark("Apply Fixed Designs",
|
||||
|
|
|
|||
|
|
@ -1,32 +1,38 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Data;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface : Window, IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
|
||||
private readonly EquipmentDrawer _equipmentDrawer;
|
||||
private readonly CustomizationDrawer _customizationDrawer;
|
||||
|
||||
private readonly ActorTab _actorTab;
|
||||
private readonly DesignTab _designTab;
|
||||
private readonly DebugStateTab _debugStateTab;
|
||||
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())
|
||||
{
|
||||
_pi = pi;
|
||||
_equipmentDrawer = new EquipmentDrawer(items);
|
||||
_customizationDrawer = new CustomizationDrawer(pi);
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += Toggle;
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
|
|
@ -34,8 +40,8 @@ internal partial class Interface : Window, IDisposable
|
|||
MinimumSize = new Vector2(675, 675),
|
||||
MaximumSize = ImGui.GetIO().DisplaySize,
|
||||
};
|
||||
_actorTab = new ActorTab(this, manipulations);
|
||||
_debugStateTab = new DebugStateTab(manipulations);
|
||||
_actorTab = new ActorTab(this, activeDesigns, objects);
|
||||
_debugStateTab = new DebugStateTab(activeDesigns);
|
||||
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
||||
_designTab = new DesignTab(this, manager, fileSystem);
|
||||
}
|
||||
|
|
@ -67,8 +73,8 @@ internal partial class Interface : Window, IDisposable
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
|
||||
CustomizationDrawer.Dispose();
|
||||
_pi.UiBuilder.OpenConfigUi -= Toggle;
|
||||
_customizationDrawer.Dispose();
|
||||
_designTab.Dispose();
|
||||
}
|
||||
|
||||
|
|
@ -77,90 +83,3 @@ internal partial class Interface : Window, IDisposable
|
|||
? "Glamourer###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
|
||||
=> Pointer != null;
|
||||
|
||||
public int Index
|
||||
=> Pointer->GameObject.ObjectIndex;
|
||||
|
||||
public uint ModelId
|
||||
{
|
||||
get => (uint)Pointer->ModelCharaId;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Penumbra.GameData.Actors;
|
||||
using static Glamourer.Interop.Actor;
|
||||
|
||||
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 List<Actor> Objects;
|
||||
|
|
@ -40,28 +33,22 @@ public static class ObjectManager
|
|||
}
|
||||
}
|
||||
|
||||
public static bool IsInGPose { get; private set; }
|
||||
public static ushort World { get; private set; }
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
||||
public static IReadOnlyDictionary<ActorIdentifier, ActorData> Actors
|
||||
=> Identifiers;
|
||||
public bool IsInGPose { get; private set; }
|
||||
public ushort World { get; private set; }
|
||||
|
||||
public static IReadOnlyList<(ActorIdentifier, ActorData)> List
|
||||
=> ListData;
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||
|
||||
private static readonly Dictionary<ActorIdentifier, ActorData> Identifiers = new(200);
|
||||
private static readonly List<(ActorIdentifier, ActorData)> ListData = new(Dalamud.Objects.Length);
|
||||
|
||||
private static void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
||||
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
||||
{
|
||||
if (!character.DrawObject || !identifier.IsValid)
|
||||
return;
|
||||
|
||||
if (!Identifiers.TryGetValue(identifier, out var data))
|
||||
if (!_identifiers.TryGetValue(identifier, out var data))
|
||||
{
|
||||
data = new ActorData(character, identifier.ToString());
|
||||
Identifiers[identifier] = data;
|
||||
ListData.Add((identifier, data));
|
||||
_identifiers[identifier] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -69,88 +56,100 @@ public static class ObjectManager
|
|||
}
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
World = (ushort)(Dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||
Identifiers.Clear();
|
||||
ListData.Clear();
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
|
||||
for (var i = 0; i < CutsceneIndex; ++i)
|
||||
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects)
|
||||
{
|
||||
Actor character = Dalamud.Objects.GetObjectAddress(i);
|
||||
_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 = _objects.GetObjectAddress(i);
|
||||
if (character.Identifier(out var identifier))
|
||||
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))
|
||||
break;
|
||||
|
||||
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))
|
||||
{
|
||||
var data = new ActorData(actor, label);
|
||||
Identifiers.Add(ident, data);
|
||||
ListData.Add((ident, data));
|
||||
_identifiers.Add(ident, data);
|
||||
}
|
||||
}
|
||||
|
||||
AddSpecial(CharacterScreenIndex, "Character Screen Actor");
|
||||
AddSpecial(ExamineScreenIndex, "Examine Screen Actor");
|
||||
AddSpecial(FittingRoomIndex, "Fitting Room Actor");
|
||||
AddSpecial(DyePreviewIndex, "Dye Preview Actor");
|
||||
AddSpecial(PortraitIndex, "Portrait Actor");
|
||||
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
|
||||
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
|
||||
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
|
||||
AddSpecial(ScreenActor.DyePreview, "Dye Preview 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))
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
|
||||
Actor gPose = Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
|
||||
var gPose = GPosePlayer;
|
||||
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
|
||||
}
|
||||
|
||||
public static Actor GPosePlayer
|
||||
=> Dalamud.Objects.GetObjectAddress(GPosePlayerIndex);
|
||||
public Actor GPosePlayer
|
||||
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
|
||||
|
||||
public static Actor Player
|
||||
=> Dalamud.Objects.GetObjectAddress(0);
|
||||
public Actor Player
|
||||
=> _objects.GetObjectAddress(0);
|
||||
|
||||
private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose)
|
||||
{
|
||||
var sb = new StringBuilder(64);
|
||||
sb.Append(playerName);
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
||||
=> _identifiers.GetEnumerator();
|
||||
|
||||
if (gPose)
|
||||
{
|
||||
sb.Append(" (GPose");
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
if (player.ObjectKind == ObjectKind.Player)
|
||||
sb.Append(')');
|
||||
else
|
||||
sb.Append(player.ModelId == 0 ? ", NPC)" : ", Monster)");
|
||||
}
|
||||
else if (player.ObjectKind != ObjectKind.Player)
|
||||
{
|
||||
sb.Append(player.ModelId == 0 ? " (NPC)" : " (Monster)");
|
||||
}
|
||||
public int Count
|
||||
=> _identifiers.Count;
|
||||
|
||||
if (num > 1)
|
||||
{
|
||||
sb.Append(" #");
|
||||
sb.Append(num);
|
||||
}
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> _identifiers.ContainsKey(key);
|
||||
|
||||
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
|
||||
{
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -69,16 +69,4 @@ public unsafe partial class RedrawManager
|
|||
//
|
||||
//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,
|
||||
};
|
||||
}
|
||||
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}.");
|
||||
//switch (slot)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,104 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class DesignBaseValidator
|
||||
public partial class Interop : IDisposable
|
||||
{
|
||||
private readonly CustomizationManager _manager;
|
||||
private readonly RestrictedGear _restrictedGear;
|
||||
|
||||
public DesignBaseValidator(CustomizationManager manager, RestrictedGear restrictedGear)
|
||||
public Interop()
|
||||
{
|
||||
_manager = manager;
|
||||
_restrictedGear = restrictedGear;
|
||||
SignatureHelper.Initialise(this);
|
||||
_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);
|
||||
|
||||
|
|
@ -44,27 +117,21 @@ public unsafe partial class RedrawManager
|
|||
public unsafe partial class RedrawManager : IDisposable
|
||||
{
|
||||
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);
|
||||
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
|
||||
_fixedDesigns = fixedDesigns;
|
||||
_currentManipulations = currentManipulations;
|
||||
_stateManager = stateManager;
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
_loadWeaponHook.Enable();
|
||||
_changeJobHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_flagSlotForUpdateHook.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)
|
||||
|
|
@ -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.
|
||||
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;
|
||||
|
||||
// Compare game object customize data against draw object customize data for transformations.
|
||||
|
|
|
|||
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.Interop;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public sealed partial class ActiveDesign : DesignBase
|
||||
{
|
||||
public readonly ActorIdentifier Identifier;
|
||||
|
||||
private CharacterData _initialData = new();
|
||||
|
||||
private CustomizeFlag _changedCustomize;
|
||||
private CustomizeFlag _fixedCustomize;
|
||||
public CustomizeFlag ChangedCustomize { get; private set; } = 0;
|
||||
public CustomizeFlag FixedCustomize { get; private set; } = 0;
|
||||
|
||||
private EquipFlag _changedEquip;
|
||||
private EquipFlag _fixedEquip;
|
||||
public EquipFlag ChangedEquip { get; private set; } = 0;
|
||||
public EquipFlag FixedEquip { get; private set; } = 0;
|
||||
|
||||
public bool IsHatVisible { get; private set; } = false;
|
||||
public bool IsWeaponVisible { get; private set; } = false;
|
||||
public bool IsVisorToggled { 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);
|
||||
}
|
||||
|
||||
|
||||
//public void ApplyToActor(Actor actor)
|
||||
//{
|
||||
// if (!actor)
|
||||
|
|
@ -72,14 +77,14 @@ public sealed partial class ActiveDesign : DesignBase
|
|||
|
||||
var initialEquip = _initialData.Equipment;
|
||||
var currentEquip = actor.Equip;
|
||||
var equipment = Equipment();
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
var current = currentEquip[slot];
|
||||
if (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;
|
||||
UpdateMainhand(actor.MainHand);
|
||||
SetStain(EquipSlot.MainHand, actor.MainHand.Stain);
|
||||
}
|
||||
|
||||
if (_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;
|
||||
|
||||
public static unsafe class CustomizeExtensions
|
||||
public static class CustomizeExtensions
|
||||
{
|
||||
// In languages other than english the actual clan name may depend on 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.
|
||||
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)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
FixRestrictedGear(customize, equip, gender, customize.Race);
|
||||
customize.Gender = gender;
|
||||
FixUpAttributes(customize);
|
||||
return true;
|
||||
return CustomizeFlag.Gender | FixUpAttributes(customize);
|
||||
}
|
||||
|
||||
// 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)
|
||||
return false;
|
||||
return 0;
|
||||
|
||||
var race = clan.ToRace();
|
||||
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
|
||||
FixRestrictedGear(customize, equip, gender, race);
|
||||
var flags = CustomizeFlag.Race | CustomizeFlag.Clan;
|
||||
if (gender != customize.Gender)
|
||||
flags |= CustomizeFlag.Gender;
|
||||
customize.Gender = gender;
|
||||
customize.Race = race;
|
||||
customize.Clan = clan;
|
||||
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;
|
||||
return flags | FixUpAttributes(customize);
|
||||
}
|
||||
|
||||
// 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);
|
||||
CustomizeFlag flags = 0;
|
||||
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
|
||||
{
|
||||
switch (id)
|
||||
|
|
@ -122,10 +103,16 @@ public static unsafe class CustomizeExtensions
|
|||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
|
||||
{
|
||||
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
|
||||
flags |= id.ToFlag();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue