mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Some cleanup, use OtterGui.
This commit is contained in:
parent
a36d1f1935
commit
0fc8992271
40 changed files with 2686 additions and 2792 deletions
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "OtterGui"]
|
||||||
|
path = OtterGui
|
||||||
|
url = git@github.com:Ottermandias/OtterGui.git
|
||||||
|
branch = main
|
||||||
|
|
@ -3,166 +3,165 @@ using System.ComponentModel;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public static class WriteExtensions
|
||||||
{
|
{
|
||||||
public static class WriteExtensions
|
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||||
{
|
{
|
||||||
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
void WriteWeapon(int offset)
|
||||||
{
|
{
|
||||||
void WriteWeapon(int offset)
|
var address = (byte*)characterPtr + offset;
|
||||||
{
|
if (id.HasValue)
|
||||||
var address = (byte*) characterPtr + offset;
|
*(ushort*)address = (ushort)id.Value;
|
||||||
if (id.HasValue)
|
|
||||||
*(ushort*) address = (ushort) id.Value;
|
|
||||||
|
|
||||||
if (type.HasValue)
|
if (type.HasValue)
|
||||||
*(ushort*) (address + 2) = (ushort) type.Value;
|
*(ushort*)(address + 2) = (ushort)type.Value;
|
||||||
|
|
||||||
if (variant.HasValue)
|
if (variant.HasValue)
|
||||||
*(ushort*) (address + 4) = variant.Value;
|
*(ushort*)(address + 4) = variant.Value;
|
||||||
|
|
||||||
if (stain.HasValue)
|
if (stain.HasValue)
|
||||||
*(address + 6) = (byte) stain.Value;
|
*(address + 6) = (byte)stain.Value;
|
||||||
}
|
|
||||||
|
|
||||||
void WriteEquip(int offset)
|
|
||||||
{
|
|
||||||
var address = (byte*) characterPtr + offset;
|
|
||||||
if (id.HasValue)
|
|
||||||
*(ushort*) address = (ushort) id.Value;
|
|
||||||
|
|
||||||
if (variant < byte.MaxValue)
|
|
||||||
*(address + 2) = (byte) variant.Value;
|
|
||||||
|
|
||||||
if (stain.HasValue)
|
|
||||||
*(address + 3) = (byte) stain.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (slot)
|
|
||||||
{
|
|
||||||
case EquipSlot.MainHand:
|
|
||||||
WriteWeapon(CharacterEquipment.MainWeaponOffset);
|
|
||||||
break;
|
|
||||||
case EquipSlot.OffHand:
|
|
||||||
WriteWeapon(CharacterEquipment.OffWeaponOffset);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Head:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Body:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 4);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Hands:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 8);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Legs:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 12);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Feet:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 16);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Ears:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 20);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Neck:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 24);
|
|
||||||
break;
|
|
||||||
case EquipSlot.Wrists:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 28);
|
|
||||||
break;
|
|
||||||
case EquipSlot.RFinger:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 32);
|
|
||||||
break;
|
|
||||||
case EquipSlot.LFinger:
|
|
||||||
WriteEquip(CharacterEquipment.EquipmentOffset + 36);
|
|
||||||
break;
|
|
||||||
default: throw new InvalidEnumArgumentException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(this Stain stain, IntPtr characterPtr, EquipSlot slot)
|
void WriteEquip(int offset)
|
||||||
=> Write(characterPtr, slot, null, null, null, stain.RowIndex);
|
|
||||||
|
|
||||||
public static void Write(this Item item, IntPtr characterAddress)
|
|
||||||
{
|
{
|
||||||
var (id, type, variant) = item.MainModel;
|
var address = (uint*)characterPtr + offset;
|
||||||
Write(characterAddress, item.EquippableTo, id, type, variant, null);
|
if (id.HasValue)
|
||||||
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
*(ushort*)address = (ushort)id.Value;
|
||||||
{
|
|
||||||
var (subId, subType, subVariant) = item.SubModel;
|
if (variant < byte.MaxValue)
|
||||||
Write(characterAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
|
*(address + 2) = (byte)variant.Value;
|
||||||
}
|
|
||||||
|
if (stain.HasValue)
|
||||||
|
*(address + 3) = (byte)stain.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Write(this CharacterArmor armor, IntPtr characterAddress, EquipSlot slot)
|
switch (slot)
|
||||||
=> Write(characterAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
|
|
||||||
|
|
||||||
public static void Write(this CharacterWeapon weapon, IntPtr characterAddress, EquipSlot slot)
|
|
||||||
=> Write(characterAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
|
|
||||||
|
|
||||||
public static unsafe void Write(this CharacterEquipment equip, IntPtr characterAddress)
|
|
||||||
{
|
{
|
||||||
if (equip.IsSet == 0)
|
case EquipSlot.MainHand:
|
||||||
return;
|
WriteWeapon(CharacterEquipment.MainWeaponOffset);
|
||||||
|
break;
|
||||||
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
|
case EquipSlot.OffHand:
|
||||||
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
|
WriteWeapon(CharacterEquipment.OffWeaponOffset);
|
||||||
|
break;
|
||||||
fixed (CharacterArmor* equipment = &equip.Head)
|
case EquipSlot.Head:
|
||||||
{
|
WriteEquip(CharacterEquipment.EquipmentOffset);
|
||||||
Buffer.MemoryCopy(equipment, (byte*) characterAddress + CharacterEquipment.EquipmentOffset,
|
break;
|
||||||
CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor), CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor));
|
case EquipSlot.Body:
|
||||||
}
|
WriteEquip(CharacterEquipment.EquipmentOffset + 4);
|
||||||
}
|
break;
|
||||||
|
case EquipSlot.Hands:
|
||||||
public static void Write(this CharacterEquipment equip, IntPtr characterAddress, CharacterEquipMask models, CharacterEquipMask stains)
|
WriteEquip(CharacterEquipment.EquipmentOffset + 8);
|
||||||
{
|
break;
|
||||||
if (models == CharacterEquipMask.All && stains == CharacterEquipMask.All)
|
case EquipSlot.Legs:
|
||||||
{
|
WriteEquip(CharacterEquipment.EquipmentOffset + 12);
|
||||||
equip.Write(characterAddress);
|
break;
|
||||||
return;
|
case EquipSlot.Feet:
|
||||||
}
|
WriteEquip(CharacterEquipment.EquipmentOffset + 16);
|
||||||
|
break;
|
||||||
if (models.HasFlag(CharacterEquipMask.MainHand))
|
case EquipSlot.Ears:
|
||||||
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
|
WriteEquip(CharacterEquipment.EquipmentOffset + 20);
|
||||||
if (stains.HasFlag(CharacterEquipMask.MainHand))
|
break;
|
||||||
Write(characterAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
|
case EquipSlot.Neck:
|
||||||
if (models.HasFlag(CharacterEquipMask.OffHand))
|
WriteEquip(CharacterEquipment.EquipmentOffset + 24);
|
||||||
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
|
break;
|
||||||
if (stains.HasFlag(CharacterEquipMask.OffHand))
|
case EquipSlot.Wrists:
|
||||||
Write(characterAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
|
WriteEquip(CharacterEquipment.EquipmentOffset + 28);
|
||||||
|
break;
|
||||||
if (models.HasFlag(CharacterEquipMask.Head))
|
case EquipSlot.RFinger:
|
||||||
Write(characterAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
|
WriteEquip(CharacterEquipment.EquipmentOffset + 32);
|
||||||
if (stains.HasFlag(CharacterEquipMask.Head))
|
break;
|
||||||
Write(characterAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
|
case EquipSlot.LFinger:
|
||||||
if (models.HasFlag(CharacterEquipMask.Body))
|
WriteEquip(CharacterEquipment.EquipmentOffset + 36);
|
||||||
Write(characterAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
|
break;
|
||||||
if (stains.HasFlag(CharacterEquipMask.Body))
|
default: throw new InvalidEnumArgumentException();
|
||||||
Write(characterAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Hands))
|
|
||||||
Write(characterAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
|
|
||||||
if (stains.HasFlag(CharacterEquipMask.Hands))
|
|
||||||
Write(characterAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Legs))
|
|
||||||
Write(characterAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
|
|
||||||
if (stains.HasFlag(CharacterEquipMask.Legs))
|
|
||||||
Write(characterAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Feet))
|
|
||||||
Write(characterAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
|
|
||||||
if (stains.HasFlag(CharacterEquipMask.Feet))
|
|
||||||
Write(characterAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
|
|
||||||
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Ears))
|
|
||||||
Write(characterAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Neck))
|
|
||||||
Write(characterAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.Wrists))
|
|
||||||
Write(characterAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.LFinger))
|
|
||||||
Write(characterAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
|
|
||||||
if (models.HasFlag(CharacterEquipMask.RFinger))
|
|
||||||
Write(characterAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Write(this Stain stain, IntPtr characterPtr, EquipSlot slot)
|
||||||
|
=> Write(characterPtr, slot, null, null, null, stain.RowIndex);
|
||||||
|
|
||||||
|
public static void Write(this Item item, IntPtr characterAddress)
|
||||||
|
{
|
||||||
|
var (id, type, variant) = item.MainModel;
|
||||||
|
Write(characterAddress, item.EquippableTo, id, type, variant, null);
|
||||||
|
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
||||||
|
{
|
||||||
|
var (subId, subType, subVariant) = item.SubModel;
|
||||||
|
Write(characterAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this CharacterArmor armor, IntPtr characterAddress, EquipSlot slot)
|
||||||
|
=> Write(characterAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
|
||||||
|
|
||||||
|
public static void Write(this CharacterWeapon weapon, IntPtr characterAddress, EquipSlot slot)
|
||||||
|
=> Write(characterAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
|
||||||
|
|
||||||
|
public static unsafe void Write(this CharacterEquipment equip, IntPtr characterAddress)
|
||||||
|
{
|
||||||
|
if (equip.IsSet == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
|
||||||
|
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
|
||||||
|
|
||||||
|
fixed (CharacterArmor* equipment = &equip.Head)
|
||||||
|
{
|
||||||
|
Buffer.MemoryCopy(equipment, (byte*)characterAddress + CharacterEquipment.EquipmentOffset,
|
||||||
|
CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor), CharacterEquipment.EquipmentSlots * sizeof(CharacterArmor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this CharacterEquipment equip, IntPtr characterAddress, CharacterEquipMask models, CharacterEquipMask stains)
|
||||||
|
{
|
||||||
|
if (models == CharacterEquipMask.All && stains == CharacterEquipMask.All)
|
||||||
|
{
|
||||||
|
equip.Write(characterAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.HasFlag(CharacterEquipMask.MainHand))
|
||||||
|
Write(characterAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.MainHand))
|
||||||
|
Write(characterAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.OffHand))
|
||||||
|
Write(characterAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.OffHand))
|
||||||
|
Write(characterAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
|
||||||
|
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Head))
|
||||||
|
Write(characterAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.Head))
|
||||||
|
Write(characterAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Body))
|
||||||
|
Write(characterAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.Body))
|
||||||
|
Write(characterAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Hands))
|
||||||
|
Write(characterAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.Hands))
|
||||||
|
Write(characterAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Legs))
|
||||||
|
Write(characterAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.Legs))
|
||||||
|
Write(characterAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Feet))
|
||||||
|
Write(characterAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
|
||||||
|
if (stains.HasFlag(CharacterEquipMask.Feet))
|
||||||
|
Write(characterAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
|
||||||
|
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Ears))
|
||||||
|
Write(characterAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Neck))
|
||||||
|
Write(characterAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.Wrists))
|
||||||
|
Write(characterAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.LFinger))
|
||||||
|
Write(characterAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
|
||||||
|
if (models.HasFlag(CharacterEquipMask.RFinger))
|
||||||
|
Write(characterAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum CharacterEquipMask : ushort
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
MainHand = 0b000000000001,
|
|
||||||
OffHand = 0b000000000010,
|
|
||||||
Head = 0b000000000100,
|
|
||||||
Body = 0b000000001000,
|
|
||||||
Hands = 0b000000010000,
|
|
||||||
Legs = 0b000000100000,
|
|
||||||
Feet = 0b000001000000,
|
|
||||||
Ears = 0b000010000000,
|
|
||||||
Neck = 0b000100000000,
|
|
||||||
Wrists = 0b001000000000,
|
|
||||||
RFinger = 0b010000000000,
|
|
||||||
LFinger = 0b100000000000,
|
|
||||||
All = 0b111111111111,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,304 +1,323 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Customization
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
public unsafe struct LazyCustomization
|
||||||
{
|
{
|
||||||
public unsafe struct LazyCustomization
|
public CharacterCustomization* Address;
|
||||||
|
|
||||||
|
public LazyCustomization(IntPtr characterPtr)
|
||||||
|
=> Address = (CharacterCustomization*)(characterPtr + CharacterCustomization.CustomizationOffset);
|
||||||
|
|
||||||
|
public ref CharacterCustomization Value
|
||||||
|
=> ref *Address;
|
||||||
|
|
||||||
|
public LazyCustomization(CharacterCustomization data)
|
||||||
|
=> Address = &data;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct CharacterCustomization
|
||||||
|
{
|
||||||
|
public const int CustomizationOffset = 0x830;
|
||||||
|
public const int CustomizationBytes = 26;
|
||||||
|
|
||||||
|
public static CharacterCustomization Default = new()
|
||||||
{
|
{
|
||||||
public CharacterCustomization* Address;
|
Race = Race.Hyur,
|
||||||
|
Gender = Gender.Male,
|
||||||
|
BodyType = 1,
|
||||||
|
Height = 50,
|
||||||
|
Clan = SubRace.Midlander,
|
||||||
|
Face = 1,
|
||||||
|
Hairstyle = 1,
|
||||||
|
HighlightsOn = false,
|
||||||
|
SkinColor = 1,
|
||||||
|
EyeColorRight = 1,
|
||||||
|
HighlightsColor = 1,
|
||||||
|
FacialFeatures = 0,
|
||||||
|
TattooColor = 1,
|
||||||
|
Eyebrow = 1,
|
||||||
|
EyeColorLeft = 1,
|
||||||
|
EyeShape = 1,
|
||||||
|
Nose = 1,
|
||||||
|
Jaw = 1,
|
||||||
|
Mouth = 1,
|
||||||
|
LipColor = 1,
|
||||||
|
MuscleMass = 50,
|
||||||
|
TailShape = 1,
|
||||||
|
BustSize = 50,
|
||||||
|
FacePaint = 1,
|
||||||
|
FacePaintColor = 1,
|
||||||
|
};
|
||||||
|
|
||||||
public LazyCustomization(IntPtr characterPtr)
|
public Race Race;
|
||||||
=> Address = (CharacterCustomization*) (characterPtr + CharacterCustomization.CustomizationOffset);
|
private byte _gender;
|
||||||
|
public byte BodyType;
|
||||||
|
public byte Height;
|
||||||
|
public SubRace Clan;
|
||||||
|
public byte Face;
|
||||||
|
public byte Hairstyle;
|
||||||
|
private byte _highlightsOn;
|
||||||
|
public byte SkinColor;
|
||||||
|
public byte EyeColorRight;
|
||||||
|
public byte HairColor;
|
||||||
|
public byte HighlightsColor;
|
||||||
|
public byte FacialFeatures;
|
||||||
|
public byte TattooColor;
|
||||||
|
public byte Eyebrow;
|
||||||
|
public byte EyeColorLeft;
|
||||||
|
private byte _eyeShape;
|
||||||
|
public byte Nose;
|
||||||
|
public byte Jaw;
|
||||||
|
private byte _mouth;
|
||||||
|
public byte LipColor;
|
||||||
|
public byte MuscleMass;
|
||||||
|
public byte TailShape;
|
||||||
|
public byte BustSize;
|
||||||
|
private byte _facePaint;
|
||||||
|
public byte FacePaintColor;
|
||||||
|
|
||||||
public ref CharacterCustomization Value
|
public Gender Gender
|
||||||
=> ref *Address;
|
{
|
||||||
|
get => (Gender)(_gender + 1);
|
||||||
|
set => _gender = (byte)(value - 1);
|
||||||
|
}
|
||||||
|
|
||||||
public LazyCustomization(CharacterCustomization data)
|
public bool HighlightsOn
|
||||||
=> Address = &data;
|
{
|
||||||
|
get => (_highlightsOn & 128) == 128;
|
||||||
|
set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FacialFeature(int idx)
|
||||||
|
=> (FacialFeatures & (1 << idx)) != 0;
|
||||||
|
|
||||||
|
public void FacialFeature(int idx, bool set)
|
||||||
|
{
|
||||||
|
if (set)
|
||||||
|
FacialFeatures |= (byte)(1 << idx);
|
||||||
|
else
|
||||||
|
FacialFeatures &= (byte)~(1 << idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte EyeShape
|
||||||
|
{
|
||||||
|
get => (byte)(_eyeShape & 127);
|
||||||
|
set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SmallIris
|
||||||
|
{
|
||||||
|
get => (_eyeShape & 128) == 128;
|
||||||
|
set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
public byte Mouth
|
||||||
public struct CharacterCustomization
|
|
||||||
{
|
{
|
||||||
public const int CustomizationOffset = 0x830;
|
get => (byte)(_mouth & 127);
|
||||||
public const int CustomizationBytes = 26;
|
set => _mouth = (byte)((value & 127) | (_mouth & 128));
|
||||||
|
}
|
||||||
|
|
||||||
public static CharacterCustomization Default = new()
|
public bool Lipstick
|
||||||
|
{
|
||||||
|
get => (_mouth & 128) == 128;
|
||||||
|
set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte FacePaint
|
||||||
|
{
|
||||||
|
get => (byte)(_facePaint & 127);
|
||||||
|
set => _facePaint = (byte)((value & 127) | (_facePaint & 128));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool FacePaintReversed
|
||||||
|
{
|
||||||
|
get => (_facePaint & 128) == 128;
|
||||||
|
set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Read(IntPtr customizeAddress)
|
||||||
|
{
|
||||||
|
fixed (Race* ptr = &Race)
|
||||||
{
|
{
|
||||||
Race = Race.Hyur,
|
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
||||||
Gender = Gender.Male,
|
}
|
||||||
BodyType = 1,
|
}
|
||||||
Height = 50,
|
|
||||||
Clan = SubRace.Midlander,
|
public unsafe void Read(Customization* customize)
|
||||||
Face = 1,
|
=> Read((IntPtr)customize);
|
||||||
Hairstyle = 1,
|
|
||||||
HighlightsOn = false,
|
public void Read(Character character)
|
||||||
SkinColor = 1,
|
=> Read(character.Address + CustomizationOffset);
|
||||||
EyeColorRight = 1,
|
|
||||||
HighlightsColor = 1,
|
public CharacterCustomization(Character character)
|
||||||
FacialFeatures = 0,
|
: this()
|
||||||
TattooColor = 1,
|
{
|
||||||
Eyebrow = 1,
|
Read(character.Address + CustomizationOffset);
|
||||||
EyeColorLeft = 1,
|
}
|
||||||
EyeShape = 1,
|
|
||||||
Nose = 1,
|
public byte this[CustomizationId id]
|
||||||
Jaw = 1,
|
{
|
||||||
Mouth = 1,
|
get => id switch
|
||||||
LipColor = 1,
|
{
|
||||||
MuscleMass = 50,
|
CustomizationId.Race => (byte)Race,
|
||||||
TailShape = 1,
|
CustomizationId.Gender => (byte)Gender,
|
||||||
BustSize = 50,
|
CustomizationId.BodyType => BodyType,
|
||||||
FacePaint = 1,
|
CustomizationId.Height => Height,
|
||||||
FacePaintColor = 1,
|
CustomizationId.Clan => (byte)Clan,
|
||||||
|
CustomizationId.Face => Face,
|
||||||
|
CustomizationId.Hairstyle => Hairstyle,
|
||||||
|
CustomizationId.HighlightsOnFlag => _highlightsOn,
|
||||||
|
CustomizationId.SkinColor => SkinColor,
|
||||||
|
CustomizationId.EyeColorR => EyeColorRight,
|
||||||
|
CustomizationId.HairColor => HairColor,
|
||||||
|
CustomizationId.HighlightColor => HighlightsColor,
|
||||||
|
CustomizationId.FacialFeaturesTattoos => FacialFeatures,
|
||||||
|
CustomizationId.TattooColor => TattooColor,
|
||||||
|
CustomizationId.Eyebrows => Eyebrow,
|
||||||
|
CustomizationId.EyeColorL => EyeColorLeft,
|
||||||
|
CustomizationId.EyeShape => EyeShape,
|
||||||
|
CustomizationId.Nose => Nose,
|
||||||
|
CustomizationId.Jaw => Jaw,
|
||||||
|
CustomizationId.Mouth => Mouth,
|
||||||
|
CustomizationId.LipColor => LipColor,
|
||||||
|
CustomizationId.MuscleToneOrTailEarLength => MuscleMass,
|
||||||
|
CustomizationId.TailEarShape => TailShape,
|
||||||
|
CustomizationId.BustSize => BustSize,
|
||||||
|
CustomizationId.FacePaint => FacePaint,
|
||||||
|
CustomizationId.FacePaintColor => FacePaintColor,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||||
};
|
};
|
||||||
|
set
|
||||||
public Race Race;
|
|
||||||
private byte _gender;
|
|
||||||
public byte BodyType;
|
|
||||||
public byte Height;
|
|
||||||
public SubRace Clan;
|
|
||||||
public byte Face;
|
|
||||||
public byte Hairstyle;
|
|
||||||
private byte _highlightsOn;
|
|
||||||
public byte SkinColor;
|
|
||||||
public byte EyeColorRight;
|
|
||||||
public byte HairColor;
|
|
||||||
public byte HighlightsColor;
|
|
||||||
public byte FacialFeatures;
|
|
||||||
public byte TattooColor;
|
|
||||||
public byte Eyebrow;
|
|
||||||
public byte EyeColorLeft;
|
|
||||||
private byte _eyeShape;
|
|
||||||
public byte Nose;
|
|
||||||
public byte Jaw;
|
|
||||||
private byte _mouth;
|
|
||||||
public byte LipColor;
|
|
||||||
public byte MuscleMass;
|
|
||||||
public byte TailShape;
|
|
||||||
public byte BustSize;
|
|
||||||
private byte _facePaint;
|
|
||||||
public byte FacePaintColor;
|
|
||||||
|
|
||||||
public Gender Gender
|
|
||||||
{
|
{
|
||||||
get => (Gender) (_gender + 1);
|
switch (id)
|
||||||
set => _gender = (byte) (value - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HighlightsOn
|
|
||||||
{
|
|
||||||
get => (_highlightsOn & 128) == 128;
|
|
||||||
set => _highlightsOn = (byte) (value ? _highlightsOn | 128 : _highlightsOn & 127);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FacialFeature(int idx)
|
|
||||||
=> (FacialFeatures & (1 << idx)) != 0;
|
|
||||||
|
|
||||||
public void FacialFeature(int idx, bool set)
|
|
||||||
{
|
|
||||||
if (set)
|
|
||||||
FacialFeatures |= (byte) (1 << idx);
|
|
||||||
else
|
|
||||||
FacialFeatures &= (byte) ~(1 << idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte EyeShape
|
|
||||||
{
|
|
||||||
get => (byte) (_eyeShape & 127);
|
|
||||||
set => _eyeShape = (byte) ((value & 127) | (_eyeShape & 128));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool SmallIris
|
|
||||||
{
|
|
||||||
get => (_eyeShape & 128) == 128;
|
|
||||||
set => _eyeShape = (byte) (value ? _eyeShape | 128 : _eyeShape & 127);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public byte Mouth
|
|
||||||
{
|
|
||||||
get => (byte) (_mouth & 127);
|
|
||||||
set => _mouth = (byte) ((value & 127) | (_mouth & 128));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Lipstick
|
|
||||||
{
|
|
||||||
get => (_mouth & 128) == 128;
|
|
||||||
set => _mouth = (byte) (value ? _mouth | 128 : _mouth & 127);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte FacePaint
|
|
||||||
{
|
|
||||||
get => (byte) (_facePaint & 127);
|
|
||||||
set => _facePaint = (byte) ((value & 127) | (_facePaint & 128));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FacePaintReversed
|
|
||||||
{
|
|
||||||
get => (_facePaint & 128) == 128;
|
|
||||||
set => _facePaint = (byte) (value ? _facePaint | 128 : _facePaint & 127);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void Read(IntPtr customizeAddress)
|
|
||||||
{
|
|
||||||
fixed (Race* ptr = &Race)
|
|
||||||
{
|
{
|
||||||
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
case CustomizationId.Race:
|
||||||
|
Race = (Race)value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Gender:
|
||||||
|
Gender = (Gender)value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.BodyType:
|
||||||
|
BodyType = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Height:
|
||||||
|
Height = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Clan:
|
||||||
|
Clan = (SubRace)value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Face:
|
||||||
|
Face = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Hairstyle:
|
||||||
|
Hairstyle = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.HighlightsOnFlag:
|
||||||
|
HighlightsOn = (value & 128) == 128;
|
||||||
|
break;
|
||||||
|
case CustomizationId.SkinColor:
|
||||||
|
SkinColor = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.EyeColorR:
|
||||||
|
EyeColorRight = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.HairColor:
|
||||||
|
HairColor = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.HighlightColor:
|
||||||
|
HighlightsColor = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.FacialFeaturesTattoos:
|
||||||
|
FacialFeatures = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.TattooColor:
|
||||||
|
TattooColor = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Eyebrows:
|
||||||
|
Eyebrow = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.EyeColorL:
|
||||||
|
EyeColorLeft = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.EyeShape:
|
||||||
|
EyeShape = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Nose:
|
||||||
|
Nose = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Jaw:
|
||||||
|
Jaw = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.Mouth:
|
||||||
|
Mouth = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.LipColor:
|
||||||
|
LipColor = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.MuscleToneOrTailEarLength:
|
||||||
|
MuscleMass = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.TailEarShape:
|
||||||
|
TailShape = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.BustSize:
|
||||||
|
BustSize = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.FacePaint:
|
||||||
|
FacePaint = value;
|
||||||
|
break;
|
||||||
|
case CustomizationId.FacePaintColor:
|
||||||
|
FacePaintColor = value;
|
||||||
|
break;
|
||||||
|
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Read(Character character)
|
public unsafe void Write(IntPtr characterAddress)
|
||||||
=> Read(character.Address + CustomizationOffset);
|
{
|
||||||
|
fixed (Race* ptr = &Race)
|
||||||
public CharacterCustomization(Character character)
|
|
||||||
: this()
|
|
||||||
{
|
{
|
||||||
Read(character.Address + CustomizationOffset);
|
Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public byte this[CustomizationId id]
|
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||||
|
{
|
||||||
|
fixed (Race* ptr = &Race)
|
||||||
{
|
{
|
||||||
get => id switch
|
Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes);
|
||||||
{
|
|
||||||
CustomizationId.Race => (byte) Race,
|
|
||||||
CustomizationId.Gender => (byte) Gender,
|
|
||||||
CustomizationId.BodyType => BodyType,
|
|
||||||
CustomizationId.Height => Height,
|
|
||||||
CustomizationId.Clan => (byte) Clan,
|
|
||||||
CustomizationId.Face => Face,
|
|
||||||
CustomizationId.Hairstyle => Hairstyle,
|
|
||||||
CustomizationId.HighlightsOnFlag => _highlightsOn,
|
|
||||||
CustomizationId.SkinColor => SkinColor,
|
|
||||||
CustomizationId.EyeColorR => EyeColorRight,
|
|
||||||
CustomizationId.HairColor => HairColor,
|
|
||||||
CustomizationId.HighlightColor => HighlightsColor,
|
|
||||||
CustomizationId.FacialFeaturesTattoos => FacialFeatures,
|
|
||||||
CustomizationId.TattooColor => TattooColor,
|
|
||||||
CustomizationId.Eyebrows => Eyebrow,
|
|
||||||
CustomizationId.EyeColorL => EyeColorLeft,
|
|
||||||
CustomizationId.EyeShape => EyeShape,
|
|
||||||
CustomizationId.Nose => Nose,
|
|
||||||
CustomizationId.Jaw => Jaw,
|
|
||||||
CustomizationId.Mouth => Mouth,
|
|
||||||
CustomizationId.LipColor => LipColor,
|
|
||||||
CustomizationId.MuscleToneOrTailEarLength => MuscleMass,
|
|
||||||
CustomizationId.TailEarShape => TailShape,
|
|
||||||
CustomizationId.BustSize => BustSize,
|
|
||||||
CustomizationId.FacePaint => FacePaint,
|
|
||||||
CustomizationId.FacePaintColor => FacePaintColor,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
|
||||||
};
|
|
||||||
set
|
|
||||||
{
|
|
||||||
switch (id)
|
|
||||||
{
|
|
||||||
case CustomizationId.Race:
|
|
||||||
Race = (Race) value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Gender:
|
|
||||||
Gender = (Gender) value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.BodyType:
|
|
||||||
BodyType = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Height:
|
|
||||||
Height = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Clan:
|
|
||||||
Clan = (SubRace) value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Face:
|
|
||||||
Face = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Hairstyle:
|
|
||||||
Hairstyle = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.HighlightsOnFlag:
|
|
||||||
HighlightsOn = (value & 128) == 128;
|
|
||||||
break;
|
|
||||||
case CustomizationId.SkinColor:
|
|
||||||
SkinColor = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.EyeColorR:
|
|
||||||
EyeColorRight = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.HairColor:
|
|
||||||
HairColor = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.HighlightColor:
|
|
||||||
HighlightsColor = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.FacialFeaturesTattoos:
|
|
||||||
FacialFeatures = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.TattooColor:
|
|
||||||
TattooColor = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Eyebrows:
|
|
||||||
Eyebrow = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.EyeColorL:
|
|
||||||
EyeColorLeft = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.EyeShape:
|
|
||||||
EyeShape = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Nose:
|
|
||||||
Nose = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Jaw:
|
|
||||||
Jaw = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.Mouth:
|
|
||||||
Mouth = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.LipColor:
|
|
||||||
LipColor = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.MuscleToneOrTailEarLength:
|
|
||||||
MuscleMass = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.TailEarShape:
|
|
||||||
TailShape = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.BustSize:
|
|
||||||
BustSize = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.FacePaint:
|
|
||||||
FacePaint = value;
|
|
||||||
break;
|
|
||||||
case CustomizationId.FacePaintColor:
|
|
||||||
FacePaintColor = value;
|
|
||||||
break;
|
|
||||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void Write(IntPtr characterAddress)
|
public byte[] ToBytes()
|
||||||
{
|
{
|
||||||
fixed (Race* ptr = &Race)
|
var ret = new byte[CustomizationBytes];
|
||||||
{
|
WriteBytes(ret);
|
||||||
Buffer.MemoryCopy(ptr, (byte*) characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
public string HumanReadable()
|
||||||
{
|
{
|
||||||
fixed (Race* ptr = &Race)
|
// TODO
|
||||||
{
|
var sb = new StringBuilder();
|
||||||
Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes);
|
sb.Append($"Race: {Race.ToName()} - {Clan.ToName()}\n");
|
||||||
}
|
sb.Append($"Gender: {Gender.ToName()}\n");
|
||||||
}
|
sb.Append($"Height: {Height}%\n");
|
||||||
|
sb.Append($"Face: #{Face}\n");
|
||||||
public byte[] ToBytes()
|
sb.Append($"Hairstyle: #{Hairstyle}\n");
|
||||||
{
|
sb.Append($"Haircolor: #{HairColor}");
|
||||||
var ret = new byte[CustomizationBytes];
|
if (HighlightsOn)
|
||||||
WriteBytes(ret);
|
sb.Append($" with Highlights #{HighlightsColor}\n");
|
||||||
return ret;
|
else
|
||||||
}
|
sb.Append('\n');
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,23 @@
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer;
|
||||||
{
|
|
||||||
public class CmpFile
|
|
||||||
{
|
|
||||||
public readonly Lumina.Data.FileResource File;
|
|
||||||
public readonly uint[] RgbaColors;
|
|
||||||
|
|
||||||
public CmpFile(DataManager gameData)
|
public class CmpFile
|
||||||
|
{
|
||||||
|
public readonly Lumina.Data.FileResource File;
|
||||||
|
public readonly uint[] RgbaColors;
|
||||||
|
|
||||||
|
public CmpFile(DataManager gameData)
|
||||||
|
{
|
||||||
|
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||||
|
RgbaColors = new uint[File.Data.Length >> 2];
|
||||||
|
for (var i = 0; i < File.Data.Length; i += 4)
|
||||||
{
|
{
|
||||||
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
RgbaColors[i >> 2] = File.Data[i]
|
||||||
RgbaColors = new uint[File.Data.Length >> 2];
|
| (uint)(File.Data[i + 1] << 8)
|
||||||
for (var i = 0; i < File.Data.Length; i += 4)
|
| (uint)(File.Data[i + 2] << 16)
|
||||||
{
|
| (uint)(File.Data[i + 3] << 24);
|
||||||
RgbaColors[i >> 2] = File.Data[i]
|
|
||||||
| (uint) (File.Data[i + 1] << 8)
|
|
||||||
| (uint) (File.Data[i + 2] << 16)
|
|
||||||
| (uint) (File.Data[i + 3] << 24);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,370 +5,369 @@ using System.Reflection;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Glamourer.Util;
|
|
||||||
using Lumina.Data;
|
using Lumina.Data;
|
||||||
using Lumina.Excel;
|
using Lumina.Excel;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Race = Penumbra.GameData.Enums.Race;
|
using Race = Penumbra.GameData.Enums.Race;
|
||||||
|
|
||||||
namespace Glamourer.Customization
|
namespace Glamourer.Customization;
|
||||||
{
|
|
||||||
public partial class CustomizationOptions
|
|
||||||
{
|
|
||||||
internal static readonly Race[] Races = ((Race[]) Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
|
||||||
internal static readonly SubRace[] Clans = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
|
||||||
|
|
||||||
internal static readonly Gender[] Genders =
|
public partial class CustomizationOptions
|
||||||
|
{
|
||||||
|
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||||
|
internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
||||||
|
|
||||||
|
internal static readonly Gender[] Genders =
|
||||||
|
{
|
||||||
|
Gender.Male,
|
||||||
|
Gender.Female,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal CustomizationSet GetList(SubRace race, Gender gender)
|
||||||
|
=> _list[ToIndex(race, gender)];
|
||||||
|
|
||||||
|
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||||
|
=> _icons.LoadIcon(id);
|
||||||
|
|
||||||
|
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||||
|
|
||||||
|
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
||||||
|
private readonly IconStorage _icons;
|
||||||
|
|
||||||
|
private static void ThrowException(SubRace race, Gender gender)
|
||||||
|
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
||||||
|
|
||||||
|
private static int ToIndex(SubRace race, Gender gender)
|
||||||
|
{
|
||||||
|
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
||||||
|
ThrowException(race, gender);
|
||||||
|
|
||||||
|
var ret = (int)race - 1;
|
||||||
|
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||||
|
{
|
||||||
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
|
var hairList = new List<Customization>(row.Unknown30);
|
||||||
|
for (var i = 0; i < row.Unknown30; ++i)
|
||||||
{
|
{
|
||||||
Gender.Male,
|
var name = $"Unknown{66 + i * 9}";
|
||||||
Gender.Female,
|
var customizeIdx =
|
||||||
|
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||||
|
?? uint.MaxValue;
|
||||||
|
if (customizeIdx == uint.MaxValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
|
hairList.Add(hairRow != null
|
||||||
|
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||||
|
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return hairList.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||||
|
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
||||||
|
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||||
|
{
|
||||||
|
if (race > SubRace.Veena || race == SubRace.Unknown)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||||
|
|
||||||
|
var gv = gender == Gender.Male ? 0 : 1;
|
||||||
|
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||||
|
|
||||||
|
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||||
|
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||||
|
{
|
||||||
|
var row = _customizeSheet.GetRow(value);
|
||||||
|
return row == null
|
||||||
|
? new Customization(id, (byte)(index + 1), value, 0)
|
||||||
|
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||||
|
{
|
||||||
|
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||||
|
return menu?.Size ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||||
|
{
|
||||||
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
|
var paintList = new List<Customization>(row.Unknown37);
|
||||||
|
for (var i = 0; i < row.Unknown37; ++i)
|
||||||
|
{
|
||||||
|
var name = $"Unknown{73 + i * 9}";
|
||||||
|
var customizeIdx =
|
||||||
|
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||||
|
?? uint.MaxValue;
|
||||||
|
if (customizeIdx == uint.MaxValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
|
paintList.Add(paintRow != null
|
||||||
|
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||||
|
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return paintList.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||||
|
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||||
|
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||||
|
?? Array.Empty<Customization>();
|
||||||
|
|
||||||
|
private Customization[] GetFaces(CharaMakeParams row)
|
||||||
|
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||||
|
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||||
|
?? Array.Empty<Customization>();
|
||||||
|
|
||||||
|
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||||
|
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||||
|
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||||
|
?? Array.Empty<Customization>();
|
||||||
|
|
||||||
|
private Customization[] HrothgarFaces(CharaMakeParams row)
|
||||||
|
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
||||||
|
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
||||||
|
?? Array.Empty<Customization>();
|
||||||
|
|
||||||
|
private CustomizationSet GetSet(SubRace race, Gender gender)
|
||||||
|
{
|
||||||
|
var (skin, hair) = GetColors(race, gender);
|
||||||
|
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
|
var set = new CustomizationSet(race, gender)
|
||||||
|
{
|
||||||
|
HairStyles = GetHairStyles(race, gender),
|
||||||
|
HairColors = hair,
|
||||||
|
SkinColors = skin,
|
||||||
|
EyeColors = _eyeColorPicker,
|
||||||
|
HighlightColors = _highlightPicker,
|
||||||
|
TattooColors = _tattooColorPicker,
|
||||||
|
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||||
|
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||||
|
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||||
|
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||||
|
Faces = GetFaces(row),
|
||||||
|
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||||
|
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||||
|
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||||
|
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||||
|
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||||
|
FacePaints = GetFacePaints(race, gender),
|
||||||
|
TailEarShapes = GetTailEarShapes(row),
|
||||||
};
|
};
|
||||||
|
|
||||||
internal CustomizationSet GetList(SubRace race, Gender gender)
|
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
||||||
=> _list[ToIndex(race, gender)];
|
set.SetAvailable(CustomizationId.BustSize);
|
||||||
|
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
||||||
|
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
||||||
|
|
||||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
if (set.NumEyebrows > 0)
|
||||||
=> _icons.LoadIcon(id);
|
set.SetAvailable(CustomizationId.Eyebrows);
|
||||||
|
if (set.NumEyeShapes > 0)
|
||||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
set.SetAvailable(CustomizationId.EyeShape);
|
||||||
|
if (set.NumNoseShapes > 0)
|
||||||
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
set.SetAvailable(CustomizationId.Nose);
|
||||||
private readonly IconStorage _icons;
|
if (set.NumJawShapes > 0)
|
||||||
|
set.SetAvailable(CustomizationId.Jaw);
|
||||||
private static void ThrowException(SubRace race, Gender gender)
|
if (set.NumMouthShapes > 0)
|
||||||
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
set.SetAvailable(CustomizationId.Mouth);
|
||||||
|
if (set.FacePaints.Count > 0)
|
||||||
private static int ToIndex(SubRace race, Gender gender)
|
|
||||||
{
|
{
|
||||||
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
set.SetAvailable(CustomizationId.FacePaint);
|
||||||
ThrowException(race, gender);
|
set.SetAvailable(CustomizationId.FacePaintColor);
|
||||||
|
|
||||||
var ret = (int) race - 1;
|
|
||||||
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
if (set.TailEarShapes.Count > 0)
|
||||||
{
|
set.SetAvailable(CustomizationId.TailEarShape);
|
||||||
var row = _hairSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!;
|
if (set.Faces.Count > 0)
|
||||||
var hairList = new List<Customization>(row.Unknown30);
|
set.SetAvailable(CustomizationId.Face);
|
||||||
for (var i = 0; i < row.Unknown30; ++i)
|
|
||||||
{
|
|
||||||
var name = $"Unknown{66 + i * 9}";
|
|
||||||
var customizeIdx =
|
|
||||||
(uint?) row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
|
||||||
?? uint.MaxValue;
|
|
||||||
if (customizeIdx == uint.MaxValue)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
var count = set.Faces.Count;
|
||||||
hairList.Add(hairRow != null
|
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort) hairRow.RowId)
|
for (var i = 0; i < count; ++i)
|
||||||
: new Customization(CustomizationId.Hairstyle, (byte) i, customizeIdx, 0));
|
{
|
||||||
|
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||||
|
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||||
|
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8)))
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
set.FeaturesTattoos = featureDict;
|
||||||
|
|
||||||
|
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||||
|
{
|
||||||
|
var menu = row.Menus
|
||||||
|
.Cast<CharaMakeParams.Menu?>()
|
||||||
|
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||||
|
if (menu == null)
|
||||||
|
{
|
||||||
|
if (c == CustomizationId.HighlightsOnFlag)
|
||||||
|
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
||||||
|
|
||||||
|
return c.ToDefaultName();
|
||||||
}
|
}
|
||||||
|
|
||||||
return hairList.ToArray();
|
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||||
}
|
return
|
||||||
|
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
||||||
|
|
||||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
var textRow = _lobby.GetRow(menu.Value.Id);
|
||||||
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
return textRow?.Text.ToString() ?? c.ToDefaultName();
|
||||||
.Select((c, i) => new Customization(id, (byte) (light ? 128 + i : 0 + i), c, (ushort) (offset + i)))
|
}).ToArray();
|
||||||
.ToArray();
|
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||||
|
nameArray[(int)CustomizationId.EyeColorR] = GetName(CustomName.OddEyes);
|
||||||
|
set.OptionName = nameArray;
|
||||||
|
|
||||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||||
{
|
{
|
||||||
if (race > SubRace.Veena || race == SubRace.Unknown)
|
switch (c)
|
||||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
|
||||||
|
|
||||||
var gv = gender == Gender.Male ? 0 : 1;
|
|
||||||
var idx = ((int) race * 2 + gv) * 5 + 3;
|
|
||||||
|
|
||||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
|
||||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
|
||||||
{
|
|
||||||
var row = _customizeSheet.GetRow(value);
|
|
||||||
return row == null
|
|
||||||
? new Customization(id, (byte) (index + 1), value, 0)
|
|
||||||
: new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
|
||||||
{
|
|
||||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
|
||||||
return menu?.Size ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
|
||||||
{
|
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
|
||||||
var paintList = new List<Customization>(row.Unknown37);
|
|
||||||
for (var i = 0; i < row.Unknown37; ++i)
|
|
||||||
{
|
{
|
||||||
var name = $"Unknown{73 + i * 9}";
|
case CustomizationId.HighlightColor:
|
||||||
var customizeIdx =
|
case CustomizationId.EyeColorL:
|
||||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
case CustomizationId.EyeColorR:
|
||||||
?? uint.MaxValue;
|
return CharaMakeParams.MenuType.ColorPicker;
|
||||||
if (customizeIdx == uint.MaxValue)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
|
||||||
paintList.Add(paintRow != null
|
|
||||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
|
||||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return paintList.ToArray();
|
var menu = row.Menus
|
||||||
|
.Cast<CharaMakeParams.Menu?>()
|
||||||
|
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||||
|
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||||
|
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||||
|
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||||
|
private readonly ExcelSheet<Lobby> _lobby;
|
||||||
|
private readonly CmpFile _cmpFile;
|
||||||
|
private readonly Customization[] _highlightPicker;
|
||||||
|
private readonly Customization[] _eyeColorPicker;
|
||||||
|
private readonly Customization[] _facePaintColorPickerDark;
|
||||||
|
private readonly Customization[] _facePaintColorPickerLight;
|
||||||
|
private readonly Customization[] _lipColorPickerDark;
|
||||||
|
private readonly Customization[] _lipColorPickerLight;
|
||||||
|
private readonly Customization[] _tattooColorPicker;
|
||||||
|
private readonly string[] _names = new string[(int)CustomName.Num];
|
||||||
|
|
||||||
|
public string GetName(CustomName name)
|
||||||
|
=> _names[(int)name];
|
||||||
|
|
||||||
|
private static Language FromClientLanguage(ClientLanguage language)
|
||||||
|
=> language switch
|
||||||
|
{
|
||||||
|
ClientLanguage.English => Language.English,
|
||||||
|
ClientLanguage.French => Language.French,
|
||||||
|
ClientLanguage.German => Language.German,
|
||||||
|
ClientLanguage.Japanese => Language.Japanese,
|
||||||
|
_ => Language.English,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cmpFile = new CmpFile(gameData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||||
|
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||||
|
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
_lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
?? Array.Empty<Customization>();
|
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[]
|
||||||
|
|
||||||
private Customization[] GetFaces(CharaMakeParams row)
|
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
|
||||||
?? Array.Empty<Customization>();
|
|
||||||
|
|
||||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
|
||||||
?? Array.Empty<Customization>();
|
|
||||||
|
|
||||||
private Customization[] HrothgarFaces(CharaMakeParams row)
|
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
|
||||||
?? Array.Empty<Customization>();
|
|
||||||
|
|
||||||
private CustomizationSet GetSet(SubRace race, Gender gender)
|
|
||||||
{
|
|
||||||
var (skin, hair) = GetColors(race, gender);
|
|
||||||
var row = _listSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!;
|
|
||||||
var set = new CustomizationSet(race, gender)
|
|
||||||
{
|
|
||||||
HairStyles = GetHairStyles(race, gender),
|
|
||||||
HairColors = hair,
|
|
||||||
SkinColors = skin,
|
|
||||||
EyeColors = _eyeColorPicker,
|
|
||||||
HighlightColors = _highlightPicker,
|
|
||||||
TattooColors = _tattooColorPicker,
|
|
||||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
|
||||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
|
||||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
|
||||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
|
||||||
Faces = GetFaces(row),
|
|
||||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
|
||||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
|
||||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
|
||||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
|
||||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
|
||||||
FacePaints = GetFacePaints(race, gender),
|
|
||||||
TailEarShapes = GetTailEarShapes(row),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
|
||||||
set.SetAvailable(CustomizationId.BustSize);
|
|
||||||
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
|
||||||
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
|
||||||
|
|
||||||
if (set.NumEyebrows > 0)
|
|
||||||
set.SetAvailable(CustomizationId.Eyebrows);
|
|
||||||
if (set.NumEyeShapes > 0)
|
|
||||||
set.SetAvailable(CustomizationId.EyeShape);
|
|
||||||
if (set.NumNoseShapes > 0)
|
|
||||||
set.SetAvailable(CustomizationId.Nose);
|
|
||||||
if (set.NumJawShapes > 0)
|
|
||||||
set.SetAvailable(CustomizationId.Jaw);
|
|
||||||
if (set.NumMouthShapes > 0)
|
|
||||||
set.SetAvailable(CustomizationId.Mouth);
|
|
||||||
if (set.FacePaints.Count > 0)
|
|
||||||
{
|
|
||||||
set.SetAvailable(CustomizationId.FacePaint);
|
|
||||||
set.SetAvailable(CustomizationId.FacePaintColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set.TailEarShapes.Count > 0)
|
|
||||||
set.SetAvailable(CustomizationId.TailEarShape);
|
|
||||||
if (set.Faces.Count > 0)
|
|
||||||
set.SetAvailable(CustomizationId.Face);
|
|
||||||
|
|
||||||
var count = set.Faces.Count;
|
|
||||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
|
||||||
for (var i = 0; i < count; ++i)
|
|
||||||
{
|
|
||||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
|
||||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte) (1 << idx), val, (ushort) (i * 8 + idx)))
|
|
||||||
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort) ((i + 1) * 8)))
|
|
||||||
.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
set.FeaturesTattoos = featureDict;
|
|
||||||
|
|
||||||
var nameArray = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
|
||||||
{
|
|
||||||
var menu = row.Menus
|
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
|
||||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
|
||||||
if (menu == null)
|
|
||||||
{
|
|
||||||
if (c == CustomizationId.HighlightsOnFlag)
|
|
||||||
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
|
||||||
|
|
||||||
return c.ToDefaultName();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
|
||||||
return
|
|
||||||
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
|
||||||
|
|
||||||
var textRow = _lobby.GetRow(menu.Value.Id);
|
|
||||||
return textRow?.Text.ToString() ?? c.ToDefaultName();
|
|
||||||
}).ToArray();
|
|
||||||
nameArray[(int) CustomizationId.EyeColorL] = nameArray[(int) CustomizationId.EyeColorR];
|
|
||||||
nameArray[(int) CustomizationId.EyeColorR] = GetName(CustomName.OddEyes);
|
|
||||||
set.OptionName = nameArray;
|
|
||||||
|
|
||||||
set.Types = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
|
||||||
{
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case CustomizationId.HighlightColor:
|
|
||||||
case CustomizationId.EyeColorL:
|
|
||||||
case CustomizationId.EyeColorR:
|
|
||||||
return CharaMakeParams.MenuType.ColorPicker;
|
|
||||||
}
|
|
||||||
|
|
||||||
var menu = row.Menus
|
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
|
||||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
|
||||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
|
||||||
}).ToArray();
|
|
||||||
|
|
||||||
return set;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
|
||||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
|
||||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
|
||||||
private readonly ExcelSheet<Lobby> _lobby;
|
|
||||||
private readonly CmpFile _cmpFile;
|
|
||||||
private readonly Customization[] _highlightPicker;
|
|
||||||
private readonly Customization[] _eyeColorPicker;
|
|
||||||
private readonly Customization[] _facePaintColorPickerDark;
|
|
||||||
private readonly Customization[] _facePaintColorPickerLight;
|
|
||||||
private readonly Customization[] _lipColorPickerDark;
|
|
||||||
private readonly Customization[] _lipColorPickerLight;
|
|
||||||
private readonly Customization[] _tattooColorPicker;
|
|
||||||
private readonly string[] _names = new string[(int) CustomName.Num];
|
|
||||||
|
|
||||||
public string GetName(CustomName name)
|
|
||||||
=> _names[(int) name];
|
|
||||||
|
|
||||||
private static Language FromClientLanguage(ClientLanguage language)
|
|
||||||
=> language switch
|
|
||||||
{
|
|
||||||
ClientLanguage.English => Language.English,
|
|
||||||
ClientLanguage.French => Language.French,
|
|
||||||
ClientLanguage.German => Language.German,
|
|
||||||
ClientLanguage.Japanese => Language.Japanese,
|
|
||||||
_ => Language.English,
|
|
||||||
};
|
|
||||||
|
|
||||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_cmpFile = new CmpFile(gameData);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
|
||||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
|
||||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
|
||||||
_lobby = gameData.GetExcelSheet<Lobby>()!;
|
|
||||||
var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
|
||||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[]
|
|
||||||
{
|
{
|
||||||
"charamaketype",
|
"charamaketype",
|
||||||
FromClientLanguage(language),
|
FromClientLanguage(language),
|
||||||
null,
|
null,
|
||||||
}) as ExcelSheet<CharaMakeParams>;
|
}) as ExcelSheet<CharaMakeParams>;
|
||||||
_listSheet = tmp!;
|
_listSheet = tmp!;
|
||||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||||
SetNames(gameData);
|
SetNames(gameData);
|
||||||
|
|
||||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||||
|
|
||||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||||
foreach (var race in Clans)
|
foreach (var race in Clans)
|
||||||
{
|
|
||||||
foreach (var gender in Genders)
|
|
||||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetNames(DataManager gameData)
|
|
||||||
{
|
{
|
||||||
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
foreach (var gender in Genders)
|
||||||
_names[(int) CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||||
_names[(int) CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
|
||||||
_names[(int) CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
|
||||||
_names[(int) CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
|
||||||
_names[(int) CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
|
||||||
_names[(int) CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
|
||||||
_names[(int) CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
|
||||||
_names[(int) CustomName.MidlanderM] = subRace.GetRow((int) SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
|
||||||
_names[(int) CustomName.MidlanderF] = subRace.GetRow((int) SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
|
||||||
_names[(int) CustomName.HighlanderM] =
|
|
||||||
subRace.GetRow((int) SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
|
||||||
_names[(int) CustomName.HighlanderF] = subRace.GetRow((int) SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
|
||||||
_names[(int) CustomName.WildwoodM] = subRace.GetRow((int) SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
|
||||||
_names[(int) CustomName.WildwoodF] = subRace.GetRow((int) SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
|
||||||
_names[(int) CustomName.DuskwightM] = subRace.GetRow((int) SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
|
||||||
_names[(int) CustomName.DuskwightF] = subRace.GetRow((int) SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
|
||||||
_names[(int) CustomName.PlainsfolkM] =
|
|
||||||
subRace.GetRow((int) SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
|
||||||
_names[(int) CustomName.PlainsfolkF] = subRace.GetRow((int) SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
|
||||||
_names[(int) CustomName.DunesfolkM] = subRace.GetRow((int) SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
|
||||||
_names[(int) CustomName.DunesfolkF] = subRace.GetRow((int) SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
|
||||||
_names[(int) CustomName.SeekerOfTheSunM] =
|
|
||||||
subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
|
||||||
_names[(int) CustomName.SeekerOfTheSunF] =
|
|
||||||
subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
|
||||||
_names[(int) CustomName.KeeperOfTheMoonM] =
|
|
||||||
subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
|
||||||
_names[(int) CustomName.KeeperOfTheMoonF] =
|
|
||||||
subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
|
||||||
_names[(int) CustomName.SeawolfM] = subRace.GetRow((int) SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
|
||||||
_names[(int) CustomName.SeawolfF] = subRace.GetRow((int) SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
|
||||||
_names[(int) CustomName.HellsguardM] =
|
|
||||||
subRace.GetRow((int) SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
|
||||||
_names[(int) CustomName.HellsguardF] = subRace.GetRow((int) SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
|
||||||
_names[(int) CustomName.RaenM] = subRace.GetRow((int) SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
|
||||||
_names[(int) CustomName.RaenF] = subRace.GetRow((int) SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
|
||||||
_names[(int) CustomName.XaelaM] = subRace.GetRow((int) SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
|
||||||
_names[(int) CustomName.XaelaF] = subRace.GetRow((int) SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
|
||||||
_names[(int) CustomName.HelionM] = subRace.GetRow((int) SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
|
||||||
_names[(int) CustomName.HelionF] = subRace.GetRow((int) SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
|
||||||
_names[(int) CustomName.LostM] = subRace.GetRow((int) SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
|
||||||
_names[(int) CustomName.LostF] = subRace.GetRow((int) SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
|
||||||
_names[(int) CustomName.RavaM] = subRace.GetRow((int) SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
|
||||||
_names[(int) CustomName.RavaF] = subRace.GetRow((int) SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
|
||||||
_names[(int) CustomName.VeenaM] = subRace.GetRow((int) SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
|
||||||
_names[(int) CustomName.VeenaF] = subRace.GetRow((int) SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetNames(DataManager gameData)
|
||||||
|
{
|
||||||
|
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
||||||
|
_names[(int)CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
||||||
|
_names[(int)CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
||||||
|
_names[(int)CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
||||||
|
_names[(int)CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
||||||
|
_names[(int)CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
||||||
|
_names[(int)CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
||||||
|
_names[(int)CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
||||||
|
_names[(int)CustomName.MidlanderM] = subRace.GetRow((int)SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
||||||
|
_names[(int)CustomName.MidlanderF] = subRace.GetRow((int)SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
||||||
|
_names[(int)CustomName.HighlanderM] =
|
||||||
|
subRace.GetRow((int)SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
||||||
|
_names[(int)CustomName.HighlanderF] = subRace.GetRow((int)SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
||||||
|
_names[(int)CustomName.WildwoodM] = subRace.GetRow((int)SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
||||||
|
_names[(int)CustomName.WildwoodF] = subRace.GetRow((int)SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
||||||
|
_names[(int)CustomName.DuskwightM] = subRace.GetRow((int)SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
||||||
|
_names[(int)CustomName.DuskwightF] = subRace.GetRow((int)SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
||||||
|
_names[(int)CustomName.PlainsfolkM] =
|
||||||
|
subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||||
|
_names[(int)CustomName.PlainsfolkF] = subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||||
|
_names[(int)CustomName.DunesfolkM] = subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||||
|
_names[(int)CustomName.DunesfolkF] = subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||||
|
_names[(int)CustomName.SeekerOfTheSunM] =
|
||||||
|
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||||
|
_names[(int)CustomName.SeekerOfTheSunF] =
|
||||||
|
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||||
|
_names[(int)CustomName.KeeperOfTheMoonM] =
|
||||||
|
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||||
|
_names[(int)CustomName.KeeperOfTheMoonF] =
|
||||||
|
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||||
|
_names[(int)CustomName.SeawolfM] = subRace.GetRow((int)SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
||||||
|
_names[(int)CustomName.SeawolfF] = subRace.GetRow((int)SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
||||||
|
_names[(int)CustomName.HellsguardM] =
|
||||||
|
subRace.GetRow((int)SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||||
|
_names[(int)CustomName.HellsguardF] = subRace.GetRow((int)SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||||
|
_names[(int)CustomName.RaenM] = subRace.GetRow((int)SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
||||||
|
_names[(int)CustomName.RaenF] = subRace.GetRow((int)SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
||||||
|
_names[(int)CustomName.XaelaM] = subRace.GetRow((int)SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
||||||
|
_names[(int)CustomName.XaelaF] = subRace.GetRow((int)SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
||||||
|
_names[(int)CustomName.HelionM] = subRace.GetRow((int)SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
||||||
|
_names[(int)CustomName.HelionF] = subRace.GetRow((int)SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
||||||
|
_names[(int)CustomName.LostM] = subRace.GetRow((int)SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
||||||
|
_names[(int)CustomName.LostF] = subRace.GetRow((int)SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
||||||
|
_names[(int)CustomName.RavaM] = subRace.GetRow((int)SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
||||||
|
_names[(int)CustomName.RavaF] = subRace.GetRow((int)SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
||||||
|
_names[(int)CustomName.VeenaM] = subRace.GetRow((int)SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
||||||
|
_names[(int)CustomName.VeenaF] = subRace.GetRow((int)SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,142 +3,180 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud;
|
using Dalamud;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
|
using Glamourer.Structs;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Item = Glamourer.Structs.Item;
|
||||||
|
using Stain = Glamourer.Structs.Stain;
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public static class GameData
|
||||||
{
|
{
|
||||||
public static class GameData
|
private static Dictionary<byte, Stain>? _stains;
|
||||||
|
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||||
|
private static Dictionary<byte, Job>? _jobs;
|
||||||
|
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||||
|
private static SortedList<uint, ModelChara>? _models;
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
||||||
{
|
{
|
||||||
private static Dictionary<byte, Stain>? _stains;
|
if (_models != null)
|
||||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
|
||||||
private static Dictionary<byte, Job>? _jobs;
|
|
||||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
|
||||||
private static SortedList<uint, ModelChara>? _models;
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
|
||||||
{
|
|
||||||
if (_models != null)
|
|
||||||
return _models;
|
|
||||||
|
|
||||||
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
|
||||||
|
|
||||||
_models = new SortedList<uint, ModelChara>((int) sheet.RowCount);
|
|
||||||
foreach (var model in sheet.Where(m => m.Type != 0))
|
|
||||||
_models.Add(model.RowId, model);
|
|
||||||
return _models;
|
return _models;
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
||||||
{
|
|
||||||
if (_stains != null)
|
|
||||||
return _stains;
|
|
||||||
|
|
||||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>()!;
|
_models = new SortedList<uint, ModelChara>((int)sheet.RowCount);
|
||||||
_stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte) s.RowId, s => new Stain((byte) s.RowId, s));
|
foreach (var model in sheet.Where(m => m.Type != 0))
|
||||||
|
_models.Add(model.RowId, model);
|
||||||
|
return _models;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||||
|
{
|
||||||
|
if (_stains != null)
|
||||||
return _stains;
|
return _stains;
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>()!;
|
||||||
{
|
_stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte)s.RowId, s => new Stain((byte)s.RowId, s));
|
||||||
if (_itemsBySlot != null)
|
return _stains;
|
||||||
return _itemsBySlot;
|
}
|
||||||
|
|
||||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
||||||
|
{
|
||||||
Item EmptySlot(EquipSlot slot)
|
if (_itemsBySlot != null)
|
||||||
=> new(sheet.First(), "Nothing", slot);
|
|
||||||
|
|
||||||
static Item EmptyNpc(EquipSlot slot)
|
|
||||||
=> new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot);
|
|
||||||
|
|
||||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
|
||||||
{
|
|
||||||
[EquipSlot.Head] = new(200) { EmptySlot(EquipSlot.Head), EmptyNpc(EquipSlot.Head) },
|
|
||||||
[EquipSlot.Body] = new(200) { EmptySlot(EquipSlot.Body), EmptyNpc(EquipSlot.Body) },
|
|
||||||
[EquipSlot.Hands] = new(200) { EmptySlot(EquipSlot.Hands), EmptyNpc(EquipSlot.Hands) },
|
|
||||||
[EquipSlot.Legs] = new(200) { EmptySlot(EquipSlot.Legs), EmptyNpc(EquipSlot.Legs) },
|
|
||||||
[EquipSlot.Feet] = new(200) { EmptySlot(EquipSlot.Feet), EmptyNpc(EquipSlot.Feet) },
|
|
||||||
[EquipSlot.RFinger] = new(200) { EmptySlot(EquipSlot.RFinger), EmptyNpc(EquipSlot.RFinger) },
|
|
||||||
[EquipSlot.Neck] = new(200) { EmptySlot(EquipSlot.Neck), EmptyNpc(EquipSlot.Neck) },
|
|
||||||
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
|
|
||||||
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
|
||||||
[EquipSlot.Wrists] = new(200) { EmptySlot(EquipSlot.Wrists), EmptyNpc(EquipSlot.Wrists) },
|
|
||||||
[EquipSlot.Ears] = new(200) { EmptySlot(EquipSlot.Ears), EmptyNpc(EquipSlot.Ears) },
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var item in sheet)
|
|
||||||
{
|
|
||||||
var name = item.Name.ToString();
|
|
||||||
if (!name.Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var slot = (EquipSlot) item.EquipSlotCategory.Row;
|
|
||||||
if (slot == EquipSlot.Unknown)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
slot = slot.ToSlot();
|
|
||||||
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
list.Add(new Item(item, name, slot));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var list in _itemsBySlot.Values)
|
|
||||||
list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture));
|
|
||||||
|
|
||||||
_itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger];
|
|
||||||
return _itemsBySlot;
|
return _itemsBySlot;
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<byte, Job> Jobs(DataManager dataManager)
|
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||||
|
|
||||||
|
Item EmptySlot(EquipSlot slot)
|
||||||
|
=> new(sheet.First(), "Nothing", slot);
|
||||||
|
|
||||||
|
static Item EmptyNpc(EquipSlot slot)
|
||||||
|
=> new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot);
|
||||||
|
|
||||||
|
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
||||||
{
|
{
|
||||||
if (_jobs != null)
|
[EquipSlot.Head] = new(200)
|
||||||
return _jobs;
|
|
||||||
|
|
||||||
var sheet = dataManager.GetExcelSheet<ClassJob>()!;
|
|
||||||
_jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j));
|
|
||||||
return _jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<ushort, JobGroup> JobGroups(DataManager dataManager)
|
|
||||||
{
|
|
||||||
if (_jobGroups != null)
|
|
||||||
return _jobGroups;
|
|
||||||
|
|
||||||
var sheet = dataManager.GetExcelSheet<ClassJobCategory>()!;
|
|
||||||
var jobs = dataManager.GetExcelSheet<ClassJob>(ClientLanguage.English)!;
|
|
||||||
|
|
||||||
static bool ValidIndex(uint idx)
|
|
||||||
{
|
{
|
||||||
if (idx > 0 && idx < 36)
|
EmptySlot(EquipSlot.Head),
|
||||||
return true;
|
EmptyNpc(EquipSlot.Head),
|
||||||
|
},
|
||||||
|
[EquipSlot.Body] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Body),
|
||||||
|
EmptyNpc(EquipSlot.Body),
|
||||||
|
},
|
||||||
|
[EquipSlot.Hands] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Hands),
|
||||||
|
EmptyNpc(EquipSlot.Hands),
|
||||||
|
},
|
||||||
|
[EquipSlot.Legs] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Legs),
|
||||||
|
EmptyNpc(EquipSlot.Legs),
|
||||||
|
},
|
||||||
|
[EquipSlot.Feet] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Feet),
|
||||||
|
EmptyNpc(EquipSlot.Feet),
|
||||||
|
},
|
||||||
|
[EquipSlot.RFinger] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.RFinger),
|
||||||
|
EmptyNpc(EquipSlot.RFinger),
|
||||||
|
},
|
||||||
|
[EquipSlot.Neck] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Neck),
|
||||||
|
EmptyNpc(EquipSlot.Neck),
|
||||||
|
},
|
||||||
|
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
|
||||||
|
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
||||||
|
[EquipSlot.Wrists] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Wrists),
|
||||||
|
EmptyNpc(EquipSlot.Wrists),
|
||||||
|
},
|
||||||
|
[EquipSlot.Ears] = new(200)
|
||||||
|
{
|
||||||
|
EmptySlot(EquipSlot.Ears),
|
||||||
|
EmptyNpc(EquipSlot.Ears),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return idx switch
|
foreach (var item in sheet)
|
||||||
{
|
{
|
||||||
91 => true,
|
var name = item.Name.ToString();
|
||||||
92 => true,
|
if (!name.Any())
|
||||||
96 => true,
|
continue;
|
||||||
98 => true,
|
|
||||||
99 => true,
|
|
||||||
111 => true,
|
|
||||||
112 => true,
|
|
||||||
129 => true,
|
|
||||||
149 => true,
|
|
||||||
150 => true,
|
|
||||||
156 => true,
|
|
||||||
157 => true,
|
|
||||||
158 => true,
|
|
||||||
159 => true,
|
|
||||||
180 => true,
|
|
||||||
181 => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_jobGroups = sheet.Where(j => ValidIndex(j.RowId))
|
var slot = (EquipSlot)item.EquipSlotCategory.Row;
|
||||||
.ToDictionary(j => (ushort) j.RowId, j => new JobGroup(j, jobs));
|
if (slot == EquipSlot.Unknown)
|
||||||
return _jobGroups;
|
continue;
|
||||||
|
|
||||||
|
slot = slot.ToSlot();
|
||||||
|
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
list.Add(new Item(item, name, slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var list in _itemsBySlot.Values)
|
||||||
|
list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture));
|
||||||
|
|
||||||
|
_itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger];
|
||||||
|
return _itemsBySlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<byte, Job> Jobs(DataManager dataManager)
|
||||||
|
{
|
||||||
|
if (_jobs != null)
|
||||||
|
return _jobs;
|
||||||
|
|
||||||
|
var sheet = dataManager.GetExcelSheet<ClassJob>()!;
|
||||||
|
_jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j));
|
||||||
|
return _jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyDictionary<ushort, JobGroup> JobGroups(DataManager dataManager)
|
||||||
|
{
|
||||||
|
if (_jobGroups != null)
|
||||||
|
return _jobGroups;
|
||||||
|
|
||||||
|
var sheet = dataManager.GetExcelSheet<ClassJobCategory>()!;
|
||||||
|
var jobs = dataManager.GetExcelSheet<ClassJob>(ClientLanguage.English)!;
|
||||||
|
|
||||||
|
static bool ValidIndex(uint idx)
|
||||||
|
{
|
||||||
|
if (idx is > 0 and < 36)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return idx switch
|
||||||
|
{
|
||||||
|
91 => true,
|
||||||
|
92 => true,
|
||||||
|
96 => true,
|
||||||
|
98 => true,
|
||||||
|
99 => true,
|
||||||
|
111 => true,
|
||||||
|
112 => true,
|
||||||
|
129 => true,
|
||||||
|
149 => true,
|
||||||
|
150 => true,
|
||||||
|
156 => true,
|
||||||
|
157 => true,
|
||||||
|
158 => true,
|
||||||
|
159 => true,
|
||||||
|
180 => true,
|
||||||
|
181 => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_jobGroups = sheet.Where(j => ValidIndex(j.RowId))
|
||||||
|
.ToDictionary(j => (ushort)j.RowId, j => new JobGroup(j, jobs));
|
||||||
|
return _jobGroups;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Penumbra\OtterGui\OtterGui.csproj" />
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
public readonly struct Item
|
|
||||||
{
|
|
||||||
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
|
||||||
{
|
|
||||||
if (slot == EquipSlot.MainHand || slot == EquipSlot.OffHand)
|
|
||||||
{
|
|
||||||
var id = (SetId) (data & 0xFFFF);
|
|
||||||
var type = (WeaponType) ((data >> 16) & 0xFFFF);
|
|
||||||
var variant = (ushort) ((data >> 32) & 0xFFFF);
|
|
||||||
return (id, type, variant);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var id = (SetId) (data & 0xFFFF);
|
|
||||||
var variant = (byte) ((data >> 16) & 0xFF);
|
|
||||||
return (id, new WeaponType(), variant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly EquipSlot EquippableTo;
|
|
||||||
|
|
||||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
|
||||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
|
||||||
|
|
||||||
public bool HasSubModel
|
|
||||||
=> Base.ModelSub != 0;
|
|
||||||
|
|
||||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
|
||||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
|
||||||
|
|
||||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
|
||||||
{
|
|
||||||
Base = item;
|
|
||||||
Name = name;
|
|
||||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot) item.EquipSlotCategory.Row).ToSlot() : slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Item Nothing(EquipSlot slot)
|
|
||||||
=> new("Nothing", slot);
|
|
||||||
|
|
||||||
private Item(string name, EquipSlot slot)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
|
||||||
EquippableTo = slot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
public readonly struct Job
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly string Abbreviation;
|
|
||||||
public readonly ClassJob Base;
|
|
||||||
|
|
||||||
public uint Id
|
|
||||||
=> Base.RowId;
|
|
||||||
|
|
||||||
public Job(ClassJob job)
|
|
||||||
{
|
|
||||||
Base = job;
|
|
||||||
Name = job.Name.ToString();
|
|
||||||
Abbreviation = job.Abbreviation.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Lumina.Excel;
|
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
public readonly struct JobGroup
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
private readonly ulong _flags;
|
|
||||||
public readonly int Count;
|
|
||||||
public readonly uint Id;
|
|
||||||
|
|
||||||
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
|
||||||
{
|
|
||||||
Count = 0;
|
|
||||||
_flags = 0ul;
|
|
||||||
Id = group.RowId;
|
|
||||||
Name = group.Name.ToString();
|
|
||||||
|
|
||||||
Debug.Assert(jobs.RowCount < 64);
|
|
||||||
foreach (var job in jobs)
|
|
||||||
{
|
|
||||||
var abbr = job.Abbreviation.ToString();
|
|
||||||
if (!abbr.Any())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var prop = group.GetType().GetProperty(abbr);
|
|
||||||
Debug.Assert(prop != null);
|
|
||||||
|
|
||||||
if (!(bool) prop.GetValue(group)!)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
++Count;
|
|
||||||
_flags |= 1ul << (int) job.RowId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Fits(Job job)
|
|
||||||
=> Fits(job.Id);
|
|
||||||
|
|
||||||
public bool Fits(uint jobId)
|
|
||||||
{
|
|
||||||
var flag = 1ul << (int)jobId;
|
|
||||||
return (flag & _flags) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
public readonly struct Stain
|
|
||||||
{
|
|
||||||
public readonly string Name;
|
|
||||||
public readonly uint RgbaColor;
|
|
||||||
|
|
||||||
private readonly uint _seColorId;
|
|
||||||
|
|
||||||
public byte R
|
|
||||||
=> (byte) (RgbaColor & 0xFF);
|
|
||||||
|
|
||||||
public byte G
|
|
||||||
=> (byte) ((RgbaColor >> 8) & 0xFF);
|
|
||||||
|
|
||||||
public byte B
|
|
||||||
=> (byte) ((RgbaColor >> 16) & 0xFF);
|
|
||||||
|
|
||||||
public byte Intensity
|
|
||||||
=> (byte) ((1 + R + G + B) / 3);
|
|
||||||
|
|
||||||
public uint SeColor
|
|
||||||
=> _seColorId & 0x00FFFFFF;
|
|
||||||
|
|
||||||
public StainId RowIndex
|
|
||||||
=> (StainId) (_seColorId >> 24);
|
|
||||||
|
|
||||||
|
|
||||||
public static uint SeColorToRgba(uint color)
|
|
||||||
=> ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000;
|
|
||||||
|
|
||||||
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
|
||||||
{
|
|
||||||
Name = stain.Name.ToString();
|
|
||||||
_seColorId = stain.Color | ((uint) index << 24);
|
|
||||||
RgbaColor = SeColorToRgba(stain.Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly Stain None = new("None");
|
|
||||||
|
|
||||||
private Stain(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
_seColorId = 0;
|
|
||||||
RgbaColor = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
43
Glamourer.GameData/Structs/CharacterEquipMask.cs
Normal file
43
Glamourer.GameData/Structs/CharacterEquipMask.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
using System;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Structs;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum CharacterEquipMask : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
MainHand = 0b000000000001,
|
||||||
|
OffHand = 0b000000000010,
|
||||||
|
Head = 0b000000000100,
|
||||||
|
Body = 0b000000001000,
|
||||||
|
Hands = 0b000000010000,
|
||||||
|
Legs = 0b000000100000,
|
||||||
|
Feet = 0b000001000000,
|
||||||
|
Ears = 0b000010000000,
|
||||||
|
Neck = 0b000100000000,
|
||||||
|
Wrists = 0b001000000000,
|
||||||
|
RFinger = 0b010000000000,
|
||||||
|
LFinger = 0b100000000000,
|
||||||
|
All = 0b111111111111,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CharacterEquipMaskExtensions
|
||||||
|
{
|
||||||
|
public static bool Fits(this CharacterEquipMask mask, EquipSlot slot)
|
||||||
|
=> slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Unknown => false,
|
||||||
|
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||||
|
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||||
|
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||||
|
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||||
|
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||||
|
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||||
|
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||||
|
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||||
|
EquipSlot.RFinger => mask.HasFlag(CharacterEquipMask.RFinger),
|
||||||
|
EquipSlot.LFinger => mask.HasFlag(CharacterEquipMask.LFinger),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
54
Glamourer.GameData/Structs/Item.cs
Normal file
54
Glamourer.GameData/Structs/Item.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Structs;
|
||||||
|
|
||||||
|
public readonly struct Item
|
||||||
|
{
|
||||||
|
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
||||||
|
{
|
||||||
|
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
||||||
|
{
|
||||||
|
var id = (SetId)(data & 0xFFFF);
|
||||||
|
var type = (WeaponType)((data >> 16) & 0xFFFF);
|
||||||
|
var variant = (ushort)((data >> 32) & 0xFFFF);
|
||||||
|
return (id, type, variant);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var id = (SetId)(data & 0xFFFF);
|
||||||
|
var variant = (byte)((data >> 16) & 0xFF);
|
||||||
|
return (id, new WeaponType(), variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly EquipSlot EquippableTo;
|
||||||
|
|
||||||
|
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||||
|
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||||
|
|
||||||
|
public bool HasSubModel
|
||||||
|
=> Base.ModelSub != 0;
|
||||||
|
|
||||||
|
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||||
|
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||||
|
|
||||||
|
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||||
|
{
|
||||||
|
Base = item;
|
||||||
|
Name = name;
|
||||||
|
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Item Nothing(EquipSlot slot)
|
||||||
|
=> new("Nothing", slot);
|
||||||
|
|
||||||
|
private Item(string name, EquipSlot slot)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||||
|
EquippableTo = slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Glamourer.GameData/Structs/Job.cs
Normal file
20
Glamourer.GameData/Structs/Job.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Glamourer.Structs;
|
||||||
|
|
||||||
|
public readonly struct Job
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly string Abbreviation;
|
||||||
|
public readonly ClassJob Base;
|
||||||
|
|
||||||
|
public uint Id
|
||||||
|
=> Base.RowId;
|
||||||
|
|
||||||
|
public Job(ClassJob job)
|
||||||
|
{
|
||||||
|
Base = job;
|
||||||
|
Name = job.Name.ToString();
|
||||||
|
Abbreviation = job.Abbreviation.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Glamourer.GameData/Structs/JobGroup.cs
Normal file
48
Glamourer.GameData/Structs/JobGroup.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Lumina.Excel;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Glamourer.Structs;
|
||||||
|
|
||||||
|
public readonly struct JobGroup
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
private readonly ulong _flags;
|
||||||
|
public readonly int Count;
|
||||||
|
public readonly uint Id;
|
||||||
|
|
||||||
|
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
||||||
|
{
|
||||||
|
Count = 0;
|
||||||
|
_flags = 0ul;
|
||||||
|
Id = group.RowId;
|
||||||
|
Name = group.Name.ToString();
|
||||||
|
|
||||||
|
Debug.Assert(jobs.RowCount < 64);
|
||||||
|
foreach (var job in jobs)
|
||||||
|
{
|
||||||
|
var abbr = job.Abbreviation.ToString();
|
||||||
|
if (!abbr.Any())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var prop = group.GetType().GetProperty(abbr);
|
||||||
|
Debug.Assert(prop != null);
|
||||||
|
|
||||||
|
if (!(bool)prop.GetValue(group)!)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
++Count;
|
||||||
|
_flags |= 1ul << (int)job.RowId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Fits(Job job)
|
||||||
|
=> Fits(job.Id);
|
||||||
|
|
||||||
|
public bool Fits(uint jobId)
|
||||||
|
{
|
||||||
|
var flag = 1ul << (int)jobId;
|
||||||
|
return (flag & _flags) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Glamourer.GameData/Structs/Stain.cs
Normal file
49
Glamourer.GameData/Structs/Stain.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Structs;
|
||||||
|
|
||||||
|
public readonly struct Stain
|
||||||
|
{
|
||||||
|
public readonly string Name;
|
||||||
|
public readonly uint RgbaColor;
|
||||||
|
|
||||||
|
private readonly uint _seColorId;
|
||||||
|
|
||||||
|
public byte R
|
||||||
|
=> (byte)(RgbaColor & 0xFF);
|
||||||
|
|
||||||
|
public byte G
|
||||||
|
=> (byte)(RgbaColor >> 8 & 0xFF);
|
||||||
|
|
||||||
|
public byte B
|
||||||
|
=> (byte)(RgbaColor >> 16 & 0xFF);
|
||||||
|
|
||||||
|
public byte Intensity
|
||||||
|
=> (byte)((1 + R + G + B) / 3);
|
||||||
|
|
||||||
|
public uint SeColor
|
||||||
|
=> _seColorId & 0x00FFFFFF;
|
||||||
|
|
||||||
|
public StainId RowIndex
|
||||||
|
=> (StainId)(_seColorId >> 24);
|
||||||
|
|
||||||
|
|
||||||
|
public static uint SeColorToRgba(uint color)
|
||||||
|
=> (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000;
|
||||||
|
|
||||||
|
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
||||||
|
{
|
||||||
|
Name = stain.Name.ToString();
|
||||||
|
_seColorId = stain.Color | (uint)index << 24;
|
||||||
|
RgbaColor = SeColorToRgba(stain.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly Stain None = new("None");
|
||||||
|
|
||||||
|
private Stain(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
_seColorId = 0;
|
||||||
|
RgbaColor = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Dalamud.Data;
|
|
||||||
using Dalamud.Plugin;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using ImGuiScene;
|
|
||||||
using Lumina.Data.Files;
|
|
||||||
|
|
||||||
namespace Glamourer.Util
|
|
||||||
{
|
|
||||||
public class IconStorage : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DalamudPluginInterface _pi;
|
|
||||||
private readonly DataManager _gameData;
|
|
||||||
private readonly Dictionary<uint, TextureWrap> _icons;
|
|
||||||
|
|
||||||
public IconStorage(DalamudPluginInterface pi, DataManager gameData, int size = 0)
|
|
||||||
{
|
|
||||||
_pi = pi;
|
|
||||||
_gameData = gameData;
|
|
||||||
_icons = new Dictionary<uint, TextureWrap>(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureWrap this[int id]
|
|
||||||
=> LoadIcon(id);
|
|
||||||
|
|
||||||
private TexFile? LoadIconHq(uint id)
|
|
||||||
{
|
|
||||||
var path = $"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex";
|
|
||||||
return _gameData.GetFile<TexFile>(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureWrap LoadIcon(int id)
|
|
||||||
=> LoadIcon((uint) id);
|
|
||||||
|
|
||||||
public TextureWrap LoadIcon(uint id)
|
|
||||||
{
|
|
||||||
if (_icons.TryGetValue(id, out var ret))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
var icon = LoadIconHq(id) ?? _gameData.GetIcon(id)!;
|
|
||||||
var iconData = icon.GetRgbaImageData();
|
|
||||||
|
|
||||||
ret = _pi.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4);
|
|
||||||
_icons[id] = ret;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var icon in _icons.Values)
|
|
||||||
icon.Dispose();
|
|
||||||
_icons.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
~IconStorage()
|
|
||||||
=> Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "..\Pen
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.PlayerWatch", "..\Penumbra\Penumbra.PlayerWatch\Penumbra.PlayerWatch.csproj", "{FECEDB39-C103-4333-82A6-A422BDC51EEE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "..\Penumbra\OtterGui\OtterGui.csproj", "{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -75,6 +77,18 @@ Global
|
||||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.Build.0 = Release|Any CPU
|
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.ActiveCfg = Release|Any CPU
|
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.Build.0 = Release|Any CPU
|
{FECEDB39-C103-4333-82A6-A422BDC51EEE}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Plugin.Ipc;
|
using Dalamud.Plugin.Ipc;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -17,10 +18,14 @@ public class PenumbraAttach : IDisposable
|
||||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
||||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
||||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
||||||
|
private ICallGateSubscriber<IntPtr, (IntPtr, string)>? _drawObjectInfo;
|
||||||
|
private ICallGateSubscriber<IntPtr, string, IntPtr, IntPtr, object?>? _creatingCharacterBase;
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<object?> _initializedEvent;
|
private readonly ICallGateSubscriber<object?> _initializedEvent;
|
||||||
private readonly ICallGateSubscriber<object?> _disposedEvent;
|
private readonly ICallGateSubscriber<object?> _disposedEvent;
|
||||||
|
|
||||||
|
public event Action<IntPtr, IntPtr, IntPtr>? CreatingCharacterBase;
|
||||||
|
|
||||||
public PenumbraAttach(bool attach)
|
public PenumbraAttach(bool attach)
|
||||||
{
|
{
|
||||||
_initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Initialized");
|
_initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Initialized");
|
||||||
|
|
@ -47,6 +52,7 @@ public class PenumbraAttach : IDisposable
|
||||||
|
|
||||||
_redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
_redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||||
_redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
_redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||||
|
_drawObjectInfo = Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, (IntPtr, string)>("Penumbra.GetDrawObjectInfo");
|
||||||
|
|
||||||
if (!attach)
|
if (!attach)
|
||||||
return;
|
return;
|
||||||
|
|
@ -54,8 +60,11 @@ public class PenumbraAttach : IDisposable
|
||||||
_tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<ChangedItemType, uint, object>("Penumbra.ChangedItemTooltip");
|
_tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<ChangedItemType, uint, object>("Penumbra.ChangedItemTooltip");
|
||||||
_clickSubscriber =
|
_clickSubscriber =
|
||||||
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
||||||
|
_creatingCharacterBase =
|
||||||
|
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, IntPtr, object?>("Penumbra.CreatingCharacterBase");
|
||||||
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
||||||
_clickSubscriber.Subscribe(PenumbraRightClick);
|
_clickSubscriber.Subscribe(PenumbraRightClick);
|
||||||
|
_creatingCharacterBase.Subscribe(SubscribeCharacterBase);
|
||||||
PluginLog.Debug("Glamourer attached to Penumbra.");
|
PluginLog.Debug("Glamourer attached to Penumbra.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -64,13 +73,19 @@ public class PenumbraAttach : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubscribeCharacterBase(IntPtr gameObject, string _, IntPtr customize, IntPtr equipment)
|
||||||
|
=> CreatingCharacterBase?.Invoke(gameObject, customize, equipment);
|
||||||
|
|
||||||
public void Unattach()
|
public void Unattach()
|
||||||
{
|
{
|
||||||
_tooltipSubscriber?.Unsubscribe(PenumbraTooltip);
|
_tooltipSubscriber?.Unsubscribe(PenumbraTooltip);
|
||||||
_clickSubscriber?.Unsubscribe(PenumbraRightClick);
|
_clickSubscriber?.Unsubscribe(PenumbraRightClick);
|
||||||
_tooltipSubscriber = null;
|
_creatingCharacterBase?.Unsubscribe(SubscribeCharacterBase);
|
||||||
_clickSubscriber = null;
|
_tooltipSubscriber = null;
|
||||||
_redrawSubscriberName = null;
|
_clickSubscriber = null;
|
||||||
|
_creatingCharacterBase = null;
|
||||||
|
_redrawSubscriberName = null;
|
||||||
|
_drawObjectInfo = null;
|
||||||
if (_redrawSubscriberObject != null)
|
if (_redrawSubscriberObject != null)
|
||||||
{
|
{
|
||||||
PluginLog.Debug("Glamourer detached from Penumbra.");
|
PluginLog.Debug("Glamourer detached from Penumbra.");
|
||||||
|
|
@ -112,6 +127,9 @@ public class PenumbraAttach : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GameObjectFromDrawObject(IntPtr drawObject)
|
||||||
|
=> (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero);
|
||||||
|
|
||||||
public void RedrawObject(GameObject actor, RedrawType settings, bool repeat)
|
public void RedrawObject(GameObject actor, RedrawType settings, bool repeat)
|
||||||
{
|
{
|
||||||
if (_redrawSubscriberObject != null)
|
if (_redrawSubscriberObject != null)
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Glamourer
|
|
||||||
{
|
|
||||||
[Flags]
|
|
||||||
public enum CharacterFlag : ulong
|
|
||||||
{
|
|
||||||
MainHand = 1ul << 0,
|
|
||||||
OffHand = 1ul << 1,
|
|
||||||
Head = 1ul << 2,
|
|
||||||
Body = 1ul << 3,
|
|
||||||
Hands = 1ul << 4,
|
|
||||||
Legs = 1ul << 5,
|
|
||||||
Feet = 1ul << 6,
|
|
||||||
Ears = 1ul << 7,
|
|
||||||
Neck = 1ul << 8,
|
|
||||||
Wrists = 1ul << 9,
|
|
||||||
RFinger = 1ul << 10,
|
|
||||||
LFinger = 1ul << 11,
|
|
||||||
ModelMask = (1ul << 12) - 1,
|
|
||||||
|
|
||||||
StainMainHand = MainHand << 16,
|
|
||||||
StainOffHand = OffHand << 16,
|
|
||||||
StainHead = Head << 16,
|
|
||||||
StainBody = Body << 16,
|
|
||||||
StainHands = Hands << 16,
|
|
||||||
StainLegs = Legs << 16,
|
|
||||||
StainFeet = Feet << 16,
|
|
||||||
StainEars = Ears << 16,
|
|
||||||
StainNeck = Neck << 16,
|
|
||||||
StainWrists = Wrists << 16,
|
|
||||||
StainRFinger = RFinger << 16,
|
|
||||||
StainLFinger = LFinger << 16,
|
|
||||||
StainMask = ModelMask << 16,
|
|
||||||
EquipMask = ModelMask | StainMask,
|
|
||||||
|
|
||||||
Race = 1ul << 32,
|
|
||||||
Gender = 1ul << 33,
|
|
||||||
BodyType = 1ul << 34,
|
|
||||||
Height = 1ul << 35,
|
|
||||||
Clan = 1ul << 36,
|
|
||||||
Face = 1ul << 37,
|
|
||||||
Hairstyle = 1ul << 38,
|
|
||||||
Highlights = 1ul << 39,
|
|
||||||
SkinColor = 1ul << 40,
|
|
||||||
EyeColorRight = 1ul << 41,
|
|
||||||
HairColor = 1ul << 42,
|
|
||||||
HighlightsColor = 1ul << 43,
|
|
||||||
FacialFeatures = 1ul << 44,
|
|
||||||
TattooColor = 1ul << 45,
|
|
||||||
Eyebrows = 1ul << 46,
|
|
||||||
EyeColorLeft = 1ul << 47,
|
|
||||||
EyeShape = 1ul << 48,
|
|
||||||
IrisSize = 1ul << 49,
|
|
||||||
NoseShape = 1ul << 50,
|
|
||||||
JawShape = 1ul << 51,
|
|
||||||
MouthShape = 1ul << 52,
|
|
||||||
Lipstick = 1ul << 53,
|
|
||||||
LipColor = 1ul << 54,
|
|
||||||
MuscleMass = 1ul << 55,
|
|
||||||
TailShape = 1ul << 56,
|
|
||||||
BustSize = 1ul << 57,
|
|
||||||
FacePaint = 1ul << 58,
|
|
||||||
FacePaintReversed = 1ul << 59,
|
|
||||||
FacePaintColor = 1ul << 60,
|
|
||||||
CustomizeMask = ((1ul << 61) - 1) & ~EquipMask,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +1,29 @@
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Game;
|
using Dalamud.Game;
|
||||||
using Dalamud.Game.ClientState;
|
using Dalamud.Game.ClientState;
|
||||||
using Dalamud.Game.ClientState.Buddy;
|
|
||||||
using Dalamud.Game.ClientState.Conditions;
|
|
||||||
using Dalamud.Game.ClientState.Fates;
|
|
||||||
using Dalamud.Game.ClientState.JobGauge;
|
|
||||||
using Dalamud.Game.ClientState.Keys;
|
|
||||||
using Dalamud.Game.ClientState.Objects;
|
using Dalamud.Game.ClientState.Objects;
|
||||||
using Dalamud.Game.ClientState.Party;
|
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Game.Gui;
|
using Dalamud.Game.Gui;
|
||||||
using Dalamud.Game.Gui.FlyText;
|
|
||||||
using Dalamud.Game.Gui.PartyFinder;
|
|
||||||
using Dalamud.Game.Gui.Toast;
|
|
||||||
using Dalamud.Game.Libc;
|
|
||||||
using Dalamud.Game.Network;
|
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
|
||||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public class Dalamud
|
||||||
{
|
{
|
||||||
public class Dalamud
|
public static void Initialize(DalamudPluginInterface pluginInterface)
|
||||||
{
|
=> pluginInterface.Create<Dalamud>();
|
||||||
public static void Initialize(DalamudPluginInterface pluginInterface)
|
|
||||||
=> pluginInterface.Create<Dalamud>();
|
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static CommandManager Commands { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public static SigScanner SigScanner { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static DataManager GameData { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ClientState ClientState { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ChatGui Chat { get; private set; } = null!;
|
||||||
//[PluginService][RequiredVersion("1.0")] public static SeStringManager SeStrings { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static ChatHandlers ChatHandlers { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static Framework Framework { get; private set; } = null!;
|
||||||
//[PluginService][RequiredVersion("1.0")] public static GameNetwork Network { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public static Condition Conditions { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static KeyState Keys { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static GameGui GameGui { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static FlyTextGui FlyTexts { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static ToastGui Toasts { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static JobGauges Gauges { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static PartyFinderGui PartyFinder { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static BuddyList Buddies { get; private set; } = null!;
|
|
||||||
//[PluginService][RequiredVersion("1.0")] public static PartyList Party { get; private set; } = null!;
|
|
||||||
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static TargetManager Targets { get; private set; } = null!;
|
||||||
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
[PluginService][RequiredVersion("1.0")] public static ObjectTable Objects { get; private set; } = null!;
|
||||||
//[PluginService][RequiredVersion("1.0")] public static FateTable Fates { get; private set; } = null!;
|
// @formatter:on
|
||||||
//[PluginService][RequiredVersion("1.0")] public static LibcFunction LibC { get; private set; } = null!;
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Glamourer.FileSystem;
|
using Glamourer.FileSystem;
|
||||||
|
using Glamourer.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.PlayerWatch;
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
|
|
@ -122,19 +123,19 @@ namespace Glamourer.Designs
|
||||||
|
|
||||||
private void OnPlayerChange(Character character)
|
private void OnPlayerChange(Character character)
|
||||||
{
|
{
|
||||||
var name = character.Name.ToString();
|
//var name = character.Name.ToString();
|
||||||
if (!EnabledDesigns.TryGetValue(name, out var designs))
|
//if (!EnabledDesigns.TryGetValue(name, out var designs))
|
||||||
return;
|
// return;
|
||||||
|
//
|
||||||
var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id));
|
//var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(character.ClassJob.Id));
|
||||||
if (design == null)
|
//if (design == null)
|
||||||
return;
|
// return;
|
||||||
|
//
|
||||||
PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(),
|
//PluginLog.Debug("Redrawing {CharacterName} with {DesignName} for job {JobGroup}.", name, design.Design.FullName(),
|
||||||
design.Jobs.Name);
|
// design.Jobs.Name);
|
||||||
design.Design.Data.Apply(character);
|
//design.Design.Data.Apply(character);
|
||||||
Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
//Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||||
Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false);
|
//Glamourer.Penumbra.RedrawObject(character, RedrawType.Redraw, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(string name, Design design, JobGroup group, bool enabled = false)
|
public void Add(string name, Design design, JobGroup group, bool enabled = false)
|
||||||
|
|
|
||||||
|
|
@ -1,211 +1,337 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Glamourer.Api;
|
using Glamourer.Api;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.FileSystem;
|
using Glamourer.FileSystem;
|
||||||
using Glamourer.Gui;
|
using Glamourer.Gui;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.PlayerWatch;
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer
|
namespace Glamourer;
|
||||||
|
|
||||||
|
public unsafe class FixedDesignManager : IDisposable
|
||||||
{
|
{
|
||||||
public class Glamourer : IDalamudPlugin
|
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, uint* data);
|
||||||
|
|
||||||
|
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A",
|
||||||
|
DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||||
|
public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook;
|
||||||
|
|
||||||
|
public readonly FixedDesigns FixedDesigns;
|
||||||
|
|
||||||
|
public FixedDesignManager(DesignManager designs)
|
||||||
{
|
{
|
||||||
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
SignatureHelper.Initialise(this);
|
||||||
|
FixedDesigns = new FixedDesigns(designs);
|
||||||
public string Name
|
|
||||||
=> "Glamourer";
|
|
||||||
|
|
||||||
public static GlamourerConfig Config = null!;
|
|
||||||
public static IPlayerWatcher PlayerWatcher = null!;
|
|
||||||
public static ICustomizationManager Customization = null!;
|
|
||||||
private readonly Interface _interface;
|
|
||||||
public readonly DesignManager Designs;
|
|
||||||
public readonly FixedDesigns FixedDesigns;
|
|
||||||
public static RevertableDesigns RevertableDesigns = new();
|
|
||||||
public readonly GlamourerIpc GlamourerIpc;
|
|
||||||
|
|
||||||
|
|
||||||
public static string Version = string.Empty;
|
if (Glamourer.Config.ApplyFixedDesigns)
|
||||||
public static PenumbraAttach Penumbra = null!;
|
Enable();
|
||||||
|
}
|
||||||
|
|
||||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
public void Enable()
|
||||||
|
{
|
||||||
|
FlagSlotForUpdateHook?.Enable();
|
||||||
|
Glamourer.Penumbra.CreatingCharacterBase += ApplyFixedDesign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Disable()
|
||||||
|
{
|
||||||
|
FlagSlotForUpdateHook?.Disable();
|
||||||
|
Glamourer.Penumbra.CreatingCharacterBase -= ApplyFixedDesign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FlagSlotForUpdateHook?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyFixedDesign(IntPtr addr, IntPtr customize, IntPtr equipData)
|
||||||
|
{
|
||||||
|
var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr;
|
||||||
|
if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player
|
||||||
|
&& human->ModelCharaId == 0)
|
||||||
{
|
{
|
||||||
Dalamud.Initialize(pluginInterface);
|
var name = new Utf8String(human->GameObject.Name).ToString();
|
||||||
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "";
|
if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||||
Config = GlamourerConfig.Load();
|
|
||||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
|
||||||
Designs = new DesignManager();
|
|
||||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
|
||||||
PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
|
|
||||||
GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
|
||||||
if (!Config.ApplyFixedDesigns)
|
|
||||||
PlayerWatcher.Disable();
|
|
||||||
|
|
||||||
FixedDesigns = new FixedDesigns(Designs);
|
|
||||||
|
|
||||||
Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer)
|
|
||||||
{
|
{
|
||||||
HelpMessage = "Open or close the Glamourer window.",
|
var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob));
|
||||||
});
|
if (design != null)
|
||||||
Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour)
|
|
||||||
{
|
|
||||||
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
|
||||||
});
|
|
||||||
|
|
||||||
_interface = new Interface(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGlamourer(string command, string arguments)
|
|
||||||
=> _interface.ToggleVisibility();
|
|
||||||
|
|
||||||
private static GameObject? GetPlayer(string name)
|
|
||||||
{
|
|
||||||
var lowerName = name.ToLowerInvariant();
|
|
||||||
return lowerName switch
|
|
||||||
{
|
|
||||||
"" => null,
|
|
||||||
"<me>" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
|
||||||
"self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
|
||||||
"<t>" => Dalamud.Targets.Target,
|
|
||||||
"target" => Dalamud.Targets.Target,
|
|
||||||
"<f>" => Dalamud.Targets.FocusTarget,
|
|
||||||
"focus" => Dalamud.Targets.FocusTarget,
|
|
||||||
"<mo>" => Dalamud.Targets.MouseOverTarget,
|
|
||||||
"mouseover" => Dalamud.Targets.MouseOverTarget,
|
|
||||||
_ => Dalamud.Objects.LastOrDefault(
|
|
||||||
a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyToClipboard(Character player)
|
|
||||||
{
|
|
||||||
var save = new CharacterSave();
|
|
||||||
save.LoadCharacter(player);
|
|
||||||
ImGui.SetClipboardText(save.ToBase64());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyCommand(Character player, string target)
|
|
||||||
{
|
|
||||||
CharacterSave? save = null;
|
|
||||||
if (target.ToLowerInvariant() == "clipboard")
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
save = CharacterSave.FromString(ImGui.GetClipboardText());
|
if (design.Design.Data.WriteCustomizations)
|
||||||
}
|
*(CharacterCustomization*)customize = design.Design.Data.Customizations;
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string.");
|
|
||||||
}
|
|
||||||
else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
|
|
||||||
Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design.");
|
|
||||||
else
|
|
||||||
save = d.Data;
|
|
||||||
|
|
||||||
save?.Apply(player);
|
var data = (uint*)equipData;
|
||||||
Penumbra.UpdateCharacters(player);
|
for (var i = 0u; i < 10; ++i)
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveCommand(Character player, string path)
|
|
||||||
{
|
|
||||||
var save = new CharacterSave();
|
|
||||||
save.LoadCharacter(player);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var (folder, name) = Designs.FileSystem.CreateAllFolders(path);
|
|
||||||
var design = new Design(folder, name) { Data = save };
|
|
||||||
folder.FindOrAddChild(design);
|
|
||||||
Designs.Designs.Add(design.FullName(), design.Data);
|
|
||||||
Designs.SaveToFile();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Dalamud.Chat.PrintError("Could not save file:");
|
|
||||||
Dalamud.Chat.PrintError($" {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnGlamour(string command, string arguments)
|
|
||||||
{
|
|
||||||
static void PrintHelp()
|
|
||||||
{
|
|
||||||
Dalamud.Chat.Print("Usage:");
|
|
||||||
Dalamud.Chat.Print($" {HelpString}");
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments = arguments.Trim();
|
|
||||||
if (!arguments.Any())
|
|
||||||
{
|
|
||||||
PrintHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var split = arguments.Split(new[]
|
|
||||||
{
|
|
||||||
',',
|
|
||||||
}, 3, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
if (split.Length < 2)
|
|
||||||
{
|
|
||||||
PrintHelp();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = GetPlayer(split[1]) as Character;
|
|
||||||
if (player == null)
|
|
||||||
{
|
|
||||||
Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (split[0].ToLowerInvariant())
|
|
||||||
{
|
|
||||||
case "copy":
|
|
||||||
CopyToClipboard(player);
|
|
||||||
return;
|
|
||||||
case "apply":
|
|
||||||
{
|
|
||||||
if (split.Length < 3)
|
|
||||||
{
|
{
|
||||||
Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
|
var slot = i.ToEquipSlot();
|
||||||
return;
|
if (design.Design.Data.WriteEquipment.Fits(slot))
|
||||||
|
data[i] = slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||||
|
EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||||
|
EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||||
|
EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||||
|
EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||||
|
EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||||
|
EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||||
|
EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||||
|
EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||||
|
EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyCommand(player, split[2]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
case "save":
|
|
||||||
{
|
|
||||||
if (split.Length < 3)
|
|
||||||
{
|
|
||||||
Dalamud.Chat.Print("Saving requires a name for the save.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SaveCommand(player, split[2]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
PrintHelp();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public void Dispose()
|
|
||||||
{
|
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data)
|
||||||
FixedDesigns.Dispose();
|
{
|
||||||
Penumbra.Dispose();
|
ulong ret;
|
||||||
PlayerWatcher.Dispose();
|
var slot = slotIdx.ToEquipSlot();
|
||||||
_interface.Dispose();
|
try
|
||||||
GlamourerIpc.Dispose();
|
{
|
||||||
Dalamud.Commands.RemoveHandler("/glamour");
|
if (slot != EquipSlot.Unknown)
|
||||||
Dalamud.Commands.RemoveHandler("/glamourer");
|
{
|
||||||
}
|
var gameObject =
|
||||||
|
(FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||||
|
if (gameObject != null)
|
||||||
|
{
|
||||||
|
var name = new Utf8String(gameObject->GameObject.Name).ToString();
|
||||||
|
if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||||
|
{
|
||||||
|
var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob));
|
||||||
|
if (design != null && design.Design.Data.WriteEquipment.Fits(slot))
|
||||||
|
*data = slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
|
||||||
|
EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
|
||||||
|
EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
|
||||||
|
EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
|
||||||
|
EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
|
||||||
|
EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
|
||||||
|
EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
|
||||||
|
EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
|
||||||
|
EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
|
||||||
|
EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Glamourer : IDalamudPlugin
|
||||||
|
{
|
||||||
|
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
=> "Glamourer";
|
||||||
|
|
||||||
|
public static GlamourerConfig Config = null!;
|
||||||
|
public static IPlayerWatcher PlayerWatcher = null!;
|
||||||
|
public static ICustomizationManager Customization = null!;
|
||||||
|
public static FixedDesignManager FixedDesignManager = null!;
|
||||||
|
private readonly Interface _interface;
|
||||||
|
public readonly DesignManager Designs;
|
||||||
|
|
||||||
|
public static RevertableDesigns RevertableDesigns = new();
|
||||||
|
public readonly GlamourerIpc GlamourerIpc;
|
||||||
|
|
||||||
|
|
||||||
|
public static string Version = string.Empty;
|
||||||
|
public static PenumbraAttach Penumbra = null!;
|
||||||
|
|
||||||
|
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||||
|
{
|
||||||
|
Dalamud.Initialize(pluginInterface);
|
||||||
|
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "";
|
||||||
|
Config = GlamourerConfig.Load();
|
||||||
|
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||||
|
Designs = new DesignManager();
|
||||||
|
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||||
|
PlayerWatcher = PlayerWatchFactory.Create(Dalamud.Framework, Dalamud.ClientState, Dalamud.Objects);
|
||||||
|
GlamourerIpc = new GlamourerIpc(Dalamud.ClientState, Dalamud.Objects, Dalamud.PluginInterface);
|
||||||
|
FixedDesignManager = new FixedDesignManager(Designs);
|
||||||
|
if (!Config.ApplyFixedDesigns)
|
||||||
|
PlayerWatcher.Disable();
|
||||||
|
|
||||||
|
Dalamud.Commands.AddHandler("/glamourer", new CommandInfo(OnGlamourer)
|
||||||
|
{
|
||||||
|
HelpMessage = "Open or close the Glamourer window.",
|
||||||
|
});
|
||||||
|
Dalamud.Commands.AddHandler("/glamour", new CommandInfo(OnGlamour)
|
||||||
|
{
|
||||||
|
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
||||||
|
});
|
||||||
|
|
||||||
|
_interface = new Interface(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGlamourer(string command, string arguments)
|
||||||
|
=> _interface.ToggleVisibility();
|
||||||
|
|
||||||
|
private static GameObject? GetPlayer(string name)
|
||||||
|
{
|
||||||
|
var lowerName = name.ToLowerInvariant();
|
||||||
|
return lowerName switch
|
||||||
|
{
|
||||||
|
"" => null,
|
||||||
|
"<me>" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||||
|
"self" => Dalamud.Objects[Interface.GPoseObjectId] ?? Dalamud.ClientState.LocalPlayer,
|
||||||
|
"<t>" => Dalamud.Targets.Target,
|
||||||
|
"target" => Dalamud.Targets.Target,
|
||||||
|
"<f>" => Dalamud.Targets.FocusTarget,
|
||||||
|
"focus" => Dalamud.Targets.FocusTarget,
|
||||||
|
"<mo>" => Dalamud.Targets.MouseOverTarget,
|
||||||
|
"mouseover" => Dalamud.Targets.MouseOverTarget,
|
||||||
|
_ => Dalamud.Objects.LastOrDefault(
|
||||||
|
a => string.Equals(a.Name.ToString(), lowerName, StringComparison.InvariantCultureIgnoreCase)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyToClipboard(Character player)
|
||||||
|
{
|
||||||
|
var save = new CharacterSave();
|
||||||
|
save.LoadCharacter(player);
|
||||||
|
ImGui.SetClipboardText(save.ToBase64());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyCommand(Character player, string target)
|
||||||
|
{
|
||||||
|
CharacterSave? save = null;
|
||||||
|
if (target.ToLowerInvariant() == "clipboard")
|
||||||
|
try
|
||||||
|
{
|
||||||
|
save = CharacterSave.FromString(ImGui.GetClipboardText());
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Dalamud.Chat.PrintError("Clipboard does not contain a valid customization string.");
|
||||||
|
}
|
||||||
|
else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
|
||||||
|
Dalamud.Chat.PrintError("The given path to a saved design does not exist or does not point to a design.");
|
||||||
|
else
|
||||||
|
save = d.Data;
|
||||||
|
|
||||||
|
save?.Apply(player);
|
||||||
|
Penumbra.UpdateCharacters(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveCommand(Character player, string path)
|
||||||
|
{
|
||||||
|
var save = new CharacterSave();
|
||||||
|
save.LoadCharacter(player);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (folder, name) = Designs.FileSystem.CreateAllFolders(path);
|
||||||
|
var design = new Design(folder, name) { Data = save };
|
||||||
|
folder.FindOrAddChild(design);
|
||||||
|
Designs.Designs.Add(design.FullName(), design.Data);
|
||||||
|
Designs.SaveToFile();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Dalamud.Chat.PrintError("Could not save file:");
|
||||||
|
Dalamud.Chat.PrintError($" {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnGlamour(string command, string arguments)
|
||||||
|
{
|
||||||
|
static void PrintHelp()
|
||||||
|
{
|
||||||
|
Dalamud.Chat.Print("Usage:");
|
||||||
|
Dalamud.Chat.Print($" {HelpString}");
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments = arguments.Trim();
|
||||||
|
if (!arguments.Any())
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = arguments.Split(new[]
|
||||||
|
{
|
||||||
|
',',
|
||||||
|
}, 3, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
if (split.Length < 2)
|
||||||
|
{
|
||||||
|
PrintHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = GetPlayer(split[1]) as Character;
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
Dalamud.Chat.Print($"Could not find object for {split[1]} or it was not a Character.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (split[0].ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "copy":
|
||||||
|
CopyToClipboard(player);
|
||||||
|
return;
|
||||||
|
case "apply":
|
||||||
|
{
|
||||||
|
if (split.Length < 3)
|
||||||
|
{
|
||||||
|
Dalamud.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyCommand(player, split[2]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "save":
|
||||||
|
{
|
||||||
|
if (split.Length < 3)
|
||||||
|
{
|
||||||
|
Dalamud.Chat.Print("Saving requires a name for the save.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveCommand(player, split[2]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
PrintHelp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
FixedDesignManager.Dispose();
|
||||||
|
Penumbra.Dispose();
|
||||||
|
PlayerWatcher.Dispose();
|
||||||
|
_interface.Dispose();
|
||||||
|
GlamourerIpc.Dispose();
|
||||||
|
Dalamud.Commands.RemoveHandler("/glamour");
|
||||||
|
Dalamud.Commands.RemoveHandler("/glamourer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,10 @@
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="ImGui.NET">
|
<Reference Include="ImGui.NET">
|
||||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
|
|
||||||
|
|
@ -4,205 +4,204 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
public class ComboWithFilter<T>
|
||||||
{
|
{
|
||||||
public class ComboWithFilter<T>
|
private readonly string _label;
|
||||||
|
private readonly string _filterLabel;
|
||||||
|
private readonly string _listLabel;
|
||||||
|
private string _currentFilter = string.Empty;
|
||||||
|
private string _currentFilterLower = string.Empty;
|
||||||
|
private bool _focus;
|
||||||
|
private readonly float _size;
|
||||||
|
private float _previewSize;
|
||||||
|
private readonly IReadOnlyList<T> _items;
|
||||||
|
private readonly IReadOnlyList<(string, int)> _itemNamesLower;
|
||||||
|
private readonly Func<T, string> _itemToName;
|
||||||
|
private IReadOnlyList<(string, int)> _currentItemNames;
|
||||||
|
private bool _needsClear;
|
||||||
|
|
||||||
|
public Action? PrePreview;
|
||||||
|
public Action? PostPreview;
|
||||||
|
public Func<T, bool>? CreateSelectable;
|
||||||
|
public Action? PreList;
|
||||||
|
public Action? PostList;
|
||||||
|
public float? HeightPerItem;
|
||||||
|
|
||||||
|
private float _heightPerItem;
|
||||||
|
|
||||||
|
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
|
||||||
|
public int ItemsAtOnce { get; set; } = 12;
|
||||||
|
|
||||||
|
private void UpdateFilter(string newFilter)
|
||||||
{
|
{
|
||||||
private readonly string _label;
|
if (newFilter == _currentFilter)
|
||||||
private readonly string _filterLabel;
|
return;
|
||||||
private readonly string _listLabel;
|
|
||||||
private string _currentFilter = string.Empty;
|
|
||||||
private string _currentFilterLower = string.Empty;
|
|
||||||
private bool _focus;
|
|
||||||
private readonly float _size;
|
|
||||||
private float _previewSize;
|
|
||||||
private readonly IReadOnlyList<T> _items;
|
|
||||||
private readonly IReadOnlyList<(string, int)> _itemNamesLower;
|
|
||||||
private readonly Func<T, string> _itemToName;
|
|
||||||
private IReadOnlyList<(string, int)> _currentItemNames;
|
|
||||||
private bool _needsClear;
|
|
||||||
|
|
||||||
public Action? PrePreview;
|
var lower = newFilter.ToLowerInvariant();
|
||||||
public Action? PostPreview;
|
if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower))
|
||||||
public Func<T, bool>? CreateSelectable;
|
_currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray();
|
||||||
public Action? PreList;
|
else if (lower.Any())
|
||||||
public Action? PostList;
|
_currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray();
|
||||||
public float? HeightPerItem;
|
else
|
||||||
|
|
||||||
private float _heightPerItem;
|
|
||||||
|
|
||||||
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
|
|
||||||
public int ItemsAtOnce { get; set; } = 12;
|
|
||||||
|
|
||||||
private void UpdateFilter(string newFilter)
|
|
||||||
{
|
|
||||||
if (newFilter == _currentFilter)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var lower = newFilter.ToLowerInvariant();
|
|
||||||
if (_currentFilterLower.Any() && lower.Contains(_currentFilterLower))
|
|
||||||
_currentItemNames = _currentItemNames.Where(p => p.Item1.Contains(lower)).ToArray();
|
|
||||||
else if (lower.Any())
|
|
||||||
_currentItemNames = _itemNamesLower.Where(p => p.Item1.Contains(lower)).ToArray();
|
|
||||||
else
|
|
||||||
_currentItemNames = _itemNamesLower;
|
|
||||||
_currentFilter = newFilter;
|
|
||||||
_currentFilterLower = lower;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList<T> items, Func<T, string> itemToName)
|
|
||||||
{
|
|
||||||
_label = label;
|
|
||||||
_filterLabel = $"##_{label}_filter";
|
|
||||||
_listLabel = $"##_{label}_list";
|
|
||||||
_itemToName = itemToName;
|
|
||||||
_items = items;
|
|
||||||
_size = size;
|
|
||||||
_previewSize = previewSize;
|
|
||||||
|
|
||||||
_itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray();
|
|
||||||
_currentItemNames = _itemNamesLower;
|
_currentItemNames = _itemNamesLower;
|
||||||
|
_currentFilter = newFilter;
|
||||||
|
_currentFilterLower = lower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComboWithFilter(string label, float size, float previewSize, IReadOnlyList<T> items, Func<T, string> itemToName)
|
||||||
|
{
|
||||||
|
_label = label;
|
||||||
|
_filterLabel = $"##_{label}_filter";
|
||||||
|
_listLabel = $"##_{label}_list";
|
||||||
|
_itemToName = itemToName;
|
||||||
|
_items = items;
|
||||||
|
_size = size;
|
||||||
|
_previewSize = previewSize;
|
||||||
|
|
||||||
|
_itemNamesLower = _items.Select((i, idx) => (_itemToName(i).ToLowerInvariant(), idx)).ToArray();
|
||||||
|
_currentItemNames = _itemNamesLower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComboWithFilter(string label, ComboWithFilter<T> other)
|
||||||
|
{
|
||||||
|
_label = label;
|
||||||
|
_filterLabel = $"##_{label}_filter";
|
||||||
|
_listLabel = $"##_{label}_list";
|
||||||
|
_itemToName = other._itemToName;
|
||||||
|
_items = other._items;
|
||||||
|
_itemNamesLower = other._itemNamesLower;
|
||||||
|
_currentItemNames = other._currentItemNames;
|
||||||
|
_size = other._size;
|
||||||
|
_previewSize = other._previewSize;
|
||||||
|
PrePreview = other.PrePreview;
|
||||||
|
PostPreview = other.PostPreview;
|
||||||
|
CreateSelectable = other.CreateSelectable;
|
||||||
|
PreList = other.PreList;
|
||||||
|
PostList = other.PostList;
|
||||||
|
HeightPerItem = other.HeightPerItem;
|
||||||
|
Flags = other.Flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value)
|
||||||
|
{
|
||||||
|
numItems = ItemsAtOnce;
|
||||||
|
nodeIdx = -1;
|
||||||
|
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
|
||||||
|
{
|
||||||
|
ImGui.EndChild();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComboWithFilter(string label, ComboWithFilter<T> other)
|
var ret = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
_label = label;
|
if (!_focus)
|
||||||
_filterLabel = $"##_{label}_filter";
|
|
||||||
_listLabel = $"##_{label}_list";
|
|
||||||
_itemToName = other._itemToName;
|
|
||||||
_items = other._items;
|
|
||||||
_itemNamesLower = other._itemNamesLower;
|
|
||||||
_currentItemNames = other._currentItemNames;
|
|
||||||
_size = other._size;
|
|
||||||
_previewSize = other._previewSize;
|
|
||||||
PrePreview = other.PrePreview;
|
|
||||||
PostPreview = other.PostPreview;
|
|
||||||
CreateSelectable = other.CreateSelectable;
|
|
||||||
PreList = other.PreList;
|
|
||||||
PostList = other.PostList;
|
|
||||||
HeightPerItem = other.HeightPerItem;
|
|
||||||
Flags = other.Flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value)
|
|
||||||
{
|
|
||||||
numItems = ItemsAtOnce;
|
|
||||||
nodeIdx = -1;
|
|
||||||
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
|
|
||||||
{
|
{
|
||||||
ImGui.EndChild();
|
ImGui.SetScrollY(0);
|
||||||
return false;
|
_focus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret = false;
|
var scrollY = Math.Max((int)(ImGui.GetScrollY() / _heightPerItem) - 1, 0);
|
||||||
try
|
var restHeight = scrollY * _heightPerItem;
|
||||||
|
numItems = 0;
|
||||||
|
nodeIdx = 0;
|
||||||
|
|
||||||
|
if (restHeight > 0)
|
||||||
|
ImGui.Dummy(Vector2.UnitY * restHeight);
|
||||||
|
|
||||||
|
for (var i = scrollY; i < _currentItemNames.Count; ++i)
|
||||||
{
|
{
|
||||||
if (!_focus)
|
if (++numItems > ItemsAtOnce + 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
nodeIdx = _currentItemNames[i].Item2;
|
||||||
|
var item = _items[nodeIdx]!;
|
||||||
|
bool success;
|
||||||
|
if (CreateSelectable != null)
|
||||||
{
|
{
|
||||||
ImGui.SetScrollY(0);
|
success = CreateSelectable(item);
|
||||||
_focus = true;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var name = _itemToName(item);
|
||||||
|
success = ImGui.Selectable(name, name == currentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var scrollY = Math.Max((int) (ImGui.GetScrollY() / _heightPerItem) - 1, 0);
|
if (success)
|
||||||
var restHeight = scrollY * _heightPerItem;
|
|
||||||
numItems = 0;
|
|
||||||
nodeIdx = 0;
|
|
||||||
|
|
||||||
if (restHeight > 0)
|
|
||||||
ImGui.Dummy(Vector2.UnitY * restHeight);
|
|
||||||
|
|
||||||
for (var i = scrollY; i < _currentItemNames.Count; ++i)
|
|
||||||
{
|
{
|
||||||
if (++numItems > ItemsAtOnce + 2)
|
value = item;
|
||||||
continue;
|
|
||||||
|
|
||||||
nodeIdx = _currentItemNames[i].Item2;
|
|
||||||
var item = _items[nodeIdx]!;
|
|
||||||
bool success;
|
|
||||||
if (CreateSelectable != null)
|
|
||||||
{
|
|
||||||
success = CreateSelectable(item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var name = _itemToName(item);
|
|
||||||
success = ImGui.Selectable(name, name == currentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
value = item;
|
|
||||||
ImGui.CloseCurrentPopup();
|
|
||||||
ret = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentItemNames.Count > ItemsAtOnce + 2)
|
|
||||||
ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Draw(string currentName, out T? value, float? size = null)
|
|
||||||
{
|
|
||||||
if (size.HasValue)
|
|
||||||
_previewSize = size.Value;
|
|
||||||
|
|
||||||
value = default;
|
|
||||||
ImGui.SetNextItemWidth(_previewSize);
|
|
||||||
PrePreview?.Invoke();
|
|
||||||
if (!ImGui.BeginCombo(_label, currentName, Flags))
|
|
||||||
{
|
|
||||||
if (_needsClear)
|
|
||||||
{
|
|
||||||
_needsClear = false;
|
|
||||||
_focus = false;
|
|
||||||
UpdateFilter(string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
PostPreview?.Invoke();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_needsClear = true;
|
|
||||||
PostPreview?.Invoke();
|
|
||||||
|
|
||||||
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
|
|
||||||
|
|
||||||
bool ret;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth(-1);
|
|
||||||
var tmp = _currentFilter;
|
|
||||||
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255))
|
|
||||||
UpdateFilter(tmp);
|
|
||||||
|
|
||||||
var isFocused = ImGui.IsItemActive();
|
|
||||||
if (!_focus)
|
|
||||||
ImGui.SetKeyboardFocusHere();
|
|
||||||
|
|
||||||
PreList?.Invoke();
|
|
||||||
ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value);
|
|
||||||
PostList?.Invoke();
|
|
||||||
|
|
||||||
if (!isFocused && numItems <= 1 && nodeIdx >= 0)
|
|
||||||
{
|
|
||||||
value = _items[nodeIdx];
|
|
||||||
ret = true;
|
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
|
ret = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
if (_currentItemNames.Count > ItemsAtOnce + 2)
|
||||||
|
ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Draw(string currentName, out T? value, float? size = null)
|
||||||
|
{
|
||||||
|
if (size.HasValue)
|
||||||
|
_previewSize = size.Value;
|
||||||
|
|
||||||
|
value = default;
|
||||||
|
ImGui.SetNextItemWidth(_previewSize);
|
||||||
|
PrePreview?.Invoke();
|
||||||
|
if (!ImGui.BeginCombo(_label, currentName, Flags))
|
||||||
|
{
|
||||||
|
if (_needsClear)
|
||||||
{
|
{
|
||||||
ImGui.EndCombo();
|
_needsClear = false;
|
||||||
|
_focus = false;
|
||||||
|
UpdateFilter(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
PostPreview?.Invoke();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_needsClear = true;
|
||||||
|
PostPreview?.Invoke();
|
||||||
|
|
||||||
|
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
|
||||||
|
|
||||||
|
bool ret;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
var tmp = _currentFilter;
|
||||||
|
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255))
|
||||||
|
UpdateFilter(tmp);
|
||||||
|
|
||||||
|
var isFocused = ImGui.IsItemActive();
|
||||||
|
if (!_focus)
|
||||||
|
ImGui.SetKeyboardFocusHere();
|
||||||
|
|
||||||
|
PreList?.Invoke();
|
||||||
|
ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value);
|
||||||
|
PostList?.Invoke();
|
||||||
|
|
||||||
|
if (!isFocused && numItems <= 1 && nodeIdx >= 0)
|
||||||
|
{
|
||||||
|
value = _items[nodeIdx];
|
||||||
|
ret = true;
|
||||||
|
ImGui.CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
|
||||||
{
|
|
||||||
public static partial class ImGuiCustom
|
|
||||||
{
|
|
||||||
public static void HoverTooltip(string text)
|
|
||||||
{
|
|
||||||
if (text.Any() && ImGui.IsItemHovered())
|
|
||||||
ImGui.SetTooltip(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,155 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Numerics;
|
|
||||||
using ImGuiNET;
|
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
|
||||||
{
|
|
||||||
public sealed class ImGuiRaii : IDisposable
|
|
||||||
{
|
|
||||||
private int _colorStack;
|
|
||||||
private int _fontStack;
|
|
||||||
private int _styleStack;
|
|
||||||
private float _indentation;
|
|
||||||
|
|
||||||
private Stack<Action>? _onDispose;
|
|
||||||
|
|
||||||
public static ImGuiRaii NewGroup()
|
|
||||||
=> new ImGuiRaii().Group();
|
|
||||||
|
|
||||||
public ImGuiRaii Group()
|
|
||||||
=> Begin(ImGui.BeginGroup, ImGui.EndGroup);
|
|
||||||
|
|
||||||
public static ImGuiRaii NewTooltip()
|
|
||||||
=> new ImGuiRaii().Tooltip();
|
|
||||||
|
|
||||||
public ImGuiRaii Tooltip()
|
|
||||||
=> Begin(ImGui.BeginTooltip, ImGui.EndTooltip);
|
|
||||||
|
|
||||||
public ImGuiRaii PushColor(ImGuiCol which, uint color)
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor(which, color);
|
|
||||||
++_colorStack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PushColor(ImGuiCol which, Vector4 color)
|
|
||||||
{
|
|
||||||
ImGui.PushStyleColor(which, color);
|
|
||||||
++_colorStack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PopColors(int n = 1)
|
|
||||||
{
|
|
||||||
var actualN = Math.Min(n, _colorStack);
|
|
||||||
if (actualN > 0)
|
|
||||||
{
|
|
||||||
ImGui.PopStyleColor(actualN);
|
|
||||||
_colorStack -= actualN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PushStyle(ImGuiStyleVar style, Vector2 value)
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar(style, value);
|
|
||||||
++_styleStack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PushStyle(ImGuiStyleVar style, float value)
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar(style, value);
|
|
||||||
++_styleStack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PopStyles(int n = 1)
|
|
||||||
{
|
|
||||||
var actualN = Math.Min(n, _styleStack);
|
|
||||||
if (actualN > 0)
|
|
||||||
{
|
|
||||||
ImGui.PopStyleVar(actualN);
|
|
||||||
_styleStack -= actualN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PushFont(ImFontPtr font)
|
|
||||||
{
|
|
||||||
ImGui.PushFont(font);
|
|
||||||
++_fontStack;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii PopFonts(int n = 1)
|
|
||||||
{
|
|
||||||
var actualN = Math.Min(n, _fontStack);
|
|
||||||
|
|
||||||
while (actualN-- > 0)
|
|
||||||
{
|
|
||||||
ImGui.PopFont();
|
|
||||||
--_fontStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii Indent(float width)
|
|
||||||
{
|
|
||||||
if (width != 0)
|
|
||||||
{
|
|
||||||
ImGui.Indent(width);
|
|
||||||
_indentation += width;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii Unindent(float width)
|
|
||||||
=> Indent(-width);
|
|
||||||
|
|
||||||
public bool Begin(Func<bool> begin, Action end)
|
|
||||||
{
|
|
||||||
if (begin())
|
|
||||||
{
|
|
||||||
_onDispose ??= new Stack<Action>();
|
|
||||||
_onDispose.Push(end);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImGuiRaii Begin(Action begin, Action end)
|
|
||||||
{
|
|
||||||
begin();
|
|
||||||
_onDispose ??= new Stack<Action>();
|
|
||||||
_onDispose.Push(end);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void End(int n = 1)
|
|
||||||
{
|
|
||||||
var actualN = Math.Min(n, _onDispose?.Count ?? 0);
|
|
||||||
while (actualN-- > 0)
|
|
||||||
_onDispose!.Pop()();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Unindent(_indentation);
|
|
||||||
PopColors(_colorStack);
|
|
||||||
PopStyles(_styleStack);
|
|
||||||
PopFonts(_fontStack);
|
|
||||||
if (_onDispose != null)
|
|
||||||
{
|
|
||||||
End(_onDispose.Count);
|
|
||||||
_onDispose = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,104 +7,104 @@ using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface : IDisposable
|
||||||
{
|
{
|
||||||
internal partial class Interface : IDisposable
|
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, ModelChara> _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)
|
||||||
{
|
{
|
||||||
public const float SelectorWidth = 200;
|
_plugin = plugin;
|
||||||
public const float MinWindowWidth = 675;
|
_designs = plugin.Designs;
|
||||||
public const int GPoseObjectId = 201;
|
_glamourerHeader = Glamourer.Version.Length > 0
|
||||||
private const string PluginName = "Glamourer";
|
? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
|
||||||
private readonly string _glamourerHeader;
|
: $"{PluginName}###{PluginName}Main";
|
||||||
|
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||||
|
Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
||||||
|
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
|
||||||
|
|
||||||
private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
_equipSlotNames = GetEquipSlotNames();
|
||||||
private readonly IReadOnlyDictionary<uint, ModelChara> _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;
|
_stains = GameData.Stains(Dalamud.GameData);
|
||||||
private bool _inGPose;
|
_models = GameData.Models(Dalamud.GameData);
|
||||||
|
_identifier = Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||||
|
|
||||||
public Interface(Glamourer plugin)
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_legacyTattooIcon?.Dispose();
|
||||||
|
Dalamud.PluginInterface.UiBuilder.Draw -= Draw;
|
||||||
|
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
_plugin = plugin;
|
ImGui.End();
|
||||||
_designs = plugin.Designs;
|
return;
|
||||||
_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()
|
try
|
||||||
=> _visible = !_visible;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
_legacyTattooIcon?.Dispose();
|
using var tabBar = ImRaii.TabBar("##tabBar");
|
||||||
Dalamud.PluginInterface.UiBuilder.Draw -= Draw;
|
if (!tabBar)
|
||||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility;
|
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
|
||||||
private void Draw()
|
|
||||||
{
|
{
|
||||||
if (!_visible)
|
ImGui.End();
|
||||||
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 raii = new ImGuiRaii();
|
|
||||||
if (!raii.Begin(() => ImGui.BeginTabBar("##tabBar"), ImGui.EndTabBar))
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,300 +11,298 @@ using Glamourer.Customization;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.FileSystem;
|
using Glamourer.FileSystem;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.PlayerWatch;
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
{
|
{
|
||||||
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()
|
||||||
{
|
{
|
||||||
private readonly CharacterSave _currentSave = new();
|
var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
||||||
private string _newDesignName = string.Empty;
|
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||||
private bool _keyboardFocus;
|
using var c = ImRaii.PushColor(ImGuiCol.Text, color)
|
||||||
private bool _holdShift;
|
.Push(ImGuiCol.Button, buttonColor)
|
||||||
private bool _holdCtrl;
|
.Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||||
private const string DesignNamePopupLabel = "Save Design As...";
|
.Push(ImGuiCol.ButtonActive, buttonColor);
|
||||||
private const uint RedHeaderColor = 0xFF1818C0;
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
private const uint GreenHeaderColor = 0xFF18C018;
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
|
ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPlayerHeader()
|
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 void ConditionalApply(CharacterSave save, Character player)
|
||||||
|
{
|
||||||
|
if (ImGui.GetIO().KeyShift)
|
||||||
|
save.ApplyOnlyCustomizations(player);
|
||||||
|
else if (ImGui.GetIO().KeyCtrl)
|
||||||
|
save.ApplyOnlyEquipment(player);
|
||||||
|
else
|
||||||
|
save.Apply(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CharacterSave ConditionalCopy(CharacterSave save, bool shift, bool ctrl)
|
||||||
|
{
|
||||||
|
var copy = save.Copy();
|
||||||
|
if (shift)
|
||||||
{
|
{
|
||||||
var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
copy.Load(new CharacterEquipment());
|
||||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
copy.SetHatState = false;
|
||||||
using var raii = new ImGuiRaii()
|
copy.SetVisorState = false;
|
||||||
.PushColor(ImGuiCol.Text, color)
|
copy.SetWeaponState = false;
|
||||||
.PushColor(ImGuiCol.Button, buttonColor)
|
copy.WriteEquipment = CharacterEquipMask.None;
|
||||||
.PushColor(ImGuiCol.ButtonHovered, buttonColor)
|
}
|
||||||
.PushColor(ImGuiCol.ButtonActive, buttonColor)
|
else if (ctrl)
|
||||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
{
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
copy.Load(CharacterCustomization.Default);
|
||||||
ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
copy.SetHatState = false;
|
||||||
|
copy.SetVisorState = false;
|
||||||
|
copy.SetWeaponState = false;
|
||||||
|
copy.WriteCustomizations = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawCopyClipboardButton(CharacterSave save)
|
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
|
||||||
{
|
{
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
var text = ImGui.GetClipboardText();
|
||||||
if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
if (!text.Any())
|
||||||
ImGui.SetClipboardText(save.ToBase64());
|
|
||||||
ImGui.PopFont();
|
|
||||||
ImGuiCustom.HoverTooltip("Copy customization code to clipboard.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConditionalApply(CharacterSave save, Character player)
|
|
||||||
{
|
|
||||||
if (ImGui.GetIO().KeyShift)
|
|
||||||
save.ApplyOnlyCustomizations(player);
|
|
||||||
else if (ImGui.GetIO().KeyCtrl)
|
|
||||||
save.ApplyOnlyEquipment(player);
|
|
||||||
else
|
|
||||||
save.Apply(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
ImGuiCustom.HoverTooltip("Apply customization code from clipboard.\nHold Shift to apply only customizations.\nHold Control to apply only equipment.");
|
|
||||||
|
|
||||||
if (!applyButton)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try
|
var save = CharacterSave.FromString(text);
|
||||||
{
|
ConditionalApply(save, _player!);
|
||||||
var text = ImGui.GetClipboardText();
|
}
|
||||||
if (!text.Any())
|
catch (Exception e)
|
||||||
return false;
|
{
|
||||||
var save = CharacterSave.FromString(text);
|
PluginLog.Information($"{e}");
|
||||||
ConditionalApply(save, _player!);
|
return false;
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Information($"{e}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawSaveDesignButton()
|
return true;
|
||||||
{
|
}
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
|
|
||||||
OpenDesignNamePopup(DesignNameUse.SaveCurrent);
|
|
||||||
|
|
||||||
ImGui.PopFont();
|
private void DrawSaveDesignButton()
|
||||||
ImGuiCustom.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Save.ToIconString()))
|
||||||
|
OpenDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||||
|
|
||||||
DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
ImGui.PopFont();
|
||||||
}
|
ImGuiUtil.HoverTooltip("Save the current design.\nHold Shift to save only customizations.\nHold Control to save only equipment.");
|
||||||
|
|
||||||
private void DrawTargetPlayerButton()
|
DrawDesignNamePopup(DesignNameUse.SaveCurrent);
|
||||||
{
|
}
|
||||||
if (ImGui.Button("Target Player"))
|
|
||||||
Dalamud.Targets.SetTarget(_player);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawApplyToPlayerButton(CharacterSave save)
|
private void DrawTargetPlayerButton()
|
||||||
{
|
{
|
||||||
if (!ImGui.Button("Apply to Self"))
|
if (ImGui.Button("Target Player"))
|
||||||
return;
|
Dalamud.Targets.SetTarget(_player);
|
||||||
|
}
|
||||||
|
|
||||||
var player = _inGPose
|
private void DrawApplyToPlayerButton(CharacterSave save)
|
||||||
? (Character?) Dalamud.Objects[GPoseObjectId]
|
{
|
||||||
: Dalamud.ClientState.LocalPlayer;
|
if (!ImGui.Button("Apply to Self"))
|
||||||
var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
return;
|
||||||
if (player == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
ConditionalApply(save, player);
|
var player = _inGPose
|
||||||
if (_inGPose)
|
? (Character?)Dalamud.Objects[GPoseObjectId]
|
||||||
ConditionalApply(save, fallback!);
|
: Dalamud.ClientState.LocalPlayer;
|
||||||
Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
||||||
}
|
if (player == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ConditionalApply(save, player);
|
||||||
|
if (_inGPose)
|
||||||
|
ConditionalApply(save, fallback!);
|
||||||
|
Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Character? TransformToCustomizable(Character? actor)
|
private static Character? TransformToCustomizable(Character? actor)
|
||||||
{
|
{
|
||||||
if (actor == null)
|
if (actor == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (actor.ModelType() == 0)
|
if (actor.ModelType() == 0)
|
||||||
return actor;
|
|
||||||
|
|
||||||
actor.SetModelType(0);
|
|
||||||
CharacterCustomization.Default.Write(actor.Address);
|
|
||||||
return actor;
|
return actor;
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawApplyToTargetButton(CharacterSave save)
|
actor.SetModelType(0);
|
||||||
|
CharacterCustomization.Default.Write(actor.Address);
|
||||||
|
return actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawApplyToTargetButton(CharacterSave save)
|
||||||
|
{
|
||||||
|
if (!ImGui.Button("Apply to Target"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target));
|
||||||
|
if (player == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var fallBackCharacter = _gPoseActors.TryGetValue(player.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
|
||||||
{
|
{
|
||||||
if (!ImGui.Button("Apply to Target"))
|
var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
||||||
|
if (!name.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var player = TransformToCustomizable(CharacterFactory.Convert(Dalamud.Targets.Target));
|
var newDesign = new Design(folder, name) { Data = save };
|
||||||
if (player == null)
|
folder.AddChild(newDesign);
|
||||||
return;
|
_designs.Designs[newDesign.FullName()] = save;
|
||||||
|
_designs.SaveToFile();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error($"Could not save new design {_newDesignName}:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null;
|
private void DrawMonsterPanel()
|
||||||
ConditionalApply(save, player);
|
{
|
||||||
if (fallBackCharacter != null)
|
if (DrawApplyClipboardButton())
|
||||||
ConditionalApply(save, fallBackCharacter!);
|
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||||
Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImGui.Button("Convert to Character"))
|
||||||
|
{
|
||||||
|
TransformToCustomizable(_player);
|
||||||
|
_currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
||||||
|
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertButton()
|
if (!_inGPose)
|
||||||
{
|
{
|
||||||
if (!DrawDisableButton("Revert", _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 void DrawMonsterPanel()
|
|
||||||
{
|
|
||||||
if (DrawApplyClipboardButton())
|
|
||||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button("Convert to Character"))
|
DrawTargetPlayerButton();
|
||||||
{
|
}
|
||||||
TransformToCustomizable(_player);
|
|
||||||
_currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
|
||||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_inGPose)
|
var currentModel = _player!.ModelType();
|
||||||
|
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;
|
||||||
|
|
||||||
|
_player!.SetModelType((int)id);
|
||||||
|
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();
|
ImGui.SameLine();
|
||||||
DrawTargetPlayerButton();
|
DrawTargetPlayerButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentModel = _player!.ModelType();
|
|
||||||
using var raii = new ImGuiRaii();
|
|
||||||
if (!raii.Begin(() => ImGui.BeginCombo("Model Id", currentModel.ToString()), ImGui.EndCombo))
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var (id, _) in _models.Skip(1))
|
|
||||||
{
|
|
||||||
if (!ImGui.Selectable($"{id:D6}##models", id == currentModel) || id == currentModel)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
_player!.SetModelType((int) id);
|
|
||||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPlayerPanel()
|
var data = _currentSave;
|
||||||
|
if (!_currentSave.WriteProtected)
|
||||||
{
|
{
|
||||||
DrawCopyClipboardButton(_currentSave);
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
var changes = !_currentSave.WriteProtected && DrawApplyClipboardButton();
|
DrawRevertButton();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
private void DrawActorPanel()
|
|
||||||
{
|
{
|
||||||
using var raii = ImGuiRaii.NewGroup();
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||||
DrawPlayerHeader();
|
data = data.Copy();
|
||||||
if (!ImGui.BeginChild("##playerData", -Vector2.One, true))
|
|
||||||
{
|
|
||||||
ImGui.EndChild();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_player == null || _player.ModelType() == 0)
|
|
||||||
DrawPlayerPanel();
|
|
||||||
else
|
|
||||||
DrawMonsterPanel();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 void DrawActorPanel()
|
||||||
|
{
|
||||||
|
using var group = ImRaii.Group();
|
||||||
|
DrawPlayerHeader();
|
||||||
|
using var child = ImRaii.Child("##playerData", -Vector2.One, true);
|
||||||
|
if (!child)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_player == null || _player.ModelType() == 0)
|
||||||
|
DrawPlayerPanel();
|
||||||
|
else
|
||||||
|
DrawMonsterPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
using Penumbra.PlayerWatch;
|
using Penumbra.PlayerWatch;
|
||||||
|
|
||||||
namespace Glamourer.Gui;
|
namespace Glamourer.Gui;
|
||||||
|
|
@ -27,9 +29,8 @@ internal partial class Interface
|
||||||
|
|
||||||
private void DrawPlayerFilter()
|
private void DrawPlayerFilter()
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii()
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
|
||||||
ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
|
ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
|
||||||
if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref _playerFilter, 32))
|
if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref _playerFilter, 32))
|
||||||
_playerFilterLower = _playerFilter.ToLowerInvariant();
|
_playerFilterLower = _playerFilter.ToLowerInvariant();
|
||||||
|
|
@ -115,23 +116,22 @@ internal partial class Interface
|
||||||
|
|
||||||
private void DrawSelectionButtons()
|
private void DrawSelectionButtons()
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii()
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0)
|
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||||
.PushFont(UiBuilder.IconFont);
|
|
||||||
Character? select = null;
|
Character? select = null;
|
||||||
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
|
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
|
||||||
if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth))
|
if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth))
|
||||||
select = Dalamud.ClientState.LocalPlayer;
|
select = Dalamud.ClientState.LocalPlayer;
|
||||||
raii.PopFonts();
|
font.Pop();
|
||||||
ImGuiCustom.HoverTooltip("Select the local player character.");
|
ImGuiUtil.HoverTooltip("Select the local player character.");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
raii.PushFont(UiBuilder.IconFont);
|
font.Push(UiBuilder.IconFont);
|
||||||
if (_inGPose)
|
if (_inGPose)
|
||||||
{
|
{
|
||||||
raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
style.Push(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth);
|
ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth);
|
||||||
raii.PopStyles();
|
style.Pop();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -139,8 +139,8 @@ internal partial class Interface
|
||||||
select = CharacterFactory.Convert(Dalamud.Targets.Target);
|
select = CharacterFactory.Convert(Dalamud.Targets.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
raii.PopFonts();
|
font.Pop();
|
||||||
ImGuiCustom.HoverTooltip("Select the current target, if it is in the list.");
|
ImGuiUtil.HoverTooltip("Select the current target, if it is in the list.");
|
||||||
|
|
||||||
if (select == null)
|
if (select == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -196,7 +196,7 @@ internal partial class Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
using (var _ = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||||
{
|
{
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
}
|
}
|
||||||
|
|
@ -207,14 +207,14 @@ internal partial class Interface
|
||||||
|
|
||||||
private void DrawPlayerTab()
|
private void DrawPlayerTab()
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii();
|
using var tab = ImRaii.TabItem("Current Players");
|
||||||
_player = null;
|
_player = null;
|
||||||
if (!raii.Begin(() => ImGui.BeginTabItem("Current Players"), ImGui.EndTabItem))
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DrawPlayerSelector();
|
DrawPlayerSelector();
|
||||||
|
|
||||||
if (!_currentLabel.Any())
|
if (_currentLabel.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui
|
||||||
{
|
{
|
||||||
|
|
@ -11,7 +13,7 @@ namespace Glamourer.Gui
|
||||||
if (DrawCheckMark(label, value, setter))
|
if (DrawCheckMark(label, value, setter))
|
||||||
Glamourer.Config.Save();
|
Glamourer.Config.Save();
|
||||||
|
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
|
private static void ChangeAndSave<T>(T value, T currentValue, Action<T> setter) where T : IEquatable<T>
|
||||||
|
|
@ -33,19 +35,19 @@ namespace Glamourer.Gui
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImGui.Button($"Default##{name}"))
|
if (ImGui.Button($"Default##{name}"))
|
||||||
ChangeAndSave(defaultValue, value, setter);
|
ChangeAndSave(defaultValue, value, setter);
|
||||||
ImGuiCustom.HoverTooltip(
|
ImGuiUtil.HoverTooltip(
|
||||||
$"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}");
|
$"Reset to default: #{defaultValue & 0xFF:X2}{(defaultValue >> 8) & 0xFF:X2}{(defaultValue >> 16) & 0xFF:X2}{defaultValue >> 24:X2}");
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(name);
|
ImGui.Text(name);
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRestorePenumbraButton()
|
private static void DrawRestorePenumbraButton()
|
||||||
{
|
{
|
||||||
const string buttonLabel = "Re-Register Penumbra";
|
const string buttonLabel = "Re-Register Penumbra";
|
||||||
if (!Glamourer.Config.AttachToPenumbra)
|
if (!Glamourer.Config.AttachToPenumbra)
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
ImGui.Button(buttonLabel);
|
ImGui.Button(buttonLabel);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -53,14 +55,14 @@ namespace Glamourer.Gui
|
||||||
if (ImGui.Button(buttonLabel))
|
if (ImGui.Button(buttonLabel))
|
||||||
Glamourer.Penumbra.Reattach(true);
|
Glamourer.Penumbra.Reattach(true);
|
||||||
|
|
||||||
ImGuiCustom.HoverTooltip(
|
ImGuiUtil.HoverTooltip(
|
||||||
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
"If Penumbra did not register the functions for some reason, pressing this button might help restore functionality.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawConfigTab()
|
private static void DrawConfigTab()
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii();
|
using var tab = ImRaii.TabItem("Config");
|
||||||
if (!raii.Begin(() => ImGui.BeginTabItem("Config"), ImGui.EndTabItem))
|
if (!tab)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var cfg = Glamourer.Config;
|
var cfg = Glamourer.Config;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ using Dalamud.Interface;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui
|
||||||
|
|
@ -14,13 +16,14 @@ namespace Glamourer.Gui
|
||||||
private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
private static bool DrawColorPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize))
|
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||||
|
if (!popup)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
var custom = set.Data(id, i);
|
var custom = set.Data(id, i);
|
||||||
|
|
@ -35,7 +38,6 @@ namespace Glamourer.Gui
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +60,7 @@ namespace Glamourer.Gui
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiCustom.HoverTooltip($"Input Range: [{minValue}, {maxValue}]");
|
ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]");
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +94,7 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
||||||
using (var _ = ImGuiRaii.NewGroup())
|
using (var _ = ImRaii.Group())
|
||||||
{
|
{
|
||||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||||
{
|
{
|
||||||
|
|
@ -102,7 +104,7 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
|
|
||||||
ImGui.Text(label);
|
ImGui.Text(label);
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DrawColorPickerPopup(popupName, set, id, out var newCustom))
|
if (!DrawColorPickerPopup(popupName, set, id, out var newCustom))
|
||||||
|
|
@ -117,7 +119,7 @@ namespace Glamourer.Gui
|
||||||
private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||||
CustomizationSet set)
|
CustomizationSet set)
|
||||||
{
|
{
|
||||||
using var bigGroup = ImGuiRaii.NewGroup();
|
using var bigGroup = ImRaii.Group();
|
||||||
var ret = false;
|
var ret = false;
|
||||||
int current = customization[id];
|
int current = customization[id];
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
|
|
@ -146,7 +148,7 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(label);
|
ImGui.Text(label);
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -157,10 +159,10 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set)
|
private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set)
|
||||||
{
|
{
|
||||||
using var bigGroup = ImGuiRaii.NewGroup();
|
using var bigGroup = ImRaii.Group();
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
|
var count = set.Count(CustomizationId.FacialFeaturesTattoos);
|
||||||
using (var _ = ImGuiRaii.NewGroup())
|
using (var _ = ImRaii.Group())
|
||||||
{
|
{
|
||||||
var face = customization.Face;
|
var face = customization.Face;
|
||||||
if (set.Faces.Count < face)
|
if (set.Faces.Count < face)
|
||||||
|
|
@ -180,11 +182,7 @@ namespace Glamourer.Gui
|
||||||
customization.FacialFeature(i, !enabled);
|
customization.FacialFeature(i, !enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
{
|
|
||||||
using var tt = ImGuiRaii.NewTooltip();
|
|
||||||
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i % 4 != 3)
|
if (i % 4 != 3)
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
|
|
@ -192,7 +190,7 @@ namespace Glamourer.Gui
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using var group = ImGuiRaii.NewGroup();
|
using var group = ImRaii.Group();
|
||||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() + ImGui.GetTextLineHeightWithSpacing() + 3 * ImGui.GetStyle().ItemSpacing.Y / 2);
|
||||||
int value = customization[CustomizationId.FacialFeaturesTattoos];
|
int value = customization[CustomizationId.FacialFeaturesTattoos];
|
||||||
if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256))
|
if (InputInt($"##{CustomizationId.FacialFeaturesTattoos}", ref value, 1, 256))
|
||||||
|
|
@ -201,7 +199,7 @@ namespace Glamourer.Gui
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text(set.Option(CustomizationId.FacialFeaturesTattoos));
|
ImGui.TextUnformatted(set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -210,13 +208,14 @@ namespace Glamourer.Gui
|
||||||
private bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
private bool DrawIconPickerPopup(string label, CustomizationSet set, CustomizationId id, out Customization.Customization value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
if (!ImGui.BeginPopup(label, ImGuiWindowFlags.AlwaysAutoResize))
|
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||||
|
if (!popup)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
var custom = set.Data(id, i);
|
var custom = set.Data(id, i);
|
||||||
|
|
@ -229,11 +228,7 @@ namespace Glamourer.Gui
|
||||||
ImGui.CloseCurrentPopup();
|
ImGui.CloseCurrentPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
{
|
|
||||||
using var tt = ImGuiRaii.NewTooltip();
|
|
||||||
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
|
|
||||||
}
|
|
||||||
|
|
||||||
var text = custom.Value.ToString();
|
var text = custom.Value.ToString();
|
||||||
var textWidth = ImGui.CalcTextSize(text).X;
|
var textWidth = ImGui.CalcTextSize(text).X;
|
||||||
|
|
@ -244,14 +239,13 @@ namespace Glamourer.Gui
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||||
CustomizationSet set)
|
CustomizationSet set)
|
||||||
{
|
{
|
||||||
using var bigGroup = ImGuiRaii.NewGroup();
|
using var bigGroup = ImRaii.Group();
|
||||||
var ret = false;
|
var ret = false;
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
|
|
||||||
|
|
@ -268,14 +262,10 @@ namespace Glamourer.Gui
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||||
ImGui.OpenPopup(popupName);
|
ImGui.OpenPopup(popupName);
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
{
|
|
||||||
using var tt = ImGuiRaii.NewTooltip();
|
|
||||||
ImGui.Image(icon.ImGuiHandle, new Vector2(icon.Width, icon.Height));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using var group = ImGuiRaii.NewGroup();
|
using var group = ImRaii.Group();
|
||||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||||
{
|
{
|
||||||
customization[id] = set.Data(id, current).Value;
|
customization[id] = set.Data(id, current).Value;
|
||||||
|
|
@ -288,8 +278,8 @@ namespace Glamourer.Gui
|
||||||
ret = true;
|
ret = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text($"{label} ({custom.Value.Value})");
|
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +288,7 @@ namespace Glamourer.Gui
|
||||||
private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||||
CustomizationSet set)
|
CustomizationSet set)
|
||||||
{
|
{
|
||||||
using var bigGroup = ImGuiRaii.NewGroup();
|
using var bigGroup = ImRaii.Group();
|
||||||
var ret = false;
|
var ret = false;
|
||||||
int value = customization[id];
|
int value = customization[id];
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
|
|
@ -318,15 +308,15 @@ namespace Glamourer.Gui
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.Text(label);
|
ImGui.TextUnformatted(label);
|
||||||
ImGuiCustom.HoverTooltip(tooltip);
|
ImGuiUtil.HoverTooltip(tooltip);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DrawRaceSelector(ref CharacterCustomization customization)
|
private bool DrawRaceSelector(ref CharacterCustomization customization)
|
||||||
{
|
{
|
||||||
using var group = ImGuiRaii.NewGroup();
|
using var group = ImRaii.Group();
|
||||||
var ret = false;
|
var ret = false;
|
||||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||||
if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender)))
|
if (ImGui.BeginCombo("##subRaceCombo", ClanName(customization.Clan, customization.Gender)))
|
||||||
|
|
@ -343,7 +333,7 @@ namespace Glamourer.Gui
|
||||||
ImGui.EndCombo();
|
ImGui.EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.Text(
|
ImGui.TextUnformatted(
|
||||||
$"{Glamourer.Customization.GetName(CustomName.Gender)} & {Glamourer.Customization.GetName(CustomName.Clan)}");
|
$"{Glamourer.Customization.GetName(CustomName.Gender)} & {Glamourer.Customization.GetName(CustomName.Clan)}");
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -352,7 +342,7 @@ namespace Glamourer.Gui
|
||||||
private bool DrawGenderSelector(ref CharacterCustomization customization)
|
private bool DrawGenderSelector(ref CharacterCustomization customization)
|
||||||
{
|
{
|
||||||
var ret = false;
|
var ret = false;
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||||
var icon = customization.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
var icon = customization.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||||
var restricted = false;
|
var restricted = false;
|
||||||
if (customization.Race == Race.Hrothgar)
|
if (customization.Race == Race.Hrothgar)
|
||||||
|
|
@ -370,7 +360,6 @@ namespace Glamourer.Gui
|
||||||
|
|
||||||
if (restricted)
|
if (restricted)
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
ImGui.PopFont();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,369 +6,369 @@ using Dalamud.Logging;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.FileSystem;
|
using Glamourer.FileSystem;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
{
|
{
|
||||||
internal partial class Interface
|
private int _totalObject;
|
||||||
|
|
||||||
|
private bool _inDesignMode;
|
||||||
|
private Design? _selection;
|
||||||
|
private string _newChildName = string.Empty;
|
||||||
|
|
||||||
|
private void DrawDesignSelector()
|
||||||
{
|
{
|
||||||
private int _totalObject;
|
_totalObject = 0;
|
||||||
|
ImGui.BeginGroup();
|
||||||
private bool _inDesignMode;
|
if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||||
private Design? _selection;
|
|
||||||
private string _newChildName = string.Empty;
|
|
||||||
|
|
||||||
private void DrawDesignSelector()
|
|
||||||
{
|
{
|
||||||
_totalObject = 0;
|
DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical);
|
||||||
ImGui.BeginGroup();
|
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||||
if (ImGui.BeginChild("##selector", new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
ImGui.EndChild();
|
||||||
{
|
ImGui.PopStyleVar();
|
||||||
DrawFolderContent(_designs.FileSystem.Root, Glamourer.Config.FoldersFirst ? SortMode.FoldersFirst : SortMode.Lexicographical);
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
|
||||||
ImGui.EndChild();
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawDesignSelectorButtons();
|
|
||||||
ImGui.EndGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPasteClipboardButton()
|
DrawDesignSelectorButtons();
|
||||||
{
|
ImGui.EndGroup();
|
||||||
if (_selection!.Data.WriteProtected)
|
}
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
|
||||||
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
private void DrawPasteClipboardButton()
|
||||||
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
|
{
|
||||||
ImGui.PopFont();
|
if (_selection!.Data.WriteProtected)
|
||||||
if (_selection!.Data.WriteProtected)
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
|
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
var applyButton = ImGui.Button(FontAwesomeIcon.Paste.ToIconString());
|
||||||
|
ImGui.PopFont();
|
||||||
|
if (_selection!.Data.WriteProtected)
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
|
ImGuiUtil.HoverTooltip("Overwrite with customization code from clipboard.");
|
||||||
|
|
||||||
|
if (_selection!.Data.WriteProtected || !applyButton)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var text = ImGui.GetClipboardText();
|
||||||
|
if (!text.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_selection!.Data = CharacterSave.FromString(text);
|
||||||
|
_designs.SaveToFile();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Information($"{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNewFolderButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||||
|
OpenDesignNamePopup(DesignNameUse.NewFolder);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiUtil.HoverTooltip("Create a new, empty Folder.");
|
||||||
|
|
||||||
|
DrawDesignNamePopup(DesignNameUse.NewFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawNewDesignButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||||
|
OpenDesignNamePopup(DesignNameUse.NewDesign);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiUtil.HoverTooltip("Create a new, empty Design.");
|
||||||
|
|
||||||
|
DrawDesignNamePopup(DesignNameUse.NewDesign);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawClipboardDesignButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
||||||
|
OpenDesignNamePopup(DesignNameUse.FromClipboard);
|
||||||
|
ImGui.PopFont();
|
||||||
|
ImGuiUtil.HoverTooltip("Create a new design from the customization string in your clipboard.");
|
||||||
|
|
||||||
|
DrawDesignNamePopup(DesignNameUse.FromClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDeleteDesignButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
var style = _selection == null;
|
||||||
|
if (style)
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||||
|
{
|
||||||
|
_designs.DeleteAllChildren(_selection, false);
|
||||||
|
_selection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopFont();
|
||||||
|
if (style)
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
ImGuiUtil.HoverTooltip("Delete the currently selected Design.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDuplicateDesignButton()
|
||||||
|
{
|
||||||
|
ImGui.PushFont(UiBuilder.IconFont);
|
||||||
|
if (_selection == null)
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
|
if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
||||||
|
OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||||
|
ImGui.PopFont();
|
||||||
|
if (_selection == null)
|
||||||
|
ImGui.PopStyleVar();
|
||||||
|
ImGuiUtil.HoverTooltip(
|
||||||
|
"Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment.");
|
||||||
|
|
||||||
|
DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDesignSelectorButtons()
|
||||||
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
|
.Push(ImGuiStyleVar.FrameRounding, 0f);
|
||||||
|
|
||||||
|
DrawNewFolderButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawNewDesignButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawClipboardDesignButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawDuplicateDesignButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawDeleteDesignButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDesignHeaderButtons()
|
||||||
|
{
|
||||||
|
DrawCopyClipboardButton(_selection!.Data);
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawPasteClipboardButton();
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawApplyToPlayerButton(_selection!.Data);
|
||||||
|
if (!_inGPose)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawApplyToTargetButton(_selection!.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDesignPanel()
|
||||||
|
{
|
||||||
|
if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
|
||||||
|
{
|
||||||
|
DrawDesignHeaderButtons();
|
||||||
|
var data = _selection!.Data;
|
||||||
|
var prot = _selection!.Data.WriteProtected;
|
||||||
|
if (prot)
|
||||||
|
{
|
||||||
|
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
||||||
|
data = data.Copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawGeneralSettings(data, prot);
|
||||||
|
var mask = data.WriteEquipment;
|
||||||
|
if (DrawEquip(data.Equipment, ref mask) && !prot)
|
||||||
|
{
|
||||||
|
data.WriteEquipment = mask;
|
||||||
|
_designs.SaveToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DrawCustomization(ref data.Customizations) && !prot)
|
||||||
|
_designs.SaveToFile();
|
||||||
|
|
||||||
|
if (DrawMiscellaneous(data, null) && !prot)
|
||||||
|
_designs.SaveToFile();
|
||||||
|
|
||||||
|
if (prot)
|
||||||
ImGui.PopStyleVar();
|
ImGui.PopStyleVar();
|
||||||
|
|
||||||
ImGuiCustom.HoverTooltip("Overwrite with customization code from clipboard.");
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_selection!.Data.WriteProtected || !applyButton)
|
private void DrawSaves()
|
||||||
return;
|
{
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
|
||||||
|
using var tab = ImRaii.TabItem("Designs");
|
||||||
|
_inDesignMode = tab.Success;
|
||||||
|
if (!_inDesignMode)
|
||||||
|
return;
|
||||||
|
|
||||||
var text = ImGui.GetClipboardText();
|
DrawDesignSelector();
|
||||||
if (!text.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
if (_selection != null)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawDesignPanel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
|
||||||
|
{
|
||||||
|
var tmp = value;
|
||||||
|
if (ImGui.Checkbox(label, ref tmp) && tmp != value)
|
||||||
|
{
|
||||||
|
setter(tmp);
|
||||||
|
if (!prot)
|
||||||
|
_designs.SaveToFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGeneralSettings(CharacterSave data, bool prot)
|
||||||
|
{
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
|
||||||
|
DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginGroup();
|
||||||
|
DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
|
||||||
|
DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
|
||||||
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenameChildInput(IFileSystemBase child)
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(150);
|
||||||
|
if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
|
||||||
|
ImGuiInputTextFlags.EnterReturnsTrue))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_newChildName.Any() && _newChildName != child.Name)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_selection!.Data = CharacterSave.FromString(text);
|
var oldPath = child.FullName();
|
||||||
_designs.SaveToFile();
|
if (_designs.FileSystem.Rename(child, _newChildName))
|
||||||
|
_designs.UpdateAllChildren(oldPath, child);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
PluginLog.Information($"{e}");
|
PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
else if (child is Folder f)
|
||||||
|
try
|
||||||
private void DrawNewFolderButton()
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.FolderPlus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
|
||||||
OpenDesignNamePopup(DesignNameUse.NewFolder);
|
|
||||||
ImGui.PopFont();
|
|
||||||
ImGuiCustom.HoverTooltip("Create a new, empty Folder.");
|
|
||||||
|
|
||||||
DrawDesignNamePopup(DesignNameUse.NewFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawNewDesignButton()
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
|
||||||
OpenDesignNamePopup(DesignNameUse.NewDesign);
|
|
||||||
ImGui.PopFont();
|
|
||||||
ImGuiCustom.HoverTooltip("Create a new, empty Design.");
|
|
||||||
|
|
||||||
DrawDesignNamePopup(DesignNameUse.NewDesign);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawClipboardDesignButton()
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Paste.ToIconString(), Vector2.UnitX * SelectorWidth / 5))
|
|
||||||
OpenDesignNamePopup(DesignNameUse.FromClipboard);
|
|
||||||
ImGui.PopFont();
|
|
||||||
ImGuiCustom.HoverTooltip("Create a new design from the customization string in your clipboard.");
|
|
||||||
|
|
||||||
DrawDesignNamePopup(DesignNameUse.FromClipboard);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDeleteDesignButton()
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
var style = _selection == null;
|
|
||||||
if (style)
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
|
||||||
{
|
{
|
||||||
_designs.DeleteAllChildren(_selection, false);
|
var oldPath = child.FullName();
|
||||||
_selection = null;
|
if (_designs.FileSystem.Merge(f, f.Parent, true))
|
||||||
|
_designs.UpdateAllChildren(oldPath, f.Parent);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
ImGui.PopFont();
|
|
||||||
if (style)
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
ImGuiCustom.HoverTooltip("Delete the currently selected Design.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDuplicateDesignButton()
|
|
||||||
{
|
|
||||||
ImGui.PushFont(UiBuilder.IconFont);
|
|
||||||
if (_selection == null)
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f);
|
|
||||||
if (ImGui.Button(FontAwesomeIcon.Clone.ToIconString(), Vector2.UnitX * SelectorWidth / 5) && _selection != null)
|
|
||||||
OpenDesignNamePopup(DesignNameUse.DuplicateDesign);
|
|
||||||
ImGui.PopFont();
|
|
||||||
if (_selection == null)
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
ImGuiCustom.HoverTooltip("Clone the currently selected Design.\nHold Shift to only clone the customizations.\nHold Control to only clone the equipment.");
|
|
||||||
|
|
||||||
DrawDesignNamePopup(DesignNameUse.DuplicateDesign);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDesignSelectorButtons()
|
|
||||||
{
|
|
||||||
using var raii = new ImGuiRaii()
|
|
||||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
|
|
||||||
|
|
||||||
DrawNewFolderButton();
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawNewDesignButton();
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawClipboardDesignButton();
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawDuplicateDesignButton();
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawDeleteDesignButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawDesignHeaderButtons()
|
|
||||||
{
|
|
||||||
DrawCopyClipboardButton(_selection!.Data);
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawPasteClipboardButton();
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawApplyToPlayerButton(_selection!.Data);
|
|
||||||
if (!_inGPose)
|
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
|
||||||
DrawApplyToTargetButton(_selection!.Data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.SameLine();
|
_newChildName = string.Empty;
|
||||||
DrawCheckbox("Write Protected", _selection!.Data.WriteProtected, v => _selection!.Data.WriteProtected = v, false);
|
}
|
||||||
|
|
||||||
|
private void ContextMenu(IFileSystemBase child)
|
||||||
|
{
|
||||||
|
var label = $"##fsPopup{child.FullName()}";
|
||||||
|
if (ImGui.BeginPopup(label))
|
||||||
|
{
|
||||||
|
if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
|
||||||
|
_designs.DeleteAllChildren(child, false);
|
||||||
|
ImGuiUtil.HoverTooltip("Hold Control and Shift to delete.");
|
||||||
|
|
||||||
|
RenameChildInput(child);
|
||||||
|
|
||||||
|
if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
|
||||||
|
ImGui.SetClipboardText(d.Data.ToBase64());
|
||||||
|
|
||||||
|
ImGui.EndPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDesignPanel()
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
{
|
{
|
||||||
if (ImGui.BeginChild("##details", -Vector2.One * 0.001f, true))
|
_newChildName = child.Name;
|
||||||
{
|
ImGui.OpenPopup(label);
|
||||||
DrawDesignHeaderButtons();
|
|
||||||
var data = _selection!.Data;
|
|
||||||
var prot = _selection!.Data.WriteProtected;
|
|
||||||
if (prot)
|
|
||||||
{
|
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.8f);
|
|
||||||
data = data.Copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawGeneralSettings(data, prot);
|
|
||||||
var mask = data.WriteEquipment;
|
|
||||||
if (DrawEquip(data.Equipment, ref mask) && !prot)
|
|
||||||
{
|
|
||||||
data.WriteEquipment = mask;
|
|
||||||
_designs.SaveToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DrawCustomization(ref data.Customizations) && !prot)
|
|
||||||
_designs.SaveToFile();
|
|
||||||
|
|
||||||
if (DrawMiscellaneous(data, null) && !prot)
|
|
||||||
_designs.SaveToFile();
|
|
||||||
|
|
||||||
if (prot)
|
|
||||||
ImGui.PopStyleVar();
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawSaves()
|
|
||||||
{
|
|
||||||
using var raii = new ImGuiRaii();
|
|
||||||
raii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
|
|
||||||
_inDesignMode = raii.Begin(() => ImGui.BeginTabItem("Designs"), ImGui.EndTabItem);
|
|
||||||
if (!_inDesignMode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
DrawDesignSelector();
|
|
||||||
|
|
||||||
if (_selection != null)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawDesignPanel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawCheckbox(string label, bool value, Action<bool> setter, bool prot)
|
|
||||||
{
|
|
||||||
var tmp = value;
|
|
||||||
if (ImGui.Checkbox(label, ref tmp) && tmp != value)
|
|
||||||
{
|
|
||||||
setter(tmp);
|
|
||||||
if (!prot)
|
|
||||||
_designs.SaveToFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawGeneralSettings(CharacterSave data, bool prot)
|
|
||||||
{
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
DrawCheckbox("Apply Customizations", data.WriteCustomizations, v => data.WriteCustomizations = v, prot);
|
|
||||||
DrawCheckbox("Write Weapon State", data.SetWeaponState, v => data.SetWeaponState = v, prot);
|
|
||||||
ImGui.EndGroup();
|
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
DrawCheckbox("Write Hat State", data.SetHatState, v => data.SetHatState = v, prot);
|
|
||||||
DrawCheckbox("Write Visor State", data.SetVisorState, v => data.SetVisorState = v, prot);
|
|
||||||
ImGui.EndGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenameChildInput(IFileSystemBase child)
|
|
||||||
{
|
|
||||||
ImGui.SetNextItemWidth(150);
|
|
||||||
if (!ImGui.InputTextWithHint("##fsNewName", "Rename...", ref _newChildName, 64,
|
|
||||||
ImGuiInputTextFlags.EnterReturnsTrue))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_newChildName.Any() && _newChildName != child.Name)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var oldPath = child.FullName();
|
|
||||||
if (_designs.FileSystem.Rename(child, _newChildName))
|
|
||||||
_designs.UpdateAllChildren(oldPath, child);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Error($"Could not rename {child.Name} to {_newChildName}:\n{e}");
|
|
||||||
}
|
|
||||||
else if (child is Folder f)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var oldPath = child.FullName();
|
|
||||||
if (_designs.FileSystem.Merge(f, f.Parent, true))
|
|
||||||
_designs.UpdateAllChildren(oldPath, f.Parent);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Error($"Could not merge folder {child.Name} into parent:\n{e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_newChildName = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ContextMenu(IFileSystemBase child)
|
|
||||||
{
|
|
||||||
var label = $"##fsPopup{child.FullName()}";
|
|
||||||
if (ImGui.BeginPopup(label))
|
|
||||||
{
|
|
||||||
if (ImGui.MenuItem("Delete") && ImGui.GetIO().KeyCtrl && ImGui.GetIO().KeyShift)
|
|
||||||
_designs.DeleteAllChildren(child, false);
|
|
||||||
ImGuiCustom.HoverTooltip("Hold Control and Shift to delete.");
|
|
||||||
|
|
||||||
RenameChildInput(child);
|
|
||||||
|
|
||||||
if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
|
|
||||||
ImGui.SetClipboardText(d.Data.ToBase64());
|
|
||||||
|
|
||||||
ImGui.EndPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
|
||||||
{
|
|
||||||
_newChildName = child.Name;
|
|
||||||
ImGui.OpenPopup(label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static uint GetDesignColor(CharacterSave save)
|
|
||||||
{
|
|
||||||
const uint white = 0xFFFFFFFF;
|
|
||||||
const uint grey = 0xFF808080;
|
|
||||||
if (!Glamourer.Config.ColorDesigns)
|
|
||||||
return white;
|
|
||||||
|
|
||||||
var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
|
|
||||||
if (save.WriteCustomizations)
|
|
||||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
|
||||||
return white;
|
|
||||||
else
|
|
||||||
return changesStates ? white : Glamourer.Config.CustomizationColor;
|
|
||||||
|
|
||||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
|
||||||
return changesStates ? white : Glamourer.Config.EquipmentColor;
|
|
||||||
|
|
||||||
return changesStates ? Glamourer.Config.StateColor : grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawFolderContent(Folder folder, SortMode mode)
|
|
||||||
{
|
|
||||||
foreach (var child in folder.AllChildren(mode).ToArray())
|
|
||||||
{
|
|
||||||
if (child.IsFolder(out var subFolder))
|
|
||||||
{
|
|
||||||
var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
|
|
||||||
DrawOrnaments(child);
|
|
||||||
|
|
||||||
if (treeNode)
|
|
||||||
{
|
|
||||||
DrawFolderContent(subFolder, mode);
|
|
||||||
ImGui.TreePop();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_totalObject += subFolder.TotalDescendantLeaves();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (child is not Design d)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
++_totalObject;
|
|
||||||
var color = GetDesignColor(d.Data);
|
|
||||||
using var raii = new ImGuiRaii()
|
|
||||||
.PushColor(ImGuiCol.Text, color);
|
|
||||||
|
|
||||||
var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
|
|
||||||
raii.PopColors();
|
|
||||||
DrawOrnaments(child);
|
|
||||||
|
|
||||||
if (Glamourer.Config.ShowLocks && d.Data.WriteProtected)
|
|
||||||
{
|
|
||||||
ImGui.SameLine();
|
|
||||||
raii.PushFont(UiBuilder.IconFont)
|
|
||||||
.PushColor(ImGuiCol.Text, color);
|
|
||||||
ImGui.Text(FontAwesomeIcon.Lock.ToIconString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected)
|
|
||||||
_selection = d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawOrnaments(IFileSystemBase child)
|
|
||||||
{
|
|
||||||
FileSystemImGui.DragDropSource(child);
|
|
||||||
if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
|
|
||||||
_designs.UpdateAllChildren(oldPath, draggedFolder!);
|
|
||||||
ContextMenu(child);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uint GetDesignColor(CharacterSave save)
|
||||||
|
{
|
||||||
|
const uint white = 0xFFFFFFFF;
|
||||||
|
const uint grey = 0xFF808080;
|
||||||
|
if (!Glamourer.Config.ColorDesigns)
|
||||||
|
return white;
|
||||||
|
|
||||||
|
var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
|
||||||
|
if (save.WriteCustomizations)
|
||||||
|
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||||
|
return white;
|
||||||
|
else
|
||||||
|
return changesStates ? white : Glamourer.Config.CustomizationColor;
|
||||||
|
|
||||||
|
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||||
|
return changesStates ? white : Glamourer.Config.EquipmentColor;
|
||||||
|
|
||||||
|
return changesStates ? Glamourer.Config.StateColor : grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFolderContent(Folder folder, SortMode mode)
|
||||||
|
{
|
||||||
|
foreach (var child in folder.AllChildren(mode).ToArray())
|
||||||
|
{
|
||||||
|
if (child.IsFolder(out var subFolder))
|
||||||
|
{
|
||||||
|
var treeNode = ImGui.TreeNodeEx($"{subFolder.Name}##{_totalObject}");
|
||||||
|
DrawOrnaments(child);
|
||||||
|
|
||||||
|
if (treeNode)
|
||||||
|
{
|
||||||
|
DrawFolderContent(subFolder, mode);
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_totalObject += subFolder.TotalDescendantLeaves();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (child is not Design d)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
++_totalObject;
|
||||||
|
var color = GetDesignColor(d.Data);
|
||||||
|
using var c = ImRaii.PushColor(ImGuiCol.Text, color);
|
||||||
|
|
||||||
|
var selected = ImGui.Selectable($"{child.Name}##{_totalObject}", ReferenceEquals(child, _selection));
|
||||||
|
c.Pop();
|
||||||
|
DrawOrnaments(child);
|
||||||
|
|
||||||
|
if (Glamourer.Config.ShowLocks && d.Data.WriteProtected)
|
||||||
|
{
|
||||||
|
ImGui.SameLine();
|
||||||
|
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||||
|
c.Push(ImGuiCol.Text, color);
|
||||||
|
ImGui.TextUnformatted(FontAwesomeIcon.Lock.ToIconString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected)
|
||||||
|
_selection = d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawOrnaments(IFileSystemBase child)
|
||||||
|
{
|
||||||
|
FileSystemImGui.DragDropSource(child);
|
||||||
|
if (FileSystemImGui.DragDropTarget(_designs.FileSystem, child, out var oldPath, out var draggedFolder))
|
||||||
|
_designs.UpdateAllChildren(oldPath, draggedFolder!);
|
||||||
|
ContextMenu(child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Text;
|
using Lumina.Text;
|
||||||
|
using OtterGui;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ namespace Glamourer.Gui
|
||||||
var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
|
var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
|
||||||
if (!change && (byte) stainIdx != 0)
|
if (!change && (byte) stainIdx != 0)
|
||||||
{
|
{
|
||||||
ImGuiCustom.HoverTooltip("Right-click to clear.");
|
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
{
|
{
|
||||||
change = true;
|
change = true;
|
||||||
|
|
@ -46,7 +47,7 @@ namespace Glamourer.Gui
|
||||||
var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
|
var change = equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item.RowId;
|
||||||
if (!change && !ReferenceEquals(item, SmallClothes))
|
if (!change && !ReferenceEquals(item, SmallClothes))
|
||||||
{
|
{
|
||||||
ImGuiCustom.HoverTooltip("Right-click to clear.");
|
ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
|
||||||
{
|
{
|
||||||
change = true;
|
change = true;
|
||||||
|
|
|
||||||
|
|
@ -6,161 +6,163 @@ using Dalamud.Interface;
|
||||||
using Glamourer.Designs;
|
using Glamourer.Designs;
|
||||||
using Glamourer.FileSystem;
|
using Glamourer.FileSystem;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
{
|
{
|
||||||
internal partial class Interface
|
private const string FixDragDropLabel = "##FixDragDrop";
|
||||||
|
|
||||||
|
private List<string>? _fullPathCache;
|
||||||
|
private string _newFixCharacterName = string.Empty;
|
||||||
|
private string _newFixDesignPath = string.Empty;
|
||||||
|
private JobGroup? _newFixDesignGroup;
|
||||||
|
private Design? _newFixDesign;
|
||||||
|
private int _fixDragDropIdx = -1;
|
||||||
|
|
||||||
|
private static unsafe bool IsDropping()
|
||||||
|
=> ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
|
||||||
|
|
||||||
|
private void DrawFixedDesignsTab()
|
||||||
{
|
{
|
||||||
private const string FixDragDropLabel = "##FixDragDrop";
|
_newFixDesignGroup ??= Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||||
|
|
||||||
private List<string>? _fullPathCache;
|
using var tabItem = ImRaii.TabItem("Fixed Designs");
|
||||||
private string _newFixCharacterName = string.Empty;
|
if (!tabItem)
|
||||||
private string _newFixDesignPath = string.Empty;
|
|
||||||
private JobGroup? _newFixDesignGroup;
|
|
||||||
private Design? _newFixDesign;
|
|
||||||
private int _fixDragDropIdx = -1;
|
|
||||||
|
|
||||||
private static unsafe bool IsDropping()
|
|
||||||
=> ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
|
|
||||||
|
|
||||||
private void DrawFixedDesignsTab()
|
|
||||||
{
|
{
|
||||||
_newFixDesignGroup ??= _plugin.FixedDesigns.JobGroups[1];
|
_fullPathCache = null;
|
||||||
|
_newFixDesign = null;
|
||||||
|
_newFixDesignPath = string.Empty;
|
||||||
|
_newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using var raii = new ImGuiRaii();
|
_fullPathCache ??= Glamourer.FixedDesignManager.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
|
||||||
if (!raii.Begin(() => ImGui.BeginTabItem("Fixed Designs"), ImGui.EndTabItem))
|
|
||||||
{
|
|
||||||
_fullPathCache = null;
|
|
||||||
_newFixDesign = null;
|
|
||||||
_newFixDesignPath = string.Empty;
|
|
||||||
_newFixDesignGroup = _plugin.FixedDesigns.JobGroups[1];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_fullPathCache ??= _plugin.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
|
using var table = ImRaii.Table("##FixedTable", 4);
|
||||||
|
var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
|
||||||
|
|
||||||
raii.Begin(() => ImGui.BeginTable("##FixedTable", 4), ImGui.EndTable);
|
ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
|
||||||
|
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch);
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
var xPos = 0f;
|
||||||
|
|
||||||
var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
|
using var style = new ImRaii.Style();
|
||||||
|
using var font = new ImRaii.Font();
|
||||||
|
for (var i = 0; i < _fullPathCache.Count; ++i)
|
||||||
ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
|
{
|
||||||
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
|
var path = _fullPathCache[i];
|
||||||
ImGui.TableSetupColumn("Jobs", ImGuiTableColumnFlags.WidthFixed, 175 * ImGuiHelpers.GlobalScale);
|
var name = Glamourer.FixedDesignManager.FixedDesigns.Data[i];
|
||||||
ImGui.TableSetupColumn("Design", ImGuiTableColumnFlags.WidthStretch);
|
|
||||||
ImGui.TableHeadersRow();
|
|
||||||
var xPos = 0f;
|
|
||||||
for (var i = 0; i < _fullPathCache.Count; ++i)
|
|
||||||
{
|
|
||||||
var path = _fullPathCache[i];
|
|
||||||
var name = _plugin.FixedDesigns.Data[i];
|
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
raii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
|
|
||||||
raii.PushFont(UiBuilder.IconFont);
|
|
||||||
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}"))
|
|
||||||
{
|
|
||||||
_fullPathCache.RemoveAt(i--);
|
|
||||||
_plugin.FixedDesigns.Remove(name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp = name.Enabled;
|
|
||||||
ImGui.SameLine();
|
|
||||||
xPos = ImGui.GetCursorPosX();
|
|
||||||
if (ImGui.Checkbox($"##Enabled{i}", ref tmp))
|
|
||||||
if (tmp && _plugin.FixedDesigns.EnableDesign(name)
|
|
||||||
|| !tmp && _plugin.FixedDesigns.DisableDesign(name))
|
|
||||||
{
|
|
||||||
Glamourer.Config.FixedDesigns[i].Enabled = tmp;
|
|
||||||
Glamourer.Config.Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
raii.PopStyles();
|
|
||||||
raii.PopFonts();
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.Selectable($"{name.Name}##Fix{i}");
|
|
||||||
if (ImGui.BeginDragDropSource())
|
|
||||||
{
|
|
||||||
_fixDragDropIdx = i;
|
|
||||||
ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0);
|
|
||||||
ImGui.Text($"Dragging {name.Name} ({path})...");
|
|
||||||
ImGui.EndDragDropSource();
|
|
||||||
}
|
|
||||||
if (ImGui.BeginDragDropTarget())
|
|
||||||
{
|
|
||||||
if (IsDropping() && _fixDragDropIdx >= 0)
|
|
||||||
{
|
|
||||||
var d = _plugin.FixedDesigns.Data[_fixDragDropIdx];
|
|
||||||
_plugin.FixedDesigns.Move(d, i);
|
|
||||||
var p = _fullPathCache[_fixDragDropIdx];
|
|
||||||
_fullPathCache.RemoveAt(_fixDragDropIdx);
|
|
||||||
_fullPathCache.Insert(i, p);
|
|
||||||
_fixDragDropIdx = -1;
|
|
||||||
}
|
|
||||||
ImGui.EndDragDropTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.Text(_plugin.FixedDesigns.Data[i].Jobs.Name);
|
|
||||||
ImGui.TableNextColumn();
|
|
||||||
ImGui.Text(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
raii.PushFont(UiBuilder.IconFont);
|
style.Push(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing / 2);
|
||||||
|
font.Push(UiBuilder.IconFont);
|
||||||
ImGui.SetCursorPosX(xPos);
|
if (ImGui.Button($"{FontAwesomeIcon.Trash.ToIconChar()}##{i}"))
|
||||||
if (_newFixDesign == null || _newFixCharacterName == string.Empty)
|
|
||||||
{
|
{
|
||||||
raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
_fullPathCache.RemoveAt(i--);
|
||||||
ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
|
Glamourer.FixedDesignManager.FixedDesigns.Remove(name);
|
||||||
raii.PopStyles();
|
continue;
|
||||||
}
|
|
||||||
else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
|
|
||||||
{
|
|
||||||
_fullPathCache.Add(_newFixDesignPath);
|
|
||||||
_plugin.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false);
|
|
||||||
_newFixCharacterName = string.Empty;
|
|
||||||
_newFixDesignPath = string.Empty;
|
|
||||||
_newFixDesign = null;
|
|
||||||
_newFixDesignGroup = _plugin.FixedDesigns.JobGroups[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
raii.PopFonts();
|
var tmp = name.Enabled;
|
||||||
ImGui.TableNextColumn();
|
ImGui.SameLine();
|
||||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
xPos = ImGui.GetCursorPosX();
|
||||||
ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
|
if (ImGui.Checkbox($"##Enabled{i}", ref tmp))
|
||||||
ImGui.TableNextColumn();
|
if (tmp && Glamourer.FixedDesignManager.FixedDesigns.EnableDesign(name)
|
||||||
ImGui.SetNextItemWidth(-1);
|
|| !tmp && Glamourer.FixedDesignManager.FixedDesigns.DisableDesign(name))
|
||||||
if (raii.Begin(() => ImGui.BeginCombo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name), ImGui.EndCombo))
|
|
||||||
{
|
|
||||||
foreach (var (id, group) in _plugin.FixedDesigns.JobGroups)
|
|
||||||
{
|
{
|
||||||
ImGui.SetNextItemWidth(-1);
|
Glamourer.Config.FixedDesigns[i].Enabled = tmp;
|
||||||
if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name))
|
Glamourer.Config.Save();
|
||||||
_newFixDesignGroup = group;
|
|
||||||
}
|
}
|
||||||
raii.End();
|
|
||||||
|
style.Pop();
|
||||||
|
font.Pop();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Selectable($"{name.Name}##Fix{i}");
|
||||||
|
if (ImGui.BeginDragDropSource())
|
||||||
|
{
|
||||||
|
_fixDragDropIdx = i;
|
||||||
|
ImGui.SetDragDropPayload("##FixDragDrop", IntPtr.Zero, 0);
|
||||||
|
ImGui.Text($"Dragging {name.Name} ({path})...");
|
||||||
|
ImGui.EndDragDropSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginDragDropTarget())
|
||||||
|
{
|
||||||
|
if (IsDropping() && _fixDragDropIdx >= 0)
|
||||||
|
{
|
||||||
|
var d = Glamourer.FixedDesignManager.FixedDesigns.Data[_fixDragDropIdx];
|
||||||
|
Glamourer.FixedDesignManager.FixedDesigns.Move(d, i);
|
||||||
|
var p = _fullPathCache[_fixDragDropIdx];
|
||||||
|
_fullPathCache.RemoveAt(_fixDragDropIdx);
|
||||||
|
_fullPathCache.Insert(i, p);
|
||||||
|
_fixDragDropIdx = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDragDropTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.Text(Glamourer.FixedDesignManager.FixedDesigns.Data[i].Jobs.Name);
|
||||||
if (!raii.Begin(() => ImGui.BeginCombo("##NewFixPath", _newFixDesignPath), ImGui.EndCombo))
|
ImGui.TableNextColumn();
|
||||||
return;
|
ImGui.Text(path);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast<Design>())
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
font.Push(UiBuilder.IconFont);
|
||||||
|
|
||||||
|
ImGui.SetCursorPosX(xPos);
|
||||||
|
if (_newFixDesign == null || _newFixCharacterName == string.Empty)
|
||||||
|
{
|
||||||
|
style.Push(ImGuiStyleVar.Alpha, 0.5f);
|
||||||
|
ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
|
||||||
|
style.Pop();
|
||||||
|
}
|
||||||
|
else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
|
||||||
|
{
|
||||||
|
_fullPathCache.Add(_newFixDesignPath);
|
||||||
|
Glamourer.FixedDesignManager.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, _newFixDesignGroup.Value, false);
|
||||||
|
_newFixCharacterName = string.Empty;
|
||||||
|
_newFixDesignPath = string.Empty;
|
||||||
|
_newFixDesign = null;
|
||||||
|
_newFixDesignGroup = Glamourer.FixedDesignManager.FixedDesigns.JobGroups[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
font.Pop();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||||
|
ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
using var combo = ImRaii.Combo("##NewFixDesignGroup", _newFixDesignGroup.Value.Name);
|
||||||
|
if (combo)
|
||||||
|
foreach (var (id, group) in Glamourer.FixedDesignManager.FixedDesigns.JobGroups)
|
||||||
{
|
{
|
||||||
var fullName = design.FullName();
|
|
||||||
ImGui.SetNextItemWidth(-1);
|
ImGui.SetNextItemWidth(-1);
|
||||||
if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath))
|
if (ImGui.Selectable($"{group.Name}##NewFixDesignGroup", group.Name == _newFixDesignGroup.Value.Name))
|
||||||
continue;
|
_newFixDesignGroup = group;
|
||||||
|
|
||||||
_newFixDesignPath = fullName;
|
|
||||||
_newFixDesign = design;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
using var combo2 = ImRaii.Combo("##NewFixPath", _newFixDesignPath);
|
||||||
|
if (!combo2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var design in _plugin.Designs.FileSystem.Root.AllLeaves(SortMode.Lexicographical).Cast<Design>())
|
||||||
|
{
|
||||||
|
var fullName = design.FullName();
|
||||||
|
ImGui.SetNextItemWidth(-1);
|
||||||
|
if (!ImGui.Selectable($"{fullName}##NewFixDesign", fullName == _newFixDesignPath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_newFixDesignPath = fullName;
|
||||||
|
_newFixDesign = design;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Structs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Reflection;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Glamourer.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,74 +2,62 @@
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
{
|
{
|
||||||
internal partial class Interface
|
private static bool DrawCheckMark(string label, bool value, Action<bool> setter)
|
||||||
{
|
{
|
||||||
private static bool DrawCheckMark(string label, bool value, Action<bool> setter)
|
var startValue = value;
|
||||||
|
if (ImGui.Checkbox(label, ref startValue) && startValue != value)
|
||||||
{
|
{
|
||||||
var startValue = value;
|
setter(startValue);
|
||||||
if (ImGui.Checkbox(label, ref startValue) && startValue != value)
|
return true;
|
||||||
{
|
|
||||||
setter(startValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DrawDisableButton(string label, bool disabled)
|
return false;
|
||||||
{
|
}
|
||||||
if (!disabled)
|
|
||||||
return ImGui.Button(label);
|
|
||||||
|
|
||||||
using var raii = new ImGuiRaii();
|
|
||||||
raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
|
||||||
ImGui.Button(label);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool DrawMiscellaneous(CharacterSave save, Character? player)
|
|
||||||
{
|
|
||||||
var ret = false;
|
|
||||||
if (!ImGui.CollapsingHeader("Miscellaneous"))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
|
|
||||||
{
|
|
||||||
save.HatState = v;
|
|
||||||
player?.SetHatVisible(v);
|
|
||||||
});
|
|
||||||
|
|
||||||
ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
|
|
||||||
{
|
|
||||||
save.WeaponState = v;
|
|
||||||
player?.SetWeaponHidden(!v);
|
|
||||||
});
|
|
||||||
|
|
||||||
ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
|
|
||||||
{
|
|
||||||
save.VisorState = v;
|
|
||||||
player?.SetVisorToggled(v);
|
|
||||||
});
|
|
||||||
|
|
||||||
ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
|
|
||||||
{
|
|
||||||
save.IsWet = v;
|
|
||||||
player?.SetWetness(v);
|
|
||||||
});
|
|
||||||
|
|
||||||
var alpha = save.Alpha;
|
|
||||||
if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
|
|
||||||
{
|
|
||||||
alpha = (float) Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
|
|
||||||
save.Alpha = alpha;
|
|
||||||
ret = true;
|
|
||||||
if (player != null)
|
|
||||||
player.Alpha() = alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private static bool DrawMiscellaneous(CharacterSave save, Character? player)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
if (!ImGui.CollapsingHeader("Miscellaneous"))
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
ret |= DrawCheckMark("Hat Visible", save.HatState, v =>
|
||||||
|
{
|
||||||
|
save.HatState = v;
|
||||||
|
player?.SetHatVisible(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
ret |= DrawCheckMark("Weapon Visible", save.WeaponState, v =>
|
||||||
|
{
|
||||||
|
save.WeaponState = v;
|
||||||
|
player?.SetWeaponHidden(!v);
|
||||||
|
});
|
||||||
|
|
||||||
|
ret |= DrawCheckMark("Visor Toggled", save.VisorState, v =>
|
||||||
|
{
|
||||||
|
save.VisorState = v;
|
||||||
|
player?.SetVisorToggled(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
ret |= DrawCheckMark("Is Wet", save.IsWet, v =>
|
||||||
|
{
|
||||||
|
save.IsWet = v;
|
||||||
|
player?.SetWetness(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
var alpha = save.Alpha;
|
||||||
|
if (ImGui.DragFloat("Alpha", ref alpha, 0.01f, 0f, 1f, "%.2f") && alpha != save.Alpha)
|
||||||
|
{
|
||||||
|
alpha = (float)Math.Round(alpha > 1 ? 1 : alpha < 0 ? 0 : alpha, 2);
|
||||||
|
save.Alpha = alpha;
|
||||||
|
ret = true;
|
||||||
|
if (player != null)
|
||||||
|
player.Alpha() = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,87 +2,86 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
namespace Glamourer.Gui
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
{
|
{
|
||||||
internal partial class Interface
|
private string? _currentRevertableName;
|
||||||
|
private CharacterSave? _currentRevertable;
|
||||||
|
|
||||||
|
private void DrawRevertablesSelector()
|
||||||
{
|
{
|
||||||
private string? _currentRevertableName;
|
ImGui.BeginGroup();
|
||||||
private CharacterSave? _currentRevertable;
|
DrawPlayerFilter();
|
||||||
|
if (!ImGui.BeginChild("##playerSelector",
|
||||||
private void DrawRevertablesSelector()
|
|
||||||
{
|
|
||||||
ImGui.BeginGroup();
|
|
||||||
DrawPlayerFilter();
|
|
||||||
if (!ImGui.BeginChild("##playerSelector",
|
|
||||||
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||||
{
|
{
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
ImGui.EndGroup();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (name, save) in Glamourer.RevertableDesigns.Saves)
|
|
||||||
{
|
|
||||||
if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName))
|
|
||||||
{
|
|
||||||
_currentRevertableName = name;
|
|
||||||
_currentRevertable = save;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var _ = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
|
||||||
{
|
|
||||||
ImGui.EndChild();
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawSelectionButtons();
|
|
||||||
ImGui.EndGroup();
|
ImGui.EndGroup();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRevertablePanel()
|
foreach (var (name, save) in Glamourer.RevertableDesigns.Saves)
|
||||||
{
|
{
|
||||||
using var group = ImGuiRaii.NewGroup();
|
if (name.ToLowerInvariant().Contains(_playerFilterLower) && ImGui.Selectable(name, name == _currentRevertableName))
|
||||||
{
|
{
|
||||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
_currentRevertableName = name;
|
||||||
using var raii = new ImGuiRaii()
|
_currentRevertable = save;
|
||||||
.PushColor(ImGuiCol.Text, GreenHeaderColor)
|
|
||||||
.PushColor(ImGuiCol.Button, buttonColor)
|
|
||||||
.PushColor(ImGuiCol.ButtonHovered, buttonColor)
|
|
||||||
.PushColor(ImGuiCol.ButtonActive, buttonColor)
|
|
||||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
|
||||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
|
||||||
ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ImGui.BeginChild("##revertableData", -Vector2.One, true))
|
using (var _ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||||
{
|
{
|
||||||
ImGui.EndChild();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var save = _currentRevertable!.Copy();
|
|
||||||
DrawCustomization(ref save.Customizations);
|
|
||||||
DrawEquip(save.Equipment);
|
|
||||||
DrawMiscellaneous(save, null);
|
|
||||||
|
|
||||||
ImGui.EndChild();
|
ImGui.EndChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
DrawSelectionButtons();
|
||||||
private void DrawRevertablesTab()
|
ImGui.EndGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawRevertablePanel()
|
||||||
|
{
|
||||||
|
using var group = ImRaii.Group();
|
||||||
{
|
{
|
||||||
using var raii = new ImGuiRaii();
|
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||||
if (!raii.Begin(() => ImGui.BeginTabItem("Revertables"), ImGui.EndTabItem))
|
using var color = ImRaii.PushColor(ImGuiCol.Text, GreenHeaderColor)
|
||||||
return;
|
.Push(ImGuiCol.Button, buttonColor)
|
||||||
|
.Push(ImGuiCol.ButtonHovered, buttonColor)
|
||||||
DrawRevertablesSelector();
|
.Push(ImGuiCol.ButtonActive, buttonColor);
|
||||||
|
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||||
if (_currentRevertableName == null)
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
return;
|
ImGui.Button($"{_currentRevertableName}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||||
|
|
||||||
ImGui.SameLine();
|
|
||||||
DrawRevertablePanel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ImGui.BeginChild("##revertableData", -Vector2.One, true))
|
||||||
|
{
|
||||||
|
ImGui.EndChild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var save = _currentRevertable!.Copy();
|
||||||
|
DrawCustomization(ref save.Customizations);
|
||||||
|
DrawEquip(save.Equipment);
|
||||||
|
DrawMiscellaneous(save, null);
|
||||||
|
|
||||||
|
ImGui.EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void DrawRevertablesTab()
|
||||||
|
{
|
||||||
|
using var tabItem = ImRaii.TabItem("Revertables");
|
||||||
|
if (!tabItem)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DrawRevertablesSelector();
|
||||||
|
|
||||||
|
if (_currentRevertableName == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
DrawRevertablePanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue