mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Merge branch 'api4' into main
This commit is contained in:
commit
b65658ef63
44 changed files with 1822 additions and 978 deletions
|
|
@ -1,168 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public static class WriteExtensions
|
||||
{
|
||||
private static unsafe void Write(IntPtr actorPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||
{
|
||||
void WriteWeapon(int offset)
|
||||
{
|
||||
var address = (byte*) actorPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*) address = (ushort) id.Value;
|
||||
|
||||
if (type.HasValue)
|
||||
*(ushort*) (address + 2) = (ushort) type.Value;
|
||||
|
||||
if (variant.HasValue)
|
||||
*(ushort*) (address + 4) = variant.Value;
|
||||
|
||||
if (stain.HasValue)
|
||||
*(address + 6) = (byte) stain.Value;
|
||||
}
|
||||
|
||||
void WriteEquip(int offset)
|
||||
{
|
||||
var address = (byte*) actorPtr + 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(ActorEquipment.MainWeaponOffset);
|
||||
break;
|
||||
case EquipSlot.OffHand:
|
||||
WriteWeapon(ActorEquipment.OffWeaponOffset);
|
||||
break;
|
||||
case EquipSlot.Head:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset);
|
||||
break;
|
||||
case EquipSlot.Body:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 4);
|
||||
break;
|
||||
case EquipSlot.Hands:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 8);
|
||||
break;
|
||||
case EquipSlot.Legs:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 12);
|
||||
break;
|
||||
case EquipSlot.Feet:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 16);
|
||||
break;
|
||||
case EquipSlot.Ears:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 20);
|
||||
break;
|
||||
case EquipSlot.Neck:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 24);
|
||||
break;
|
||||
case EquipSlot.Wrists:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 28);
|
||||
break;
|
||||
case EquipSlot.RFinger:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 32);
|
||||
break;
|
||||
case EquipSlot.LFinger:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 36);
|
||||
break;
|
||||
default: throw new InvalidEnumArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this Stain stain, IntPtr actorPtr, EquipSlot slot)
|
||||
=> Write(actorPtr, slot, null, null, null, stain.RowIndex);
|
||||
|
||||
public static void Write(this Item item, IntPtr actorAddress)
|
||||
{
|
||||
var (id, type, variant) = item.MainModel;
|
||||
Write(actorAddress, item.EquippableTo, id, type, variant, null);
|
||||
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
||||
{
|
||||
var (subId, subType, subVariant) = item.SubModel;
|
||||
Write(actorAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this ActorArmor armor, IntPtr actorAddress, EquipSlot slot)
|
||||
=> Write(actorAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
|
||||
|
||||
public static void Write(this ActorWeapon weapon, IntPtr actorAddress, EquipSlot slot)
|
||||
=> Write(actorAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
|
||||
|
||||
public static unsafe void Write(this ActorEquipment equip, IntPtr actorAddress)
|
||||
{
|
||||
if (equip.IsSet == 0)
|
||||
return;
|
||||
|
||||
Write(actorAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
|
||||
Write(actorAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
|
||||
|
||||
fixed (ActorArmor* equipment = &equip.Head)
|
||||
{
|
||||
Buffer.MemoryCopy(equipment, (byte*) actorAddress + ActorEquipment.EquipmentOffset,
|
||||
ActorEquipment.EquipmentSlots * sizeof(ActorArmor), ActorEquipment.EquipmentSlots * sizeof(ActorArmor));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this ActorEquipment equip, IntPtr actorAddress, ActorEquipMask models, ActorEquipMask stains)
|
||||
{
|
||||
if (models == ActorEquipMask.All && stains == ActorEquipMask.All)
|
||||
{
|
||||
equip.Write(actorAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.MainHand))
|
||||
Write(actorAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.MainHand))
|
||||
Write(actorAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.OffHand))
|
||||
Write(actorAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.OffHand))
|
||||
Write(actorAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.Head))
|
||||
Write(actorAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Head))
|
||||
Write(actorAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Body))
|
||||
Write(actorAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Body))
|
||||
Write(actorAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Hands))
|
||||
Write(actorAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Hands))
|
||||
Write(actorAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Legs))
|
||||
Write(actorAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Legs))
|
||||
Write(actorAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Feet))
|
||||
Write(actorAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Feet))
|
||||
Write(actorAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.Ears))
|
||||
Write(actorAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.Neck))
|
||||
Write(actorAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.Wrists))
|
||||
Write(actorAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.LFinger))
|
||||
Write(actorAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.RFinger))
|
||||
Write(actorAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Glamourer.GameData/CharacterEquipExtensions.cs
Normal file
168
Glamourer.GameData/CharacterEquipExtensions.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public static class WriteExtensions
|
||||
{
|
||||
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||
{
|
||||
void WriteWeapon(int offset)
|
||||
{
|
||||
var address = (byte*) characterPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*) address = (ushort) id.Value;
|
||||
|
||||
if (type.HasValue)
|
||||
*(ushort*) (address + 2) = (ushort) type.Value;
|
||||
|
||||
if (variant.HasValue)
|
||||
*(ushort*) (address + 4) = variant.Value;
|
||||
|
||||
if (stain.HasValue)
|
||||
*(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)
|
||||
=> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ using System;
|
|||
namespace Glamourer
|
||||
{
|
||||
[Flags]
|
||||
public enum ActorEquipMask : ushort
|
||||
public enum CharacterEquipMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
MainHand = 0b000000000001,
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public unsafe struct LazyCustomization
|
||||
{
|
||||
public ActorCustomization* Address;
|
||||
public CharacterCustomization* Address;
|
||||
|
||||
public LazyCustomization(IntPtr actorPtr)
|
||||
=> Address = (ActorCustomization*) (actorPtr + ActorCustomization.CustomizationOffset);
|
||||
public LazyCustomization(IntPtr characterPtr)
|
||||
=> Address = (CharacterCustomization*) (characterPtr + CharacterCustomization.CustomizationOffset);
|
||||
|
||||
public ref ActorCustomization Value
|
||||
public ref CharacterCustomization Value
|
||||
=> ref *Address;
|
||||
|
||||
public LazyCustomization(ActorCustomization data)
|
||||
public LazyCustomization(CharacterCustomization data)
|
||||
=> Address = &data;
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ActorCustomization
|
||||
public struct CharacterCustomization
|
||||
{
|
||||
public const int CustomizationOffset = 0x1898;
|
||||
public const int CustomizationBytes = 26;
|
||||
|
||||
public static ActorCustomization Default = new()
|
||||
public static CharacterCustomization Default = new()
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
|
|
@ -150,13 +150,13 @@ namespace Glamourer.Customization
|
|||
}
|
||||
}
|
||||
|
||||
public void Read(Actor actor)
|
||||
=> Read(actor.Address + CustomizationOffset);
|
||||
public void Read(Character character)
|
||||
=> Read(character.Address + CustomizationOffset);
|
||||
|
||||
public ActorCustomization(Actor actor)
|
||||
public CharacterCustomization(Character character)
|
||||
: this()
|
||||
{
|
||||
Read(actor.Address + CustomizationOffset);
|
||||
Read(character.Address + CustomizationOffset);
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
|
|
@ -278,11 +278,11 @@ namespace Glamourer.Customization
|
|||
}
|
||||
}
|
||||
|
||||
public unsafe void Write(IntPtr actorAddress)
|
||||
public unsafe void Write(IntPtr characterAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, (byte*) actorAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
Buffer.MemoryCopy(ptr, (byte*) characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Dalamud.Plugin;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
|
|
@ -7,9 +8,9 @@ namespace Glamourer
|
|||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
|
||||
public CmpFile(DalamudPluginInterface pi)
|
||||
public CmpFile(DataManager gameData)
|
||||
{
|
||||
File = pi.Data.GetFile("chara/xls/charamake/human.cmp");
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
OddEyes,
|
||||
IrisSmall,
|
||||
IrisLarge,
|
||||
IrisSize,
|
||||
MidlanderM,
|
||||
HighlanderM,
|
||||
WildwoodM,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -11,9 +13,9 @@ namespace Glamourer.Customization
|
|||
private CustomizationManager()
|
||||
{ }
|
||||
|
||||
public static ICustomizationManager Create(DalamudPluginInterface pi)
|
||||
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
_options ??= new CustomizationOptions(pi);
|
||||
_options ??= new CustomizationOptions(pi, gameData, language);
|
||||
return new CustomizationManager();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Util;
|
||||
using Lumina.Data;
|
||||
|
|
@ -50,7 +51,7 @@ namespace Glamourer.Customization
|
|||
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) 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)
|
||||
{
|
||||
|
|
@ -129,7 +130,7 @@ namespace Glamourer.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 row = _listSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!;
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = race.ToRace() == Race.Hrothgar ? HrothgarFaces(row) : GetHairStyles(race, gender),
|
||||
|
|
@ -190,7 +191,7 @@ namespace Glamourer.Customization
|
|||
|
||||
set.FeaturesTattoos = featureDict;
|
||||
|
||||
set.OptionName = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
var nameArray = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
|
|
@ -210,14 +211,19 @@ namespace Glamourer.Customization
|
|||
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 =>
|
||||
set.Types = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
if (c == CustomizationId.HighlightColor)
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
|
||||
if (c == CustomizationId.EyeColorL)
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
|
|
@ -255,21 +261,21 @@ namespace Glamourer.Customization
|
|||
_ => Language.English,
|
||||
};
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi)
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
_cmpFile = new CmpFile(pi);
|
||||
_customizeSheet = pi.Data.GetExcelSheet<CharaMakeCustomize>();
|
||||
_lobby = pi.Data.GetExcelSheet<Lobby>();
|
||||
var tmp = pi.Data.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(pi.Data.Excel, new object?[]
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
_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",
|
||||
FromClientLanguage(pi.ClientState.ClientLanguage),
|
||||
FromClientLanguage(language),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = pi.Data.GetExcelSheet<HairMakeType>();
|
||||
SetNames(pi);
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
SetNames(gameData);
|
||||
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
|
|
@ -279,7 +285,7 @@ namespace Glamourer.Customization
|
|||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
|
||||
_icons = new IconStorage(pi, _list.Length * 50);
|
||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
|
|
@ -287,15 +293,16 @@ namespace Glamourer.Customization
|
|||
}
|
||||
}
|
||||
|
||||
public void SetNames(DalamudPluginInterface pi)
|
||||
private void SetNames(DataManager gameData)
|
||||
{
|
||||
var subRace = pi.Data.GetExcelSheet<Tribe>();
|
||||
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] =
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace Glamourer.Customization
|
|||
public IReadOnlyList<Customization> LipColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> _types { get; internal set; } = null!;
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int) id];
|
||||
|
|
@ -154,7 +154,7 @@ namespace Glamourer.Customization
|
|||
}
|
||||
|
||||
public CharaMakeParams.MenuType Type(CustomizationId id)
|
||||
=> _types[(int) id];
|
||||
=> Types[(int) id];
|
||||
|
||||
|
||||
public int Count(CustomizationId id)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Data;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer
|
||||
|
|
@ -10,23 +11,37 @@ namespace Glamourer
|
|||
{
|
||||
private static Dictionary<byte, Stain>? _stains;
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static SortedList<uint, ModelChara>? _models;
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DalamudPluginInterface pi)
|
||||
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;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||
{
|
||||
if (_stains != null)
|
||||
return _stains;
|
||||
|
||||
var sheet = pi.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>();
|
||||
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));
|
||||
return _stains;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DalamudPluginInterface pi)
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
||||
{
|
||||
if (_itemsBySlot != null)
|
||||
return _itemsBySlot;
|
||||
|
||||
var sheet = pi.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>();
|
||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||
|
||||
Item EmptySlot(EquipSlot slot)
|
||||
=> new(sheet.First(), "Nothing", slot);
|
||||
|
|
@ -40,7 +55,7 @@ namespace Glamourer
|
|||
[EquipSlot.Feet] = new(200) { EmptySlot(EquipSlot.Feet) },
|
||||
[EquipSlot.RFinger] = new(200) { EmptySlot(EquipSlot.RFinger) },
|
||||
[EquipSlot.Neck] = new(200) { EmptySlot(EquipSlot.Neck) },
|
||||
[EquipSlot.MainHand] = new(200) { EmptySlot(EquipSlot.MainHand) },
|
||||
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
|
||||
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
||||
[EquipSlot.Wrists] = new(200) { EmptySlot(EquipSlot.Wrists) },
|
||||
[EquipSlot.Ears] = new(200) { EmptySlot(EquipSlot.Ears) },
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer.GameData</AssemblyName>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
|
|
@ -55,7 +56,7 @@
|
|||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.GameData">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\$(TargetFramework)\Penumbra.GameData.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Data.LuminaExtensions;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiScene;
|
||||
using Lumina.Data.Files;
|
||||
|
||||
|
|
@ -9,33 +10,35 @@ namespace Glamourer.Util
|
|||
{
|
||||
public class IconStorage : IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly Dictionary<int, TextureWrap> _icons;
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly DataManager _gameData;
|
||||
private readonly Dictionary<uint, TextureWrap> _icons;
|
||||
|
||||
public IconStorage(DalamudPluginInterface pi, int size = 0)
|
||||
public IconStorage(DalamudPluginInterface pi, DataManager gameData, int size = 0)
|
||||
{
|
||||
_pi = pi;
|
||||
_icons = new Dictionary<int, TextureWrap>(size);
|
||||
_pi = pi;
|
||||
_gameData = gameData;
|
||||
_icons = new Dictionary<uint, TextureWrap>(size);
|
||||
}
|
||||
|
||||
public TextureWrap this[int id]
|
||||
=> LoadIcon(id);
|
||||
|
||||
private TexFile? LoadIconHq(int id)
|
||||
private TexFile? LoadIconHq(uint id)
|
||||
{
|
||||
var path = $"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex";
|
||||
return _pi.Data.GetFile<TexFile>(path);
|
||||
return _gameData.GetFile<TexFile>(path);
|
||||
}
|
||||
|
||||
public TextureWrap LoadIcon(uint id)
|
||||
=> LoadIcon((int) id);
|
||||
|
||||
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) ?? _pi.Data.GetIcon(id);
|
||||
var icon = LoadIconHq(id) ?? _gameData.GetIcon(id)!;
|
||||
var iconData = icon.GetRgbaImageData();
|
||||
|
||||
ret = _pi.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4);
|
||||
|
|
|
|||
BIN
Glamourer.zip
BIN
Glamourer.zip
Binary file not shown.
|
|
@ -1,8 +1,8 @@
|
|||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public static class ActorExtensions
|
||||
public static class CharacterExtensions
|
||||
{
|
||||
public const int WetnessOffset = 0x19A5;
|
||||
public const byte WetnessFlag = 0x10;
|
||||
|
|
@ -13,10 +13,10 @@ namespace Glamourer
|
|||
public const int WeaponHiddenOffset = 0xF64;
|
||||
public const byte WeaponHiddenFlag = 0x02;
|
||||
|
||||
public static unsafe bool IsWet(this Actor a)
|
||||
public static unsafe bool IsWet(this Character a)
|
||||
=> (*((byte*) a.Address + WetnessOffset) & WetnessFlag) != 0;
|
||||
|
||||
public static unsafe bool SetWetness(this Actor a, bool value)
|
||||
public static unsafe bool SetWetness(this Character a, bool value)
|
||||
{
|
||||
var current = a.IsWet();
|
||||
if (current == value)
|
||||
|
|
@ -29,10 +29,10 @@ namespace Glamourer
|
|||
return true;
|
||||
}
|
||||
|
||||
public static unsafe ref byte StateFlags(this Actor a)
|
||||
public static unsafe ref byte StateFlags(this Character a)
|
||||
=> ref *((byte*) a.Address + StateFlagsOffset);
|
||||
|
||||
public static bool SetStateFlag(this Actor a, bool value, byte flag)
|
||||
public static bool SetStateFlag(this Character a, bool value, byte flag)
|
||||
{
|
||||
var current = a.StateFlags();
|
||||
var previousValue = (current & flag) != 0;
|
||||
|
|
@ -46,20 +46,20 @@ namespace Glamourer
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool IsHatHidden(this Actor a)
|
||||
public static bool IsHatHidden(this Character a)
|
||||
=> (a.StateFlags() & HatHiddenFlag) != 0;
|
||||
|
||||
public static unsafe bool IsWeaponHidden(this Actor a)
|
||||
public static unsafe bool IsWeaponHidden(this Character a)
|
||||
=> (a.StateFlags() & WeaponHiddenFlag) != 0
|
||||
&& (*((byte*) a.Address + WeaponHiddenOffset) & WeaponHiddenFlag) != 0;
|
||||
|
||||
public static bool IsVisorToggled(this Actor a)
|
||||
public static bool IsVisorToggled(this Character a)
|
||||
=> (a.StateFlags() & VisorToggledFlag) != 0;
|
||||
|
||||
public static bool SetHatHidden(this Actor a, bool value)
|
||||
public static bool SetHatHidden(this Character a, bool value)
|
||||
=> SetStateFlag(a, value, HatHiddenFlag);
|
||||
|
||||
public static unsafe bool SetWeaponHidden(this Actor a, bool value)
|
||||
public static unsafe bool SetWeaponHidden(this Character a, bool value)
|
||||
{
|
||||
var ret = SetStateFlag(a, value, WeaponHiddenFlag);
|
||||
var val = *((byte*) a.Address + WeaponHiddenOffset);
|
||||
|
|
@ -70,10 +70,10 @@ namespace Glamourer
|
|||
return ret || (val & WeaponHiddenFlag) != 0 != value;
|
||||
}
|
||||
|
||||
public static bool SetVisorToggled(this Actor a, bool value)
|
||||
public static bool SetVisorToggled(this Character a, bool value)
|
||||
=> SetStateFlag(a, value, VisorToggledFlag);
|
||||
|
||||
public static unsafe ref float Alpha(this Actor a)
|
||||
public static unsafe ref float Alpha(this Character a)
|
||||
=> ref *(float*) ((byte*) a.Address + AlphaOffset);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Glamourer.Customization;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
|
|
@ -36,8 +39,8 @@ namespace Glamourer
|
|||
public class CharacterSave
|
||||
{
|
||||
public const byte CurrentVersion = 2;
|
||||
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + ActorCustomization.CustomizationBytes;
|
||||
public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + ActorCustomization.CustomizationBytes + 4 + 1;
|
||||
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes;
|
||||
public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CharacterCustomization.CustomizationBytes + 4 + 1;
|
||||
|
||||
public const byte TotalSize = TotalSizeVersion2;
|
||||
|
||||
|
|
@ -97,8 +100,8 @@ namespace Glamourer
|
|||
|
||||
public byte StateFlags
|
||||
{
|
||||
get => _bytes[64 + ActorCustomization.CustomizationBytes];
|
||||
set => _bytes[64 + ActorCustomization.CustomizationBytes] = value;
|
||||
get => _bytes[64 + CharacterCustomization.CustomizationBytes];
|
||||
set => _bytes[64 + CharacterCustomization.CustomizationBytes] = value;
|
||||
}
|
||||
|
||||
public bool HatState
|
||||
|
|
@ -119,9 +122,9 @@ namespace Glamourer
|
|||
set => StateFlags = (byte) (value ? StateFlags & ~0x02 : StateFlags | 0x02);
|
||||
}
|
||||
|
||||
public ActorEquipMask WriteEquipment
|
||||
public CharacterEquipMask WriteEquipment
|
||||
{
|
||||
get => (ActorEquipMask) ((ushort) _bytes[2] | ((ushort) _bytes[3] << 8));
|
||||
get => (CharacterEquipMask) (_bytes[2] | (_bytes[3] << 8));
|
||||
set
|
||||
{
|
||||
_bytes[2] = (byte) ((ushort) value & 0xFF);
|
||||
|
|
@ -129,11 +132,104 @@ namespace Glamourer
|
|||
}
|
||||
}
|
||||
|
||||
private static Dictionary<EquipSlot, (int, int, bool)> Offsets()
|
||||
{
|
||||
var stainOffsetWeapon = (int) Marshal.OffsetOf<CharacterWeapon>("Stain");
|
||||
var stainOffsetEquip = (int) Marshal.OffsetOf<CharacterArmor>("Stain");
|
||||
|
||||
(int, int, bool) ToOffsets(IntPtr offset, bool weapon)
|
||||
{
|
||||
var off = 4 + CharacterCustomization.CustomizationBytes + (int) offset;
|
||||
return weapon ? (off, off + stainOffsetWeapon, weapon) : (off, off + stainOffsetEquip, weapon);
|
||||
}
|
||||
|
||||
return new Dictionary<EquipSlot, (int, int, bool)>(12)
|
||||
{
|
||||
[EquipSlot.MainHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("MainHand"), true),
|
||||
[EquipSlot.OffHand] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("OffHand"), true),
|
||||
[EquipSlot.Head] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Head"), false),
|
||||
[EquipSlot.Body] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Body"), false),
|
||||
[EquipSlot.Hands] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Hands"), false),
|
||||
[EquipSlot.Legs] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Legs"), false),
|
||||
[EquipSlot.Feet] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Feet"), false),
|
||||
[EquipSlot.Ears] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Ears"), false),
|
||||
[EquipSlot.Neck] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Neck"), false),
|
||||
[EquipSlot.Wrists] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("Wrists"), false),
|
||||
[EquipSlot.RFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("RFinger"), false),
|
||||
[EquipSlot.LFinger] = ToOffsets(Marshal.OffsetOf<CharacterEquipment>("LFinger"), false),
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<EquipSlot, (int, int, bool)> FieldOffsets = Offsets();
|
||||
|
||||
public bool WriteStain(EquipSlot slot, StainId stainId)
|
||||
{
|
||||
if (WriteProtected)
|
||||
return false;
|
||||
|
||||
var (_, stainOffset, _) = FieldOffsets[slot];
|
||||
if (_bytes[stainOffset] == (byte) stainId)
|
||||
return false;
|
||||
|
||||
_bytes[stainOffset] = stainId.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool WriteItem(int offset, SetId id, WeaponType type, ushort variant, bool weapon)
|
||||
{
|
||||
var idBytes = BitConverter.GetBytes(id.Value);
|
||||
|
||||
static bool WriteIfDifferent(ref byte x, byte y)
|
||||
{
|
||||
if (x == y)
|
||||
return false;
|
||||
|
||||
x = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
var ret = WriteIfDifferent(ref _bytes[offset], idBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 1], idBytes[1]);
|
||||
if (weapon)
|
||||
{
|
||||
var typeBytes = BitConverter.GetBytes(type.Value);
|
||||
var variantBytes = BitConverter.GetBytes(variant);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 2], typeBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 3], typeBytes[1]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 4], variantBytes[0]);
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 5], variantBytes[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret |= WriteIfDifferent(ref _bytes[offset + 2], (byte) variant);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool WriteItem(Item item)
|
||||
{
|
||||
if (WriteProtected)
|
||||
return false;
|
||||
|
||||
var (itemOffset, _, isWeapon) = FieldOffsets[item.EquippableTo];
|
||||
var (id, type, variant) = item.MainModel;
|
||||
var ret = WriteItem(itemOffset, id, type, variant, isWeapon);
|
||||
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
||||
{
|
||||
var (subOffset, _, _) = FieldOffsets[EquipSlot.OffHand];
|
||||
var (subId, subType, subVariant) = item.SubModel;
|
||||
ret |= WriteItem(subOffset, subId, subType, subVariant, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public unsafe float Alpha
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = &_bytes[60 + ActorCustomization.CustomizationBytes])
|
||||
fixed (byte* ptr = &_bytes[60 + CharacterCustomization.CustomizationBytes])
|
||||
{
|
||||
return *(float*) ptr;
|
||||
}
|
||||
|
|
@ -142,24 +238,24 @@ namespace Glamourer
|
|||
{
|
||||
fixed (byte* ptr = _bytes)
|
||||
{
|
||||
*(ptr + 60 + ActorCustomization.CustomizationBytes + 0) = *((byte*) &value + 0);
|
||||
*(ptr + 60 + ActorCustomization.CustomizationBytes + 1) = *((byte*) &value + 1);
|
||||
*(ptr + 60 + ActorCustomization.CustomizationBytes + 2) = *((byte*) &value + 2);
|
||||
*(ptr + 60 + ActorCustomization.CustomizationBytes + 3) = *((byte*) &value + 3);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 0) = *((byte*) &value + 0);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 1) = *((byte*) &value + 1);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 2) = *((byte*) &value + 2);
|
||||
*(ptr + 60 + CharacterCustomization.CustomizationBytes + 3) = *((byte*) &value + 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(ActorCustomization customization)
|
||||
public void Load(CharacterCustomization customization)
|
||||
{
|
||||
WriteCustomizations = true;
|
||||
customization.WriteBytes(_bytes, 4);
|
||||
}
|
||||
|
||||
public void Load(ActorEquipment equipment, ActorEquipMask mask = ActorEquipMask.All)
|
||||
public void Load(CharacterEquipment equipment, CharacterEquipMask mask = CharacterEquipMask.All)
|
||||
{
|
||||
WriteEquipment = mask;
|
||||
equipment.WriteBytes(_bytes, 4 + ActorCustomization.CustomizationBytes);
|
||||
equipment.WriteBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
|
||||
}
|
||||
|
||||
public string ToBase64()
|
||||
|
|
@ -179,19 +275,19 @@ namespace Glamourer
|
|||
$"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}].");
|
||||
}
|
||||
|
||||
private static void CheckActorMask(byte val1, byte val2)
|
||||
private static void CheckCharacterMask(byte val1, byte val2)
|
||||
{
|
||||
var mask = (ActorEquipMask) ((ushort) val1 | ((ushort) val2 << 8));
|
||||
if (mask > ActorEquipMask.All)
|
||||
var mask = (CharacterEquipMask) (val1 | (val2 << 8));
|
||||
if (mask > CharacterEquipMask.All)
|
||||
throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid value {mask} in byte 3 and 4.");
|
||||
}
|
||||
|
||||
public void LoadActor(Actor a)
|
||||
public void LoadCharacter(Character a)
|
||||
{
|
||||
WriteCustomizations = true;
|
||||
Load(new ActorCustomization(a));
|
||||
Load(new CharacterCustomization(a));
|
||||
|
||||
Load(new ActorEquipment(a), ActorEquipMask.All);
|
||||
Load(new CharacterEquipment(a));
|
||||
|
||||
SetHatState = true;
|
||||
SetVisorState = true;
|
||||
|
|
@ -202,11 +298,13 @@ namespace Glamourer
|
|||
Alpha = a.Alpha();
|
||||
}
|
||||
|
||||
public void Apply(Actor a)
|
||||
public void Apply(Character a)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(a);
|
||||
|
||||
if (WriteCustomizations)
|
||||
Customizations.Write(a.Address);
|
||||
if (WriteEquipment != ActorEquipMask.None)
|
||||
if (WriteEquipment != CharacterEquipMask.None)
|
||||
Equipment.Write(a.Address, WriteEquipment, WriteEquipment);
|
||||
a.SetWetness(IsWet);
|
||||
a.Alpha() = Alpha;
|
||||
|
|
@ -243,7 +341,7 @@ namespace Glamourer
|
|||
default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
||||
CheckActorMask(bytes[2], bytes[3]);
|
||||
CheckCharacterMask(bytes[2], bytes[3]);
|
||||
bytes.CopyTo(_bytes, 0);
|
||||
}
|
||||
|
||||
|
|
@ -254,23 +352,23 @@ namespace Glamourer
|
|||
return ret;
|
||||
}
|
||||
|
||||
public unsafe ref ActorCustomization Customizations
|
||||
public unsafe ref CharacterCustomization Customizations
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (byte* ptr = _bytes)
|
||||
{
|
||||
return ref *(ActorCustomization*) (ptr + 4);
|
||||
return ref *(CharacterCustomization*) (ptr + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ActorEquipment Equipment
|
||||
public CharacterEquipment Equipment
|
||||
{
|
||||
get
|
||||
{
|
||||
var ret = new ActorEquipment();
|
||||
ret.FromBytes(_bytes, 4 + ActorCustomization.CustomizationBytes);
|
||||
var ret = new CharacterEquipment();
|
||||
ret.FromBytes(_bytes, 4 + CharacterCustomization.CustomizationBytes);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
|
||||
public CmpFile(DalamudPluginInterface pi)
|
||||
{
|
||||
File = pi.Data.GetFile("chara/xls/charamake/human.cmp");
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
{
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint) (File.Data[i + 1] << 8)
|
||||
| (uint) (File.Data[i + 2] << 16)
|
||||
| (uint) (File.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Glamourer/Dalamud.cs
Normal file
56
Glamourer/Dalamud.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Game;
|
||||
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.Party;
|
||||
using Dalamud.Game.Command;
|
||||
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.Plugin;
|
||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class Dalamud
|
||||
{
|
||||
public static void Initialize(DalamudPluginInterface pluginInterface)
|
||||
=> pluginInterface.Create<Dalamud>();
|
||||
|
||||
// @formatter:off
|
||||
[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 SigScanner SigScanner { 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 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 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 ObjectTable Objects { get; private set; } = null!;
|
||||
//[PluginService][RequiredVersion("1.0")] public static FateTable Fates { get; private set; } = null!;
|
||||
//[PluginService][RequiredVersion("1.0")] public static LibcFunction LibC { get; private set; } = null!;
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.FileSystem;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -16,9 +16,9 @@ namespace Glamourer.Designs
|
|||
public SortedList<string, CharacterSave> Designs = null!;
|
||||
public FileSystem.FileSystem FileSystem { get; } = new();
|
||||
|
||||
public DesignManager(DalamudPluginInterface pi)
|
||||
public DesignManager()
|
||||
{
|
||||
var saveFolder = new DirectoryInfo(pi.GetPluginConfigDirectory());
|
||||
var saveFolder = new DirectoryInfo(Dalamud.PluginInterface.GetPluginConfigDirectory());
|
||||
if (!saveFolder.Exists)
|
||||
Directory.CreateDirectory(saveFolder.FullName);
|
||||
|
||||
|
|
@ -31,24 +31,21 @@ namespace Glamourer.Designs
|
|||
{
|
||||
FileSystem.Clear();
|
||||
var anyChanges = false;
|
||||
foreach (var kvp in Designs.ToArray())
|
||||
foreach (var (path, save) in Designs.ToArray())
|
||||
{
|
||||
var path = kvp.Key;
|
||||
var save = kvp.Value;
|
||||
|
||||
try
|
||||
{
|
||||
var (folder, name) = FileSystem.CreateAllFolders(path);
|
||||
var design = new Design(folder, name) { Data = save };
|
||||
folder.FindOrAddChild(design);
|
||||
var fixedPath = design.FullName();
|
||||
if (!string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Designs.Remove(path);
|
||||
Designs[fixedPath] = save;
|
||||
anyChanges = true;
|
||||
PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}.");
|
||||
}
|
||||
if (string.Equals(fixedPath, path, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
Designs.Remove(path);
|
||||
Designs[fixedPath] = save;
|
||||
anyChanges = true;
|
||||
PluginLog.Debug($"Problem loading saved designs, {path} was renamed to {fixedPath}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
162
Glamourer/Designs/FixedDesigns.cs
Normal file
162
Glamourer/Designs/FixedDesigns.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.FileSystem;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
{
|
||||
public class FixedDesigns : IDisposable
|
||||
{
|
||||
public class FixedDesign
|
||||
{
|
||||
public string Name;
|
||||
public Design Design;
|
||||
public bool Enabled;
|
||||
|
||||
public GlamourerConfig.FixedDesign ToSave()
|
||||
=> new()
|
||||
{
|
||||
Name = Name,
|
||||
Path = Design.FullName(),
|
||||
Enabled = Enabled,
|
||||
};
|
||||
|
||||
public FixedDesign(string name, Design design, bool enabled)
|
||||
{
|
||||
Name = name;
|
||||
Design = design;
|
||||
Enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public List<FixedDesign> Data;
|
||||
public Dictionary<string, FixedDesign> EnabledDesigns;
|
||||
|
||||
public bool EnableDesign(FixedDesign design)
|
||||
{
|
||||
var changes = !design.Enabled;
|
||||
if (EnabledDesigns.TryGetValue(design.Name, out var oldDesign))
|
||||
{
|
||||
oldDesign.Enabled = false;
|
||||
changes = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Glamourer.PlayerWatcher.AddPlayerToWatch(design.Name);
|
||||
}
|
||||
|
||||
EnabledDesigns[design.Name] = design;
|
||||
design.Enabled = true;
|
||||
if (Dalamud.Objects.FirstOrDefault(o => o.ObjectKind == ObjectKind.Player && o.Name.ToString() == design.Name)
|
||||
is Character character)
|
||||
OnPlayerChange(character);
|
||||
return changes;
|
||||
}
|
||||
|
||||
public bool DisableDesign(FixedDesign design)
|
||||
{
|
||||
if (!design.Enabled)
|
||||
return false;
|
||||
|
||||
design.Enabled = false;
|
||||
EnabledDesigns.Remove(design.Name);
|
||||
Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name);
|
||||
return true;
|
||||
}
|
||||
|
||||
public FixedDesigns(DesignManager designs)
|
||||
{
|
||||
Data = new List<FixedDesign>(Glamourer.Config.FixedDesigns.Count);
|
||||
EnabledDesigns = new Dictionary<string, FixedDesign>(Glamourer.Config.FixedDesigns.Count);
|
||||
Glamourer.PlayerWatcher.PlayerChanged += OnPlayerChange;
|
||||
var changes = false;
|
||||
for (var i = 0; i < Glamourer.Config.FixedDesigns.Count; ++i)
|
||||
{
|
||||
var save = Glamourer.Config.FixedDesigns[i];
|
||||
if (designs.FileSystem.Find(save.Path, out var d) && d is Design design)
|
||||
{
|
||||
Data.Add(new FixedDesign(save.Name, design, save.Enabled));
|
||||
if (save.Enabled)
|
||||
changes |= EnableDesign(Data.Last());
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Warning($"{save.Path} does not exist anymore, removing {save.Name} from fixed designs.");
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(i--);
|
||||
changes = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changes)
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
private void OnPlayerChange(Character character)
|
||||
{
|
||||
var name = character.Name.ToString();
|
||||
if (EnabledDesigns.TryGetValue(name, out var design))
|
||||
{
|
||||
PluginLog.Debug("Redrawing {CharacterName} with {DesignName}.", name, design.Design.FullName());
|
||||
design.Design.Data.Apply(character);
|
||||
Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||
Glamourer.Penumbra.RedrawObject(character, RedrawType.WithSettings, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string name, Design design, bool enabled = false)
|
||||
{
|
||||
Data.Add(new FixedDesign(name, design, enabled));
|
||||
Glamourer.Config.FixedDesigns.Add(Data.Last().ToSave());
|
||||
|
||||
if (enabled)
|
||||
EnableDesign(Data.Last());
|
||||
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Remove(FixedDesign design)
|
||||
{
|
||||
var idx = Data.IndexOf(design);
|
||||
if (idx < 0)
|
||||
return;
|
||||
|
||||
Data.RemoveAt(idx);
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
if (design.Enabled)
|
||||
{
|
||||
EnabledDesigns.Remove(design.Name);
|
||||
Glamourer.PlayerWatcher.RemovePlayerFromWatch(design.Name);
|
||||
}
|
||||
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Move(FixedDesign design, int newIdx)
|
||||
{
|
||||
if (newIdx < 0)
|
||||
newIdx = 0;
|
||||
if (newIdx >= Data.Count)
|
||||
newIdx = Data.Count - 1;
|
||||
|
||||
var idx = Data.IndexOf(design);
|
||||
if (idx < 0 || idx == newIdx)
|
||||
return;
|
||||
|
||||
Data.RemoveAt(idx);
|
||||
Data.Insert(newIdx, design);
|
||||
Glamourer.Config.FixedDesigns.RemoveAt(idx);
|
||||
Glamourer.Config.FixedDesigns.Insert(newIdx, design.ToSave());
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Glamourer.Config.FixedDesigns = Data.Select(d => d.ToSave()).ToList();
|
||||
Glamourer.Config.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Glamourer/Designs/RevertableDesigns.cs
Normal file
31
Glamourer/Designs/RevertableDesigns.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
|
||||
namespace Glamourer.Designs
|
||||
{
|
||||
public class RevertableDesigns
|
||||
{
|
||||
public readonly Dictionary<string, CharacterSave> Saves = new();
|
||||
|
||||
public bool Add(Character actor)
|
||||
{
|
||||
var name = actor.Name.ToString();
|
||||
if (Saves.TryGetValue(name, out var save))
|
||||
return false;
|
||||
|
||||
save = new CharacterSave();
|
||||
save.LoadCharacter(actor);
|
||||
Saves[name] = save;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Revert(Character actor)
|
||||
{
|
||||
if (!Saves.TryGetValue(actor.Name.ToString(), out var save))
|
||||
return false;
|
||||
|
||||
save.Apply(actor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
|
|
@ -12,7 +11,7 @@ namespace Glamourer.FileSystem
|
|||
private static unsafe bool IsDropping(string name)
|
||||
=> ImGui.AcceptDragDropPayload(name).NativePtr != null;
|
||||
|
||||
private static IFileSystemBase? _draggedObject = null;
|
||||
private static IFileSystemBase? _draggedObject;
|
||||
|
||||
public static bool DragDropTarget(FileSystem fs, IFileSystemBase child, out string oldPath, out IFileSystemBase? draggedChild)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Glamourer.Designs;
|
||||
|
||||
namespace Glamourer.FileSystem
|
||||
{
|
||||
internal class FolderStructureComparer : IComparer<IFileSystemBase>
|
||||
{
|
||||
// Compare only the direct folder names since this is only used inside an enumeration of children of one folder.
|
||||
public static int Cmp(IFileSystemBase x, IFileSystemBase y)
|
||||
=> ReferenceEquals(x, y) ? 0 : string.Compare(x.Name, y.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||
public static int Cmp(IFileSystemBase? x, IFileSystemBase? y)
|
||||
=> ReferenceEquals(x, y) ? 0 : string.Compare(x?.Name, y?.Name, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
public int Compare(IFileSystemBase x, IFileSystemBase y)
|
||||
public int Compare(IFileSystemBase? x, IFileSystemBase? y)
|
||||
=> Cmp(x, y);
|
||||
|
||||
internal static readonly FolderStructureComparer Default = new();
|
||||
|
|
|
|||
207
Glamourer/Glamourer.cs
Normal file
207
Glamourer/Glamourer.cs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Gui;
|
||||
using ImGuiNET;
|
||||
using Penumbra.PlayerWatch;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
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!;
|
||||
private readonly Interface _interface;
|
||||
public readonly DesignManager Designs;
|
||||
public readonly FixedDesigns FixedDesigns;
|
||||
public static RevertableDesigns RevertableDesigns = new();
|
||||
|
||||
|
||||
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);
|
||||
FixedDesigns = new FixedDesigns(Designs);
|
||||
|
||||
if (Config.ApplyFixedDesigns)
|
||||
PlayerWatcher.Enable();
|
||||
|
||||
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()
|
||||
{
|
||||
FixedDesigns.Dispose();
|
||||
Penumbra.Dispose();
|
||||
PlayerWatcher.Dispose();
|
||||
_interface.Dispose();
|
||||
Dalamud.Commands.RemoveHandler("/glamour");
|
||||
Dalamud.Commands.RemoveHandler("/glamourer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer</AssemblyName>
|
||||
<FileVersion>0.0.3.0</FileVersion>
|
||||
<AssemblyVersion>0.0.3.0</AssemblyVersion>
|
||||
<FileVersion>0.0.5.4</FileVersion>
|
||||
<AssemblyVersion>0.0.5.4</AssemblyVersion>
|
||||
<Company>SoftOtter</Company>
|
||||
<Product>Glamourer</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
|
|
@ -46,47 +47,40 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="SDL2-CS">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\SDL2-CS.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.GameData">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.Api">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.Api.dll</HintPath>
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\$(TargetFramework)\Penumbra.GameData.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.PlayerWatch">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.PlayerWatch.dll</HintPath>
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\$(TargetFramework)\Penumbra.PlayerWatch.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
@ -100,7 +94,7 @@
|
|||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.Api.dll, $(TargetDir)Penumbra.PlayerWatch.dll $(SolutionDir)$(SolutionName).zip" />
|
||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.PlayerWatch.dll $(SolutionDir)$(SolutionName).zip" />
|
||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
{
|
||||
"Author": "Ottermandias",
|
||||
"Name": "Glamourer",
|
||||
"Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.",
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "0.0.3.0",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 3,
|
||||
"LoadPriority": -100
|
||||
{
|
||||
"Author": "Ottermandias",
|
||||
"Name": "Glamourer",
|
||||
"Punchline": "Change and save appearance of players.",
|
||||
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
||||
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "0.0.5.4",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 4,
|
||||
"LoadPriority": -100,
|
||||
"ImageUrls": null,
|
||||
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
|
||||
}
|
||||
|
|
@ -1,36 +1,45 @@
|
|||
using Dalamud.Configuration;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Configuration;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class GlamourerConfig : IPluginConfiguration
|
||||
{
|
||||
public struct FixedDesign
|
||||
{
|
||||
public string Name;
|
||||
public string Path;
|
||||
public bool Enabled;
|
||||
}
|
||||
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public const uint DefaultCustomizationColor = 0xFFC000C0;
|
||||
public const uint DefaultStateColor = 0xFF00C0C0;
|
||||
public const uint DefaultEquipmentColor = 0xFF00C000;
|
||||
|
||||
public bool FoldersFirst { get; set; } = false;
|
||||
public bool ColorDesigns { get; set; } = true;
|
||||
public bool ShowLocks { get; set; } = true;
|
||||
public bool AttachToPenumbra { get; set; } = true;
|
||||
public bool FoldersFirst { get; set; } = false;
|
||||
public bool ColorDesigns { get; set; } = true;
|
||||
public bool ShowLocks { get; set; } = true;
|
||||
public bool AttachToPenumbra { get; set; } = true;
|
||||
public bool ApplyFixedDesigns { get; set; } = true;
|
||||
|
||||
public uint CustomizationColor { get; set; } = DefaultCustomizationColor;
|
||||
public uint StateColor { get; set; } = DefaultStateColor;
|
||||
public uint EquipmentColor { get; set; } = DefaultEquipmentColor;
|
||||
|
||||
public List<FixedDesign> FixedDesigns { get; set; } = new();
|
||||
|
||||
public void Save()
|
||||
=> Glamourer.PluginInterface.SavePluginConfig(this);
|
||||
=> Dalamud.PluginInterface.SavePluginConfig(this);
|
||||
|
||||
public static GlamourerConfig Create()
|
||||
public static GlamourerConfig Load()
|
||||
{
|
||||
var config = Glamourer.PluginInterface.GetPluginConfig() as GlamourerConfig;
|
||||
if (config == null)
|
||||
{
|
||||
config = new GlamourerConfig();
|
||||
Glamourer.PluginInterface.SavePluginConfig(config);
|
||||
}
|
||||
if (Dalamud.PluginInterface.GetPluginConfig() is GlamourerConfig config)
|
||||
return config;
|
||||
|
||||
config = new GlamourerConfig();
|
||||
config.Save();
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,30 +8,48 @@ namespace Glamourer.Gui
|
|||
{
|
||||
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> _itemNamesLower;
|
||||
private readonly Func<T, string> _itemToName;
|
||||
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 = null;
|
||||
public Action? PostPreview = null;
|
||||
public Func<T, bool>? CreateSelectable = null;
|
||||
public Action? PreList = null;
|
||||
public Action? PostList = null;
|
||||
public float? HeightPerItem = null;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
|
@ -42,26 +60,28 @@ namespace Glamourer.Gui
|
|||
_size = size;
|
||||
_previewSize = previewSize;
|
||||
|
||||
_itemNamesLower = _items.Select(i => _itemToName(i).ToLowerInvariant()).ToList();
|
||||
_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;
|
||||
_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;
|
||||
_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)
|
||||
|
|
@ -69,7 +89,10 @@ namespace Glamourer.Gui
|
|||
numItems = ItemsAtOnce;
|
||||
nodeIdx = -1;
|
||||
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
return false;
|
||||
}
|
||||
|
||||
var ret = false;
|
||||
try
|
||||
|
|
@ -80,7 +103,6 @@ namespace Glamourer.Gui
|
|||
_focus = true;
|
||||
}
|
||||
|
||||
|
||||
var scrollY = Math.Max((int) (ImGui.GetScrollY() / _heightPerItem) - 1, 0);
|
||||
var restHeight = scrollY * _heightPerItem;
|
||||
numItems = 0;
|
||||
|
|
@ -89,38 +111,34 @@ namespace Glamourer.Gui
|
|||
if (restHeight > 0)
|
||||
ImGui.Dummy(Vector2.UnitY * restHeight);
|
||||
|
||||
for (var i = scrollY; i < _items.Count; ++i)
|
||||
for (var i = scrollY; i < _currentItemNames.Count; ++i)
|
||||
{
|
||||
if (!_itemNamesLower[i].Contains(_currentFilterLower))
|
||||
if (++numItems > ItemsAtOnce + 2)
|
||||
continue;
|
||||
|
||||
++numItems;
|
||||
if (numItems <= ItemsAtOnce + 2)
|
||||
nodeIdx = _currentItemNames[i].Item2;
|
||||
var item = _items[nodeIdx]!;
|
||||
bool success;
|
||||
if (CreateSelectable != null)
|
||||
{
|
||||
nodeIdx = i;
|
||||
var item = _items[i]!;
|
||||
var success = false;
|
||||
if (CreateSelectable != null)
|
||||
{
|
||||
success = CreateSelectable(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = _itemToName(item);
|
||||
success = ImGui.Selectable(name, name == currentName);
|
||||
}
|
||||
success = CreateSelectable(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = _itemToName(item);
|
||||
success = ImGui.Selectable(name, name == currentName);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
value = item;
|
||||
ImGui.CloseCurrentPopup();
|
||||
ret = true;
|
||||
}
|
||||
if (success)
|
||||
{
|
||||
value = item;
|
||||
ImGui.CloseCurrentPopup();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (numItems > ItemsAtOnce + 2)
|
||||
ImGui.Dummy(Vector2.UnitY * (numItems - ItemsAtOnce - 2) * _heightPerItem);
|
||||
if (_currentItemNames.Count > ItemsAtOnce + 2)
|
||||
ImGui.Dummy(Vector2.UnitY * (_currentItemNames.Count - ItemsAtOnce - 2 - scrollY) * _heightPerItem);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -140,23 +158,29 @@ namespace Glamourer.Gui
|
|||
PrePreview?.Invoke();
|
||||
if (!ImGui.BeginCombo(_label, currentName, Flags))
|
||||
{
|
||||
_focus = false;
|
||||
_currentFilter = string.Empty;
|
||||
_currentFilterLower = string.Empty;
|
||||
if (_needsClear)
|
||||
{
|
||||
_needsClear = false;
|
||||
_focus = false;
|
||||
UpdateFilter(string.Empty);
|
||||
}
|
||||
|
||||
PostPreview?.Invoke();
|
||||
return false;
|
||||
}
|
||||
|
||||
_needsClear = true;
|
||||
PostPreview?.Invoke();
|
||||
|
||||
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
|
||||
|
||||
var ret = false;
|
||||
bool ret;
|
||||
try
|
||||
{
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref _currentFilter, 255))
|
||||
_currentFilterLower = _currentFilter.ToLowerInvariant();
|
||||
var tmp = _currentFilter;
|
||||
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref tmp, 255))
|
||||
UpdateFilter(tmp);
|
||||
|
||||
var isFocused = ImGui.IsItemActive();
|
||||
if (!_focus)
|
||||
|
|
|
|||
|
|
@ -7,15 +7,12 @@ namespace Glamourer.Gui
|
|||
{
|
||||
public sealed class ImGuiRaii : IDisposable
|
||||
{
|
||||
private int _colorStack = 0;
|
||||
private int _fontStack = 0;
|
||||
private int _styleStack = 0;
|
||||
private float _indentation = 0f;
|
||||
private int _colorStack;
|
||||
private int _fontStack;
|
||||
private int _styleStack;
|
||||
private float _indentation;
|
||||
|
||||
private Stack<Action>? _onDispose = null;
|
||||
|
||||
public ImGuiRaii()
|
||||
{ }
|
||||
private Stack<Action>? _onDispose;
|
||||
|
||||
public static ImGuiRaii NewGroup()
|
||||
=> new ImGuiRaii().Group();
|
||||
|
|
@ -51,6 +48,7 @@ namespace Glamourer.Gui
|
|||
ImGui.PopStyleColor(actualN);
|
||||
_colorStack -= actualN;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +74,7 @@ namespace Glamourer.Gui
|
|||
ImGui.PopStyleVar(actualN);
|
||||
_styleStack -= actualN;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -95,6 +94,7 @@ namespace Glamourer.Gui
|
|||
ImGui.PopFont();
|
||||
--_fontStack;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +105,7 @@ namespace Glamourer.Gui
|
|||
ImGui.Indent(width);
|
||||
_indentation += width;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +135,7 @@ namespace Glamourer.Gui
|
|||
public void End(int n = 1)
|
||||
{
|
||||
var actualN = Math.Min(n, _onDispose?.Count ?? 0);
|
||||
while(actualN-- > 0)
|
||||
while (actualN-- > 0)
|
||||
_onDispose!.Pop()();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Glamourer.Designs;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -14,12 +16,12 @@ namespace Glamourer.Gui
|
|||
{
|
||||
public const float SelectorWidth = 200;
|
||||
public const float MinWindowWidth = 675;
|
||||
public const int GPoseActorId = 201;
|
||||
public const int GPoseObjectId = 201;
|
||||
private const string PluginName = "Glamourer";
|
||||
private readonly string _glamourerHeader;
|
||||
|
||||
private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
||||
private readonly ActorTable _actors;
|
||||
private readonly IReadOnlyDictionary<uint, ModelChara> _models;
|
||||
private readonly IObjectIdentifier _identifier;
|
||||
private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
|
||||
private readonly ImGuiScene.TextureWrap? _legacyTattooIcon;
|
||||
|
|
@ -27,8 +29,8 @@ namespace Glamourer.Gui
|
|||
private readonly DesignManager _designs;
|
||||
private readonly Glamourer _plugin;
|
||||
|
||||
private bool _visible = false;
|
||||
private bool _inGPose = false;
|
||||
private bool _visible;
|
||||
private bool _inGPose;
|
||||
|
||||
public Interface(Glamourer plugin)
|
||||
{
|
||||
|
|
@ -37,31 +39,37 @@ namespace Glamourer.Gui
|
|||
_glamourerHeader = Glamourer.Version.Length > 0
|
||||
? $"{PluginName} v{Glamourer.Version}###{PluginName}Main"
|
||||
: $"{PluginName}###{PluginName}Main";
|
||||
Glamourer.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Glamourer.PluginInterface.UiBuilder.OnBuildUi += Draw;
|
||||
Glamourer.PluginInterface.UiBuilder.OnOpenConfigUi += ToggleVisibility;
|
||||
Dalamud.PluginInterface.UiBuilder.DisableGposeUiHide = true;
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi += ToggleVisibility;
|
||||
|
||||
_characterConstructor = typeof(Character).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new[]
|
||||
{
|
||||
typeof(IntPtr),
|
||||
}, null)!;
|
||||
|
||||
_equipSlotNames = GetEquipSlotNames();
|
||||
|
||||
_stains = GameData.Stains(Glamourer.PluginInterface);
|
||||
_identifier = Penumbra.GameData.GameData.GetIdentifier(Glamourer.PluginInterface);
|
||||
_actors = Glamourer.PluginInterface.ClientState.Actors;
|
||||
_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(Glamourer.PluginInterface);
|
||||
var equip = GameData.ItemsBySlot(Dalamud.GameData);
|
||||
_combos = equip.ToDictionary(kvp => kvp.Key, kvp => CreateCombos(kvp.Key, kvp.Value, stainCombo));
|
||||
_legacyTattooIcon = GetLegacyTattooIcon();
|
||||
}
|
||||
|
||||
public void ToggleVisibility(object _, object _2)
|
||||
public void ToggleVisibility()
|
||||
=> _visible = !_visible;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_legacyTattooIcon?.Dispose();
|
||||
Glamourer.PluginInterface.UiBuilder.OnBuildUi -= Draw;
|
||||
Glamourer.PluginInterface.UiBuilder.OnOpenConfigUi -= ToggleVisibility;
|
||||
Dalamud.PluginInterface.UiBuilder.Draw -= Draw;
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= ToggleVisibility;
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
|
|
@ -72,7 +80,10 @@ namespace Glamourer.Gui
|
|||
ImGui.SetNextWindowSizeConstraints(Vector2.One * MinWindowWidth * ImGui.GetIO().FontGlobalScale,
|
||||
Vector2.One * 5000 * ImGui.GetIO().FontGlobalScale);
|
||||
if (!ImGui.Begin(_glamourerHeader, ref _visible))
|
||||
{
|
||||
ImGui.End();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -80,7 +91,7 @@ namespace Glamourer.Gui
|
|||
if (!raii.Begin(() => ImGui.BeginTabBar("##tabBar"), ImGui.EndTabBar))
|
||||
return;
|
||||
|
||||
_inGPose = _actors[GPoseActorId] != null;
|
||||
_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;
|
||||
|
|
@ -89,9 +100,11 @@ namespace Glamourer.Gui
|
|||
_raceSelectorWidth = _inputIntSize + _percentageSize - _actualIconSize.X;
|
||||
_itemComboWidth = 6 * _actualIconSize.X + 4 * ImGui.GetStyle().ItemSpacing.X - ColorButtonWidth + 1;
|
||||
|
||||
DrawActorTab();
|
||||
DrawPlayerTab();
|
||||
DrawSaves();
|
||||
DrawFixedDesignsTab();
|
||||
DrawConfigTab();
|
||||
DrawRevertablesTab();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Windows.Forms;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using ImGuiNET;
|
||||
|
|
@ -12,14 +16,15 @@ namespace Glamourer.Gui
|
|||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private readonly CharacterSave _currentSave = new();
|
||||
private string _newDesignName = string.Empty;
|
||||
private bool _keyboardFocus = false;
|
||||
private const string DesignNamePopupLabel = "Save Design As...";
|
||||
private const uint RedHeaderColor = 0xFF1818C0;
|
||||
private const uint GreenHeaderColor = 0xFF18C018;
|
||||
private readonly CharacterSave _currentSave = new();
|
||||
private string _newDesignName = string.Empty;
|
||||
private bool _keyboardFocus;
|
||||
private const string DesignNamePopupLabel = "Save Design As...";
|
||||
private const uint RedHeaderColor = 0xFF1818C0;
|
||||
private const uint GreenHeaderColor = 0xFF18C018;
|
||||
private readonly ConstructorInfo _characterConstructor;
|
||||
|
||||
private void DrawActorHeader()
|
||||
private void DrawPlayerHeader()
|
||||
{
|
||||
var color = _player == null ? RedHeaderColor : GreenHeaderColor;
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
|
|
@ -30,14 +35,14 @@ namespace Glamourer.Gui
|
|||
.PushColor(ImGuiCol.ButtonActive, buttonColor)
|
||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.Button($"{_currentActorName}##actorHeader", -Vector2.UnitX * 0.0001f);
|
||||
ImGui.Button($"{_currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
|
||||
}
|
||||
|
||||
private static void DrawCopyClipboardButton(CharacterSave save)
|
||||
{
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (ImGui.Button(FontAwesomeIcon.Clipboard.ToIconString()))
|
||||
Clipboard.SetText(save.ToBase64());
|
||||
ImGui.SetClipboardText(save.ToBase64());
|
||||
ImGui.PopFont();
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Copy customization code to clipboard.");
|
||||
|
|
@ -54,7 +59,7 @@ namespace Glamourer.Gui
|
|||
if (!applyButton)
|
||||
return false;
|
||||
|
||||
var text = Clipboard.GetText();
|
||||
var text = ImGui.GetClipboardText();
|
||||
if (!text.Any())
|
||||
return false;
|
||||
|
||||
|
|
@ -88,41 +93,100 @@ namespace Glamourer.Gui
|
|||
private void DrawTargetPlayerButton()
|
||||
{
|
||||
if (ImGui.Button("Target Player"))
|
||||
Glamourer.PluginInterface.ClientState.Targets.SetCurrentTarget(_player);
|
||||
Dalamud.Targets.SetTarget(_player);
|
||||
}
|
||||
|
||||
private void DrawApplyToPlayerButton(CharacterSave save)
|
||||
{
|
||||
if (ImGui.Button("Apply to Self"))
|
||||
if (!ImGui.Button("Apply to Self"))
|
||||
return;
|
||||
|
||||
var player = _inGPose
|
||||
? (Character?) Dalamud.Objects[GPoseObjectId]
|
||||
: Dalamud.ClientState.LocalPlayer;
|
||||
var fallback = _inGPose ? Dalamud.ClientState.LocalPlayer : null;
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
save.Apply(player);
|
||||
if (_inGPose)
|
||||
save.Apply(fallback!);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, fallback);
|
||||
}
|
||||
|
||||
private const int ModelTypeOffset = 0x01B4;
|
||||
|
||||
private static unsafe int ModelType(GameObject actor)
|
||||
=> *(int*) (actor.Address + ModelTypeOffset);
|
||||
|
||||
private static unsafe void SetModelType(GameObject actor, int value)
|
||||
=> *(int*) (actor.Address + ModelTypeOffset) = value;
|
||||
|
||||
private Character Character(IntPtr address)
|
||||
=> (Character) _characterConstructor.Invoke(new object[]
|
||||
{
|
||||
var player = _inGPose
|
||||
? Glamourer.PluginInterface.ClientState.Actors[GPoseActorId]
|
||||
: Glamourer.PluginInterface.ClientState.LocalPlayer;
|
||||
var fallback = _inGPose ? Glamourer.PluginInterface.ClientState.LocalPlayer : null;
|
||||
if (player != null)
|
||||
address,
|
||||
});
|
||||
|
||||
private Character? CreateCharacter(GameObject? actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
return actor switch
|
||||
{
|
||||
PlayerCharacter p => p,
|
||||
BattleChara b => b,
|
||||
_ => actor.ObjectKind switch
|
||||
{
|
||||
save.Apply(player);
|
||||
if (_inGPose)
|
||||
save.Apply(fallback!);
|
||||
_plugin.UpdateActors(player, fallback);
|
||||
}
|
||||
}
|
||||
ObjectKind.BattleNpc => Character(actor.Address),
|
||||
ObjectKind.Companion => Character(actor.Address),
|
||||
ObjectKind.EventNpc => Character(actor.Address),
|
||||
_ => null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static Character? TransformToCustomizable(Character? actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
if (ModelType(actor) == 0)
|
||||
return actor;
|
||||
|
||||
SetModelType(actor, 0);
|
||||
CharacterCustomization.Default.Write(actor.Address);
|
||||
return actor;
|
||||
}
|
||||
|
||||
private void DrawApplyToTargetButton(CharacterSave save)
|
||||
{
|
||||
if (ImGui.Button("Apply to Target"))
|
||||
{
|
||||
var player = Glamourer.PluginInterface.ClientState.Targets.CurrentTarget;
|
||||
if (player != null)
|
||||
{
|
||||
var fallBackActor = _playerNames[player.Name];
|
||||
save.Apply(player);
|
||||
if (fallBackActor != null)
|
||||
save.Apply(fallBackActor);
|
||||
_plugin.UpdateActors(player, fallBackActor);
|
||||
}
|
||||
}
|
||||
if (!ImGui.Button("Apply to Target"))
|
||||
return;
|
||||
|
||||
var player = TransformToCustomizable(CreateCharacter(Dalamud.Targets.Target));
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
var fallBackCharacter = _gPoseActors.TryGetValue(player.Name.ToString(), out var f) ? f : null;
|
||||
save.Apply(player);
|
||||
if (fallBackCharacter != null)
|
||||
save.Apply(fallBackCharacter);
|
||||
Glamourer.Penumbra.UpdateCharacters(player, fallBackCharacter);
|
||||
}
|
||||
|
||||
private void DrawRevertButton()
|
||||
{
|
||||
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)
|
||||
|
|
@ -130,13 +194,13 @@ namespace Glamourer.Gui
|
|||
try
|
||||
{
|
||||
var (folder, name) = _designs.FileSystem.CreateAllFolders(_newDesignName);
|
||||
if (name.Any())
|
||||
{
|
||||
var newDesign = new Design(folder, name) { Data = save };
|
||||
folder.AddChild(newDesign);
|
||||
_designs.Designs[newDesign.FullName()] = save;
|
||||
_designs.SaveToFile();
|
||||
}
|
||||
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)
|
||||
{
|
||||
|
|
@ -144,13 +208,42 @@ namespace Glamourer.Gui
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawActorPanel()
|
||||
private void DrawMonsterPanel()
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawActorHeader();
|
||||
if (!ImGui.BeginChild("##actorData", -Vector2.One, true))
|
||||
if (DrawApplyClipboardButton())
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Convert to Character"))
|
||||
{
|
||||
TransformToCustomizable(_player);
|
||||
_currentLabel = _currentLabel.Replace("(Monster)", "(NPC)");
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
}
|
||||
|
||||
if (!_inGPose)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
DrawTargetPlayerButton();
|
||||
}
|
||||
|
||||
var currentModel = ModelType(_player!);
|
||||
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;
|
||||
|
||||
SetModelType(_player!, (int) id);
|
||||
Glamourer.Penumbra.UpdateCharacters(_player!);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPlayerPanel()
|
||||
{
|
||||
DrawCopyClipboardButton(_currentSave);
|
||||
ImGui.SameLine();
|
||||
var changes = DrawApplyClipboardButton();
|
||||
|
|
@ -169,9 +262,12 @@ namespace Glamourer.Gui
|
|||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
DrawRevertButton();
|
||||
|
||||
if (DrawCustomization(ref _currentSave.Customizations) && _player != null)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
_currentSave.Customizations.Write(_player.Address);
|
||||
changes = true;
|
||||
}
|
||||
|
|
@ -180,9 +276,25 @@ namespace Glamourer.Gui
|
|||
changes |= DrawMiscellaneous(_currentSave, _player);
|
||||
|
||||
if (_player != null && changes)
|
||||
_plugin.UpdateActors(_player);
|
||||
Glamourer.Penumbra.UpdateCharacters(_player);
|
||||
}
|
||||
|
||||
private void DrawActorPanel()
|
||||
{
|
||||
using var raii = ImGuiRaii.NewGroup();
|
||||
DrawPlayerHeader();
|
||||
if (!ImGui.BeginChild("##playerData", -Vector2.One, true))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_player == null || ModelType(_player) == 0)
|
||||
DrawPlayerPanel();
|
||||
else
|
||||
DrawMonsterPanel();
|
||||
|
||||
ImGui.EndChild();
|
||||
ImGui.EndGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -10,51 +10,83 @@ namespace Glamourer.Gui
|
|||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private Actor? _player;
|
||||
private string _currentActorName = string.Empty;
|
||||
private string _actorFilter = string.Empty;
|
||||
private string _actorFilterLower = string.Empty;
|
||||
private readonly Dictionary<string, Actor?> _playerNames = new(400);
|
||||
private Character? _player;
|
||||
private string _currentLabel = string.Empty;
|
||||
private string _playerFilter = string.Empty;
|
||||
private string _playerFilterLower = string.Empty;
|
||||
private readonly Dictionary<string, int> _playerNames = new(100);
|
||||
private readonly Dictionary<string, Character?> _gPoseActors = new(48);
|
||||
|
||||
private void DrawActorFilter()
|
||||
private void DrawPlayerFilter()
|
||||
{
|
||||
using var raii = new ImGuiRaii()
|
||||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0);
|
||||
ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
|
||||
if (ImGui.InputTextWithHint("##actorFilter", "Filter Players...", ref _actorFilter, 32))
|
||||
_actorFilterLower = _actorFilter.ToLowerInvariant();
|
||||
if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref _playerFilter, 32))
|
||||
_playerFilterLower = _playerFilter.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void DrawActorSelectable(Actor actor, bool gPose)
|
||||
private void DrawGPoseSelectable(Character player)
|
||||
{
|
||||
var actorName = actor.Name;
|
||||
if (!actorName.Any())
|
||||
var playerName = player.Name.ToString();
|
||||
if (!playerName.Any())
|
||||
return;
|
||||
|
||||
if (_playerNames.ContainsKey(actorName))
|
||||
_gPoseActors[playerName] = null;
|
||||
|
||||
DrawSelectable(player, $"{playerName} (GPose)");
|
||||
}
|
||||
|
||||
private static string GetLabel(Character player, string playerName, int num)
|
||||
{
|
||||
if (player.ObjectKind == ObjectKind.Player)
|
||||
return num == 1 ? playerName : $"{playerName} #{num}";
|
||||
|
||||
if (ModelType(player) == 0)
|
||||
return num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)";
|
||||
|
||||
return num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)";
|
||||
}
|
||||
|
||||
private void DrawPlayerSelectable(Character player)
|
||||
{
|
||||
var playerName = player.Name.ToString();
|
||||
if (!playerName.Any())
|
||||
return;
|
||||
|
||||
if (_playerNames.TryGetValue(playerName, out var num))
|
||||
_playerNames[playerName] = ++num;
|
||||
else
|
||||
_playerNames[playerName] = num = 1;
|
||||
|
||||
if (_gPoseActors.ContainsKey(playerName))
|
||||
{
|
||||
_playerNames[actorName] = actor;
|
||||
_gPoseActors[playerName] = player;
|
||||
return;
|
||||
}
|
||||
|
||||
_playerNames.Add(actorName, null);
|
||||
var label = GetLabel(player, playerName, num);
|
||||
DrawSelectable(player, label);
|
||||
}
|
||||
|
||||
var label = gPose ? $"{actorName} (GPose)" : actorName;
|
||||
if (!_actorFilterLower.Any() || actorName.ToLowerInvariant().Contains(_actorFilterLower))
|
||||
if (ImGui.Selectable(label, _currentActorName == actorName))
|
||||
|
||||
private void DrawSelectable(Character player, string label)
|
||||
{
|
||||
if (!_playerFilterLower.Any() || label.ToLowerInvariant().Contains(_playerFilterLower))
|
||||
if (ImGui.Selectable(label, _currentLabel == label))
|
||||
{
|
||||
_currentActorName = actorName;
|
||||
_currentSave.LoadActor(actor);
|
||||
_player = actor;
|
||||
_currentLabel = label;
|
||||
_currentSave.LoadCharacter(player);
|
||||
_player = player;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentActorName == actor.Name)
|
||||
{
|
||||
_currentSave.LoadActor(actor);
|
||||
_player = actor;
|
||||
}
|
||||
if (_currentLabel != label)
|
||||
return;
|
||||
|
||||
_currentSave.LoadCharacter(player);
|
||||
_player = player;
|
||||
}
|
||||
|
||||
private void DrawSelectionButtons()
|
||||
|
|
@ -63,10 +95,10 @@ namespace Glamourer.Gui
|
|||
.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0)
|
||||
.PushFont(UiBuilder.IconFont);
|
||||
Actor? select = null;
|
||||
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
|
||||
Character? select = null;
|
||||
var buttonWidth = Vector2.UnitX * SelectorWidth / 2;
|
||||
if (ImGui.Button(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth))
|
||||
select = Glamourer.PluginInterface.ClientState.LocalPlayer;
|
||||
select = Dalamud.ClientState.LocalPlayer;
|
||||
raii.PopFonts();
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Select the local player character.");
|
||||
|
|
@ -81,49 +113,60 @@ namespace Glamourer.Gui
|
|||
else
|
||||
{
|
||||
if (ImGui.Button(FontAwesomeIcon.HandPointer.ToIconString(), buttonWidth))
|
||||
select = Glamourer.PluginInterface.ClientState.Targets.CurrentTarget;
|
||||
select = CreateCharacter(Dalamud.Targets.Target);
|
||||
}
|
||||
|
||||
raii.PopFonts();
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip("Select the current target, if it is a player actor.");
|
||||
ImGui.SetTooltip("Select the current target, if it is in the list.");
|
||||
|
||||
if (select == null || select.ObjectKind != ObjectKind.Player)
|
||||
if (select == null)
|
||||
return;
|
||||
|
||||
_player = select;
|
||||
_currentActorName = _player.Name;
|
||||
_currentSave.LoadActor(_player);
|
||||
_player = select;
|
||||
_currentLabel = _player.Name.ToString();
|
||||
_currentSave.LoadCharacter(_player);
|
||||
}
|
||||
|
||||
private void DrawActorSelector()
|
||||
private void DrawPlayerSelector()
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawActorFilter();
|
||||
if (!ImGui.BeginChild("##actorSelector",
|
||||
DrawPlayerFilter();
|
||||
if (!ImGui.BeginChild("##playerSelector",
|
||||
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
ImGui.EndGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
_playerNames.Clear();
|
||||
for (var i = GPoseActorId; i < GPoseActorId + 48; ++i)
|
||||
_gPoseActors.Clear();
|
||||
for (var i = GPoseObjectId; i < GPoseObjectId + 48; ++i)
|
||||
{
|
||||
var actor = _actors[i];
|
||||
if (actor == null)
|
||||
var player = CreateCharacter(Dalamud.Objects[i]);
|
||||
if (player == null)
|
||||
break;
|
||||
|
||||
if (actor.ObjectKind == ObjectKind.Player)
|
||||
DrawActorSelectable(actor, true);
|
||||
DrawGPoseSelectable(player);
|
||||
}
|
||||
|
||||
for (var i = 0; i < GPoseActorId; i += 2)
|
||||
for (var i = 0; i < GPoseObjectId; ++i)
|
||||
{
|
||||
var actor = _actors[i];
|
||||
if (actor != null && actor.ObjectKind == ObjectKind.Player)
|
||||
DrawActorSelectable(actor, false);
|
||||
var player = CreateCharacter(Dalamud.Objects[i])!;
|
||||
if (player != null)
|
||||
DrawPlayerSelectable(player);
|
||||
}
|
||||
|
||||
for (var i = GPoseObjectId + 48; i < Dalamud.Objects.Length; ++i)
|
||||
{
|
||||
var player = CreateCharacter(Dalamud.Objects[i])!;
|
||||
if (player != null)
|
||||
DrawPlayerSelectable(player);
|
||||
}
|
||||
|
||||
|
||||
using (var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
using (var _ = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero))
|
||||
{
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
|
@ -132,16 +175,16 @@ namespace Glamourer.Gui
|
|||
ImGui.EndGroup();
|
||||
}
|
||||
|
||||
private void DrawActorTab()
|
||||
private void DrawPlayerTab()
|
||||
{
|
||||
using var raii = new ImGuiRaii();
|
||||
_player = null;
|
||||
if (!raii.Begin(() => ImGui.BeginTabItem("Current Players"), ImGui.EndTabItem))
|
||||
return;
|
||||
|
||||
_player = null;
|
||||
DrawActorSelector();
|
||||
DrawPlayerSelector();
|
||||
|
||||
if (!_currentActorName.Any())
|
||||
if (!_currentLabel.Any())
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
|
|||
|
|
@ -53,11 +53,8 @@ namespace Glamourer.Gui
|
|||
return;
|
||||
}
|
||||
|
||||
if (ImGui.Button(buttonLabel) && _plugin.GetPenumbra())
|
||||
{
|
||||
_plugin.UnregisterFunctions();
|
||||
_plugin.RegisterFunctions();
|
||||
}
|
||||
if (ImGui.Button(buttonLabel))
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(
|
||||
|
|
@ -87,18 +84,25 @@ namespace Glamourer.Gui
|
|||
{
|
||||
cfg.AttachToPenumbra = v;
|
||||
if (v)
|
||||
{
|
||||
if (_plugin.GetPenumbra())
|
||||
_plugin.RegisterFunctions();
|
||||
}
|
||||
Glamourer.Penumbra.Reattach(true);
|
||||
else
|
||||
{
|
||||
_plugin.UnregisterFunctions();
|
||||
}
|
||||
Glamourer.Penumbra.Unattach();
|
||||
});
|
||||
ImGui.SameLine();
|
||||
DrawRestorePenumbraButton();
|
||||
|
||||
DrawConfigCheckMark("Apply Fixed Designs",
|
||||
"Automatically apply fixed designs to characters and redraw them when anything changes.",
|
||||
cfg.ApplyFixedDesigns,
|
||||
v =>
|
||||
{
|
||||
cfg.ApplyFixedDesigns = v;
|
||||
if (v)
|
||||
Glamourer.PlayerWatcher.Enable();
|
||||
else
|
||||
Glamourer.PlayerWatcher.Disable();
|
||||
});
|
||||
|
||||
ImGui.Dummy(Vector2.UnitY * ImGui.GetTextLineHeightWithSpacing() / 2);
|
||||
|
||||
DrawColorPicker("Customization Color", "The color for designs that only apply their character customization.",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -39,20 +39,20 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
private Vector2 _actualIconSize = Vector2.Zero;
|
||||
private float _raceSelectorWidth = 0;
|
||||
private float _inputIntSize = 0;
|
||||
private float _comboSelectorSize = 0;
|
||||
private float _percentageSize = 0;
|
||||
private float _itemComboWidth = 0;
|
||||
private Vector2 _iconSize = Vector2.Zero;
|
||||
private Vector2 _actualIconSize = Vector2.Zero;
|
||||
private float _raceSelectorWidth;
|
||||
private float _inputIntSize;
|
||||
private float _comboSelectorSize;
|
||||
private float _percentageSize;
|
||||
private float _itemComboWidth;
|
||||
|
||||
private bool InputInt(string label, ref int value, int minValue, int maxValue)
|
||||
{
|
||||
var ret = false;
|
||||
var tmp = value + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt(label, ref tmp, 1) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue)
|
||||
if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue) && tmp != value + 1 && tmp >= minValue && tmp <= maxValue)
|
||||
{
|
||||
value = tmp - 1;
|
||||
ret = true;
|
||||
|
|
@ -64,7 +64,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private static (int, Customization.Customization) GetCurrentCustomization(ref ActorCustomization customization, CustomizationId id,
|
||||
private static (int, Customization.Customization) GetCurrentCustomization(ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
var current = set.DataByValue(id, customization[id], out var custom);
|
||||
|
|
@ -78,7 +78,7 @@ namespace Glamourer.Gui
|
|||
return (current, custom!.Value);
|
||||
}
|
||||
|
||||
private bool DrawColorPicker(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
|
||||
private bool DrawColorPicker(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
var ret = false;
|
||||
|
|
@ -93,11 +93,11 @@ namespace Glamourer.Gui
|
|||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var group = ImGuiRaii.NewGroup())
|
||||
using (var _ = ImGuiRaii.NewGroup())
|
||||
{
|
||||
if (InputInt($"##text_{id}", ref current, 1, count))
|
||||
{
|
||||
customization[id] = set.Data(id, current - 1).Value;
|
||||
customization[id] = set.Data(id, current).Value;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawListSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
|
||||
private bool DrawListSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImGuiRaii.NewGroup();
|
||||
|
|
@ -158,7 +158,7 @@ namespace Glamourer.Gui
|
|||
private static readonly Vector4 NoColor = new(1f, 1f, 1f, 1f);
|
||||
private static readonly Vector4 RedColor = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
|
||||
private bool DrawMultiSelector(ref ActorCustomization customization, CustomizationSet set)
|
||||
private bool DrawMultiSelector(ref CharacterCustomization customization, CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImGuiRaii.NewGroup();
|
||||
var ret = false;
|
||||
|
|
@ -242,7 +242,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawIconSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
|
||||
private bool DrawIconSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImGuiRaii.NewGroup();
|
||||
|
|
@ -282,6 +282,9 @@ namespace Glamourer.Gui
|
|||
ret = true;
|
||||
}
|
||||
|
||||
if (id == CustomizationId.Hairstyle && customization.Race == Race.Hrothgar)
|
||||
customization[CustomizationId.Face] = (byte) ((customization[CustomizationId.Hairstyle] + 1) / 2);
|
||||
|
||||
ImGui.Text(label);
|
||||
if (tooltip.Any() && ImGui.IsItemHovered())
|
||||
ImGui.SetTooltip(tooltip);
|
||||
|
|
@ -290,7 +293,7 @@ namespace Glamourer.Gui
|
|||
}
|
||||
|
||||
|
||||
private bool DrawPercentageSelector(string label, string tooltip, ref ActorCustomization customization, CustomizationId id,
|
||||
private bool DrawPercentageSelector(string label, string tooltip, ref CharacterCustomization customization, CustomizationId id,
|
||||
CustomizationSet set)
|
||||
{
|
||||
using var bigGroup = ImGuiRaii.NewGroup();
|
||||
|
|
@ -320,7 +323,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawRaceSelector(ref ActorCustomization customization)
|
||||
private bool DrawRaceSelector(ref CharacterCustomization customization)
|
||||
{
|
||||
using var group = ImGuiRaii.NewGroup();
|
||||
var ret = false;
|
||||
|
|
@ -345,7 +348,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawGenderSelector(ref ActorCustomization customization)
|
||||
private bool DrawGenderSelector(ref CharacterCustomization customization)
|
||||
{
|
||||
var ret = false;
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
|
|
@ -376,7 +379,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawPicker(CustomizationSet set, CustomizationId id, ref ActorCustomization customization)
|
||||
private bool DrawPicker(CustomizationSet set, CustomizationId id, ref CharacterCustomization customization)
|
||||
{
|
||||
if (!set.IsAvailable(id))
|
||||
return false;
|
||||
|
|
@ -394,9 +397,18 @@ namespace Glamourer.Gui
|
|||
return false;
|
||||
}
|
||||
|
||||
private static readonly CustomizationId[] AllCustomizations = (CustomizationId[]) Enum.GetValues(typeof(CustomizationId));
|
||||
private static CustomizationId[] GetCustomizationOrder()
|
||||
{
|
||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
||||
ret[(int) CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
||||
ret[(int) CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
||||
ret[(int) CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawCustomization(ref ActorCustomization custom)
|
||||
private static readonly CustomizationId[] AllCustomizations = GetCustomizationOrder();
|
||||
|
||||
private bool DrawCustomization(ref CharacterCustomization custom)
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
||||
return false;
|
||||
|
|
@ -457,7 +469,7 @@ namespace Glamourer.Gui
|
|||
}
|
||||
|
||||
tmp = custom.SmallIris;
|
||||
if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {set.Option(CustomizationId.EyeColorL)}",
|
||||
if (ImGui.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
ref tmp)
|
||||
&& tmp != custom.SmallIris)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Windows.Forms;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using ImGuiNET;
|
||||
|
|
@ -12,9 +11,10 @@ namespace Glamourer.Gui
|
|||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private int _totalObject = 0;
|
||||
private int _totalObject;
|
||||
|
||||
private Design? _selection = null;
|
||||
private bool _inDesignMode;
|
||||
private Design? _selection;
|
||||
private string _newChildName = string.Empty;
|
||||
|
||||
private void DrawDesignSelector()
|
||||
|
|
@ -50,7 +50,7 @@ namespace Glamourer.Gui
|
|||
if (_selection!.Data.WriteProtected || !applyButton)
|
||||
return;
|
||||
|
||||
var text = Clipboard.GetText();
|
||||
var text = ImGui.GetClipboardText();
|
||||
if (!text.Any())
|
||||
return;
|
||||
|
||||
|
|
@ -208,7 +208,8 @@ namespace Glamourer.Gui
|
|||
{
|
||||
using var raii = new ImGuiRaii();
|
||||
raii.PushStyle(ImGuiStyleVar.IndentSpacing, 12.5f * ImGui.GetIO().FontGlobalScale);
|
||||
if (!raii.Begin(() => ImGui.BeginTabItem("Saves"), ImGui.EndTabItem))
|
||||
_inDesignMode = raii.Begin(() => ImGui.BeginTabItem("Designs"), ImGui.EndTabItem);
|
||||
if (!_inDesignMode)
|
||||
return;
|
||||
|
||||
DrawDesignSelector();
|
||||
|
|
@ -279,8 +280,7 @@ namespace Glamourer.Gui
|
|||
|
||||
private void ContextMenu(IFileSystemBase child)
|
||||
{
|
||||
var label = $"##fsPopup{child.FullName()}";
|
||||
var renameLabel = $"{label}_rename";
|
||||
var label = $"##fsPopup{child.FullName()}";
|
||||
if (ImGui.BeginPopup(label))
|
||||
{
|
||||
if (ImGui.MenuItem("Delete"))
|
||||
|
|
@ -289,7 +289,7 @@ namespace Glamourer.Gui
|
|||
RenameChildInput(child);
|
||||
|
||||
if (child is Design d && ImGui.MenuItem("Copy to Clipboard"))
|
||||
Clipboard.SetText(d.Data.ToBase64());
|
||||
ImGui.SetClipboardText(d.Data.ToBase64());
|
||||
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
|
@ -310,12 +310,12 @@ namespace Glamourer.Gui
|
|||
|
||||
var changesStates = save.SetHatState || save.SetVisorState || save.SetWeaponState || save.IsWet || save.Alpha != 1.0f;
|
||||
if (save.WriteCustomizations)
|
||||
if (save.WriteEquipment != ActorEquipMask.None)
|
||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
return white;
|
||||
else
|
||||
return changesStates ? white : Glamourer.Config.CustomizationColor;
|
||||
|
||||
if (save.WriteEquipment != ActorEquipMask.None)
|
||||
if (save.WriteEquipment != CharacterEquipMask.None)
|
||||
return changesStates ? white : Glamourer.Config.EquipmentColor;
|
||||
|
||||
return changesStates ? Glamourer.Config.StateColor : grey;
|
||||
|
|
|
|||
|
|
@ -15,10 +15,17 @@ namespace Glamourer.Gui
|
|||
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
|
||||
}
|
||||
|
||||
if (stainCombo.Draw(string.Empty, out var newStain) && _player != null && !newStain.RowIndex.Equals(stainIdx))
|
||||
if (stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx))
|
||||
{
|
||||
newStain.Write(_player.Address, slot);
|
||||
return true;
|
||||
if (_player != null)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
newStain.Write(_player.Address, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inDesignMode && (_selection?.Data.WriteStain(slot, newStain.RowIndex) ?? false))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -27,22 +34,29 @@ namespace Glamourer.Gui
|
|||
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item? item)
|
||||
{
|
||||
var currentName = item?.Name.ToString() ?? "Nothing";
|
||||
if (equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && _player != null && newItem.Base.RowId != item?.RowId)
|
||||
if (equipCombo.Draw(currentName, out var newItem, _itemComboWidth) && newItem.Base.RowId != item?.RowId)
|
||||
{
|
||||
newItem.Write(_player.Address);
|
||||
return true;
|
||||
if (_player != null)
|
||||
{
|
||||
Glamourer.RevertableDesigns.Add(_player);
|
||||
newItem.Write(_player.Address);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_inDesignMode && (_selection?.Data.WriteItem(newItem) ?? false))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawCheckbox(ActorEquipMask flag, ref ActorEquipMask mask)
|
||||
private static bool DrawCheckbox(CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var tmp = (uint) mask;
|
||||
var ret = false;
|
||||
if (ImGui.CheckboxFlags($"##flag_{(uint) flag}", ref tmp, (uint) flag) && tmp != (uint) mask)
|
||||
{
|
||||
mask = (ActorEquipMask) tmp;
|
||||
mask = (CharacterEquipMask) tmp;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +65,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquipSlot(EquipSlot slot, ActorArmor equip)
|
||||
private bool DrawEquipSlot(EquipSlot slot, CharacterArmor equip)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
|
|
@ -63,7 +77,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquipSlotWithCheck(EquipSlot slot, ActorArmor equip, ActorEquipMask flag, ref ActorEquipMask mask)
|
||||
private bool DrawEquipSlotWithCheck(EquipSlot slot, CharacterArmor equip, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = DrawCheckbox(flag, ref mask);
|
||||
ImGui.SameLine();
|
||||
|
|
@ -71,7 +85,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawWeapon(EquipSlot slot, ActorWeapon weapon)
|
||||
private bool DrawWeapon(EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
|
|
@ -83,7 +97,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawWeaponWithCheck(EquipSlot slot, ActorWeapon weapon, ActorEquipMask flag, ref ActorEquipMask mask)
|
||||
private bool DrawWeaponWithCheck(EquipSlot slot, CharacterWeapon weapon, CharacterEquipMask flag, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = DrawCheckbox(flag, ref mask);
|
||||
ImGui.SameLine();
|
||||
|
|
@ -91,7 +105,7 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquip(ActorEquipment equip)
|
||||
private bool DrawEquip(CharacterEquipment equip)
|
||||
{
|
||||
var ret = false;
|
||||
if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
|
|
@ -113,23 +127,23 @@ namespace Glamourer.Gui
|
|||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawEquip(ActorEquipment equip, ref ActorEquipMask mask)
|
||||
private bool DrawEquip(CharacterEquipment equip, ref CharacterEquipMask mask)
|
||||
{
|
||||
var ret = false;
|
||||
if (ImGui.CollapsingHeader("Character Equipment"))
|
||||
{
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, ActorEquipMask.MainHand, ref mask);
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, ActorEquipMask.OffHand, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, ActorEquipMask.Head, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, ActorEquipMask.Body, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, ActorEquipMask.Hands, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, ActorEquipMask.Legs, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, ActorEquipMask.Feet, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, ActorEquipMask.Ears, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, ActorEquipMask.Neck, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, ActorEquipMask.Wrists, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, ActorEquipMask.RFinger, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, ActorEquipMask.LFinger, ref mask);
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.MainHand, equip.MainHand, CharacterEquipMask.MainHand, ref mask);
|
||||
ret |= DrawWeaponWithCheck(EquipSlot.OffHand, equip.OffHand, CharacterEquipMask.OffHand, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Head, equip.Head, CharacterEquipMask.Head, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Body, equip.Body, CharacterEquipMask.Body, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Hands, equip.Hands, CharacterEquipMask.Hands, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Legs, equip.Legs, CharacterEquipMask.Legs, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Feet, equip.Feet, CharacterEquipMask.Feet, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Ears, equip.Ears, CharacterEquipMask.Ears, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Neck, equip.Neck, CharacterEquipMask.Neck, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.Wrists, equip.Wrists, CharacterEquipMask.Wrists, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.RFinger, equip.RFinger, CharacterEquipMask.RFinger, ref mask);
|
||||
ret |= DrawEquipSlotWithCheck(EquipSlot.LFinger, equip.LFinger, CharacterEquipMask.LFinger, ref mask);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
140
Glamourer/Gui/InterfaceFixedDesigns.cs
Normal file
140
Glamourer/Gui/InterfaceFixedDesigns.cs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private const string FixDragDropLabel = "##FixDragDrop";
|
||||
|
||||
private List<string>? _fullPathCache;
|
||||
private string _newFixCharacterName = string.Empty;
|
||||
private string _newFixDesignPath = string.Empty;
|
||||
private Design? _newFixDesign;
|
||||
private int _fixDragDropIdx = -1;
|
||||
|
||||
private static unsafe bool IsDropping()
|
||||
=> ImGui.AcceptDragDropPayload(FixDragDropLabel).NativePtr != null;
|
||||
|
||||
private void DrawFixedDesignsTab()
|
||||
{
|
||||
using var raii = new ImGuiRaii();
|
||||
if (!raii.Begin(() => ImGui.BeginTabItem("Fixed Designs"), ImGui.EndTabItem))
|
||||
{
|
||||
_fullPathCache = null;
|
||||
_newFixDesign = null;
|
||||
_newFixDesignPath = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
_fullPathCache ??= _plugin.FixedDesigns.Data.Select(d => d.Design.FullName()).ToList();
|
||||
|
||||
raii.Begin(() => ImGui.BeginTable("##FixedTable", 3), ImGui.EndTable);
|
||||
|
||||
var buttonWidth = 23.5f * ImGuiHelpers.GlobalScale;
|
||||
|
||||
|
||||
ImGui.TableSetupColumn("##DeleteColumn", ImGuiTableColumnFlags.WidthFixed, 2 * buttonWidth);
|
||||
ImGui.TableSetupColumn("Character", ImGuiTableColumnFlags.WidthFixed, 200 * ImGuiHelpers.GlobalScale);
|
||||
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);
|
||||
}
|
||||
|
||||
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.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(path);
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
raii.PushFont(UiBuilder.IconFont);
|
||||
|
||||
ImGui.SetCursorPosX(xPos);
|
||||
if (_newFixDesign == null || _newFixCharacterName == string.Empty)
|
||||
{
|
||||
raii.PushStyle(ImGuiStyleVar.Alpha, 0.5f);
|
||||
ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix");
|
||||
raii.PopStyles();
|
||||
}
|
||||
else if (ImGui.Button($"{FontAwesomeIcon.Plus.ToIconChar()}##NewFix"))
|
||||
{
|
||||
_fullPathCache.Add(_newFixDesignPath);
|
||||
_plugin.FixedDesigns.Add(_newFixCharacterName, _newFixDesign, false);
|
||||
_newFixCharacterName = string.Empty;
|
||||
_newFixDesignPath = string.Empty;
|
||||
_newFixDesign = null;
|
||||
}
|
||||
|
||||
raii.PopFonts();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputTextWithHint("##NewFix", "Enter new Character", ref _newFixCharacterName, 32);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (!raii.Begin(() => ImGui.BeginCombo("##NewFixPath", _newFixDesignPath), ImGui.EndCombo))
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
|
|
@ -27,7 +24,7 @@ namespace Glamourer.Gui
|
|||
}
|
||||
|
||||
// Go through a whole customization struct and fix up all settings that need fixing.
|
||||
private static void FixUpAttributes(ref ActorCustomization customization)
|
||||
private static void FixUpAttributes(ref CharacterCustomization customization)
|
||||
{
|
||||
var set = Glamourer.Customization.GetList(customization.Clan, customization.Gender);
|
||||
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
|
||||
|
|
@ -46,7 +43,7 @@ namespace Glamourer.Gui
|
|||
break;
|
||||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customization[id], out var value) < 0)
|
||||
if (set.DataByValue(id, customization[id], out _) < 0)
|
||||
if (count == 0)
|
||||
customization[id] = 0;
|
||||
else
|
||||
|
|
@ -57,7 +54,7 @@ namespace Glamourer.Gui
|
|||
}
|
||||
|
||||
// Change a race and fix up all required customizations afterwards.
|
||||
private static bool ChangeRace(ref ActorCustomization customization, SubRace clan)
|
||||
private static bool ChangeRace(ref CharacterCustomization customization, SubRace clan)
|
||||
{
|
||||
if (clan == customization.Clan)
|
||||
return false;
|
||||
|
|
@ -79,7 +76,7 @@ namespace Glamourer.Gui
|
|||
}
|
||||
|
||||
// Change a gender and fix up all required customizations afterwards.
|
||||
private static bool ChangeGender(ref ActorCustomization customization, Gender gender)
|
||||
private static bool ChangeGender(ref CharacterCustomization customization, Gender gender)
|
||||
{
|
||||
if (gender == customization.Gender)
|
||||
return false;
|
||||
|
|
@ -159,7 +156,7 @@ namespace Glamourer.Gui
|
|||
break;
|
||||
case DesignNameUse.NewDesign:
|
||||
var empty = new CharacterSave();
|
||||
empty.Load(ActorCustomization.Default);
|
||||
empty.Load(CharacterCustomization.Default);
|
||||
empty.WriteCustomizations = false;
|
||||
SaveNewDesign(empty);
|
||||
break;
|
||||
|
|
@ -173,7 +170,7 @@ namespace Glamourer.Gui
|
|||
case DesignNameUse.FromClipboard:
|
||||
try
|
||||
{
|
||||
var text = Clipboard.GetText();
|
||||
var text = ImGui.GetClipboardText();
|
||||
var save = CharacterSave.FromString(text);
|
||||
SaveNewDesign(save);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace Glamourer.Gui
|
|||
{
|
||||
var rawImage = new byte[resource.Length];
|
||||
resource.Read(rawImage, 0, (int) resource.Length);
|
||||
return Glamourer.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
|
||||
return Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -61,7 +61,7 @@ namespace Glamourer.Gui
|
|||
|
||||
private static Dictionary<EquipSlot, string> GetEquipSlotNames()
|
||||
{
|
||||
var sheet = Glamourer.PluginInterface.Data.GetExcelSheet<Addon>();
|
||||
var sheet = Dalamud.GameData.GetExcelSheet<Addon>()!;
|
||||
var ret = new Dictionary<EquipSlot, string>(12)
|
||||
{
|
||||
[EquipSlot.MainHand] = sheet.GetRow(738)?.Text.ToString() ?? "Main Hand",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
|
|
@ -18,7 +18,18 @@ namespace Glamourer.Gui
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool DrawMiscellaneous(CharacterSave save, Actor? player)
|
||||
private static bool DrawDisableButton(string label, bool disabled)
|
||||
{
|
||||
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"))
|
||||
|
|
|
|||
88
Glamourer/Gui/InterfaceRevertables.cs
Normal file
88
Glamourer/Gui/InterfaceRevertables.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal partial class Interface
|
||||
{
|
||||
private string? _currentRevertableName;
|
||||
private CharacterSave? _currentRevertable;
|
||||
|
||||
private void DrawRevertablesSelector()
|
||||
{
|
||||
ImGui.BeginGroup();
|
||||
DrawPlayerFilter();
|
||||
if (!ImGui.BeginChild("##playerSelector",
|
||||
new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
private void DrawRevertablePanel()
|
||||
{
|
||||
using var group = ImGuiRaii.NewGroup();
|
||||
{
|
||||
var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
|
||||
using var raii = new ImGuiRaii()
|
||||
.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))
|
||||
{
|
||||
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 raii = new ImGuiRaii();
|
||||
if (!raii.Begin(() => ImGui.BeginTabItem("Revertables"), ImGui.EndTabItem))
|
||||
return;
|
||||
|
||||
DrawRevertablesSelector();
|
||||
|
||||
if (_currentRevertableName == null)
|
||||
return;
|
||||
|
||||
ImGui.SameLine();
|
||||
DrawRevertablePanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Forms;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.FileSystem;
|
||||
using Glamourer.Gui;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.PlayerWatch;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class Glamourer : IDalamudPlugin
|
||||
{
|
||||
public const int RequiredPenumbraShareVersion = 1;
|
||||
|
||||
private const string HelpString = "[Copy|Apply|Save],[Name or PlaceHolder],<Name for Save>";
|
||||
|
||||
public string Name
|
||||
=> "Glamourer";
|
||||
|
||||
public static DalamudPluginInterface PluginInterface = null!;
|
||||
public static GlamourerConfig Config = null!;
|
||||
private Interface _interface = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
public DesignManager Designs = null!;
|
||||
public IPlayerWatcher PlayerWatcher = null!;
|
||||
|
||||
public static string Version = string.Empty;
|
||||
|
||||
public static IPenumbraApi? Penumbra;
|
||||
|
||||
private Dalamud.Dalamud _dalamud = null!;
|
||||
private List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)> _plugins = null!;
|
||||
|
||||
private void SetDalamud(DalamudPluginInterface pi)
|
||||
{
|
||||
var dalamud = (Dalamud.Dalamud?) pi.GetType()
|
||||
?.GetField("dalamud", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?.GetValue(pi);
|
||||
|
||||
_dalamud = dalamud ?? throw new Exception("Could not obtain Dalamud.");
|
||||
}
|
||||
|
||||
private static void PenumbraTooltip(object? it)
|
||||
{
|
||||
if (it is Lumina.Excel.GeneratedSheets.Item)
|
||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||
}
|
||||
|
||||
private void PenumbraRightClick(MouseButton button, object? it)
|
||||
{
|
||||
if (button == MouseButton.Right && it is Lumina.Excel.GeneratedSheets.Item item)
|
||||
{
|
||||
var actors = PluginInterface.ClientState.Actors;
|
||||
var gPose = actors[Interface.GPoseActorId];
|
||||
var player = actors[0];
|
||||
var writeItem = new Item(item, string.Empty);
|
||||
if (gPose != null)
|
||||
{
|
||||
writeItem.Write(gPose.Address);
|
||||
UpdateActors(gPose, player);
|
||||
}
|
||||
else if (player != null)
|
||||
{
|
||||
writeItem.Write(player.Address);
|
||||
UpdateActors(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterFunctions()
|
||||
{
|
||||
if (Penumbra == null || !Penumbra.Valid)
|
||||
return;
|
||||
|
||||
Penumbra!.ChangedItemTooltip += PenumbraTooltip;
|
||||
Penumbra!.ChangedItemClicked += PenumbraRightClick;
|
||||
}
|
||||
|
||||
public void UnregisterFunctions()
|
||||
{
|
||||
if (Penumbra == null || !Penumbra.Valid)
|
||||
return;
|
||||
|
||||
Penumbra!.ChangedItemTooltip -= PenumbraTooltip;
|
||||
Penumbra!.ChangedItemClicked -= PenumbraRightClick;
|
||||
}
|
||||
|
||||
private void SetPlugins(DalamudPluginInterface pi)
|
||||
{
|
||||
var pluginManager = _dalamud?.GetType()
|
||||
?.GetProperty("PluginManager", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.GetValue(_dalamud);
|
||||
|
||||
if (pluginManager == null)
|
||||
throw new Exception("Could not obtain plugin manager.");
|
||||
|
||||
var pluginsList =
|
||||
(List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)>?) pluginManager
|
||||
?.GetType()
|
||||
?.GetProperty("Plugins", BindingFlags.Instance | BindingFlags.Public)
|
||||
?.GetValue(pluginManager);
|
||||
|
||||
_plugins = pluginsList ?? throw new Exception("Could not obtain Dalamud.");
|
||||
}
|
||||
|
||||
public bool GetPenumbra()
|
||||
{
|
||||
if (Penumbra?.Valid ?? false)
|
||||
return true;
|
||||
|
||||
var plugin = _plugins.Find(p
|
||||
=> p.Definition.InternalName == "Penumbra"
|
||||
&& string.Compare(p.Definition.AssemblyVersion, "0.4.0.3", StringComparison.Ordinal) >= 0).Plugin;
|
||||
|
||||
var penumbra = (IPenumbraApiBase?) plugin?.GetType().GetProperty("Api", BindingFlags.Instance | BindingFlags.Public)
|
||||
?.GetValue(plugin);
|
||||
if (penumbra != null && penumbra.Valid && penumbra.ApiVersion >= RequiredPenumbraShareVersion)
|
||||
Penumbra = (IPenumbraApi) penumbra!;
|
||||
else
|
||||
Penumbra = null;
|
||||
|
||||
return Penumbra != null;
|
||||
}
|
||||
|
||||
public void Initialize(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
Version = Assembly.GetExecutingAssembly()?.GetName().Version.ToString() ?? "";
|
||||
PluginInterface = pluginInterface;
|
||||
Config = GlamourerConfig.Create();
|
||||
Customization = CustomizationManager.Create(PluginInterface);
|
||||
SetDalamud(PluginInterface);
|
||||
SetPlugins(PluginInterface);
|
||||
Designs = new DesignManager(PluginInterface);
|
||||
if (GetPenumbra() && Config.AttachToPenumbra)
|
||||
RegisterFunctions();
|
||||
PlayerWatcher = PlayerWatchFactory.Create(PluginInterface);
|
||||
|
||||
PluginInterface.CommandManager.AddHandler("/glamourer", new CommandInfo(OnGlamourer)
|
||||
{
|
||||
HelpMessage = "Open or close the Glamourer window.",
|
||||
});
|
||||
PluginInterface.CommandManager.AddHandler("/glamour", new CommandInfo(OnGlamour)
|
||||
{
|
||||
HelpMessage = $"Use Glamourer Functions: {HelpString}",
|
||||
});
|
||||
|
||||
_interface = new Interface(this);
|
||||
}
|
||||
|
||||
public void OnGlamourer(string command, string arguments)
|
||||
=> _interface?.ToggleVisibility(null!, null!);
|
||||
|
||||
private Actor? GetActor(string name)
|
||||
{
|
||||
var lowerName = name.ToLowerInvariant();
|
||||
return lowerName switch
|
||||
{
|
||||
"" => null,
|
||||
"<me>" => PluginInterface.ClientState.Actors[Interface.GPoseActorId] ?? PluginInterface.ClientState.LocalPlayer,
|
||||
"self" => PluginInterface.ClientState.Actors[Interface.GPoseActorId] ?? PluginInterface.ClientState.LocalPlayer,
|
||||
"<t>" => PluginInterface.ClientState.Targets.CurrentTarget,
|
||||
"target" => PluginInterface.ClientState.Targets.CurrentTarget,
|
||||
"<f>" => PluginInterface.ClientState.Targets.FocusTarget,
|
||||
"focus" => PluginInterface.ClientState.Targets.FocusTarget,
|
||||
"<mo>" => PluginInterface.ClientState.Targets.MouseOverTarget,
|
||||
"mouseover" => PluginInterface.ClientState.Targets.MouseOverTarget,
|
||||
_ => PluginInterface.ClientState.Actors.LastOrDefault(
|
||||
a => string.Equals(a.Name, lowerName, StringComparison.InvariantCultureIgnoreCase)),
|
||||
};
|
||||
}
|
||||
|
||||
public void CopyToClipboard(Actor actor)
|
||||
{
|
||||
var save = new CharacterSave();
|
||||
save.LoadActor(actor);
|
||||
Clipboard.SetText(save.ToBase64());
|
||||
}
|
||||
|
||||
public void ApplyCommand(Actor actor, string target)
|
||||
{
|
||||
CharacterSave? save = null;
|
||||
if (target.ToLowerInvariant() == "clipboard")
|
||||
try
|
||||
{
|
||||
save = CharacterSave.FromString(Clipboard.GetText());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.PrintError("Clipboard does not contain a valid customization string.");
|
||||
}
|
||||
else if (!Designs.FileSystem.Find(target, out var child) || child is not Design d)
|
||||
PluginInterface.Framework.Gui.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(actor);
|
||||
UpdateActors(actor);
|
||||
}
|
||||
|
||||
public void SaveCommand(Actor actor, string path)
|
||||
{
|
||||
var save = new CharacterSave();
|
||||
save.LoadActor(actor);
|
||||
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)
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.PrintError("Could not save file:");
|
||||
PluginInterface.Framework.Gui.Chat.PrintError($" {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGlamour(string command, string arguments)
|
||||
{
|
||||
static void PrintHelp()
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.Print("Usage:");
|
||||
PluginInterface.Framework.Gui.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 actor = GetActor(split[1]);
|
||||
if (actor == null)
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.Print($"Could not find actor for {split[1]}.");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (split[0].ToLowerInvariant())
|
||||
{
|
||||
case "copy":
|
||||
CopyToClipboard(actor);
|
||||
return;
|
||||
case "apply":
|
||||
{
|
||||
if (split.Length < 3)
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.Print("Applying requires a name for the save to be applied or 'clipboard'.");
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyCommand(actor, split[2]);
|
||||
|
||||
return;
|
||||
}
|
||||
case "save":
|
||||
{
|
||||
if (split.Length < 3)
|
||||
{
|
||||
PluginInterface.Framework.Gui.Chat.Print("Saving requires a name for the save.");
|
||||
return;
|
||||
}
|
||||
|
||||
SaveCommand(actor, split[2]);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
PrintHelp();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PlayerWatcher?.Dispose();
|
||||
UnregisterFunctions();
|
||||
_interface?.Dispose();
|
||||
PluginInterface.CommandManager.RemoveHandler("/glamour");
|
||||
PluginInterface.CommandManager.RemoveHandler("/glamourer");
|
||||
PluginInterface.Dispose();
|
||||
}
|
||||
|
||||
// Update actors without triggering PlayerWatcher Events,
|
||||
// then manually redraw using Penumbra.
|
||||
public void UpdateActors(Actor actor, Actor? gPoseOriginalActor = null)
|
||||
{
|
||||
var newEquip = PlayerWatcher.UpdateActorWithoutEvent(actor);
|
||||
Penumbra?.RedrawActor(actor, RedrawType.WithSettings);
|
||||
|
||||
// Special case for carrying over changes to the gPose actor to the regular player actor, too.
|
||||
if (gPoseOriginalActor != null)
|
||||
{
|
||||
newEquip.Write(gPoseOriginalActor.Address);
|
||||
PlayerWatcher.UpdateActorWithoutEvent(gPoseOriginalActor);
|
||||
Penumbra?.RedrawActor(gPoseOriginalActor, RedrawType.AfterGPoseWithSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Glamourer/PenumbraAttach.cs
Normal file
140
Glamourer/PenumbraAttach.cs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using Glamourer.Gui;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class PenumbraAttach : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraShareVersion = 3;
|
||||
|
||||
private ICallGateSubscriber<ChangedItemType, uint, object>? _tooltipSubscriber;
|
||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
||||
|
||||
public PenumbraAttach(bool attach)
|
||||
=> Reattach(attach);
|
||||
|
||||
public void Reattach(bool attach)
|
||||
{
|
||||
try
|
||||
{
|
||||
Unattach();
|
||||
|
||||
var versionSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<int>("Penumbra.ApiVersion");
|
||||
var version = versionSubscriber.InvokeFunc();
|
||||
if (version != RequiredPenumbraShareVersion)
|
||||
throw new Exception($"Invalid Version {version}, required Version {RequiredPenumbraShareVersion}.");
|
||||
|
||||
_redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
||||
_redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
||||
|
||||
if (!attach)
|
||||
return;
|
||||
|
||||
_tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<ChangedItemType, uint, object>("Penumbra.ChangedItemTooltip");
|
||||
_clickSubscriber =
|
||||
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
||||
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
||||
_clickSubscriber.Subscribe(PenumbraRightClick);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Debug($"Could not attach to Penumbra:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Unattach()
|
||||
{
|
||||
_tooltipSubscriber?.Unsubscribe(PenumbraTooltip);
|
||||
_clickSubscriber?.Unsubscribe(PenumbraRightClick);
|
||||
_tooltipSubscriber = null;
|
||||
_clickSubscriber = null;
|
||||
_redrawSubscriberName = null;
|
||||
_redrawSubscriberObject = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> Unattach();
|
||||
|
||||
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||
{
|
||||
if (type == ChangedItemType.Item)
|
||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||
}
|
||||
|
||||
private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
||||
{
|
||||
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
||||
return;
|
||||
|
||||
var gPose = Dalamud.Objects[Interface.GPoseObjectId] as Character;
|
||||
var player = Dalamud.Objects[0] as Character;
|
||||
var item = (Lumina.Excel.GeneratedSheets.Item) type.GetObject(id)!;
|
||||
var writeItem = new Item(item, string.Empty);
|
||||
if (gPose != null)
|
||||
{
|
||||
writeItem.Write(gPose.Address);
|
||||
UpdateCharacters(gPose, player);
|
||||
}
|
||||
else if (player != null)
|
||||
{
|
||||
writeItem.Write(player.Address);
|
||||
UpdateCharacters(player);
|
||||
}
|
||||
}
|
||||
|
||||
public void RedrawObject(GameObject actor, RedrawType settings, bool repeat)
|
||||
{
|
||||
if (_redrawSubscriberObject != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_redrawSubscriberObject.InvokeAction(actor, (int) settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (repeat)
|
||||
{
|
||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
||||
RedrawObject(actor, settings, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (repeat)
|
||||
{
|
||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
||||
RedrawObject(actor, settings, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Debug("Trying to redraw object, but not attached to Penumbra.");
|
||||
}
|
||||
}
|
||||
|
||||
// Update objects without triggering PlayerWatcher Events,
|
||||
// then manually redraw using Penumbra.
|
||||
public void UpdateCharacters(Character character, Character? gPoseOriginalCharacter = null)
|
||||
{
|
||||
var newEquip = Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(character);
|
||||
RedrawObject(character, RedrawType.WithSettings, true);
|
||||
|
||||
// Special case for carrying over changes to the gPose player to the regular player, too.
|
||||
if (gPoseOriginalCharacter == null)
|
||||
return;
|
||||
|
||||
newEquip.Write(gPoseOriginalCharacter.Address);
|
||||
Glamourer.PlayerWatcher.UpdatePlayerWithoutEvent(gPoseOriginalCharacter);
|
||||
RedrawObject(gPoseOriginalCharacter, RedrawType.AfterGPoseWithSettings, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
images/icon.png
Normal file
BIN
images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
32
repo.json
32
repo.json
|
|
@ -1,19 +1,21 @@
|
|||
[
|
||||
{
|
||||
"Author": "Ottermandias",
|
||||
"Name": "Glamourer",
|
||||
"Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.",
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "0.0.3.0",
|
||||
"TestingAssemblyVersion": "0.0.3.0",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 3,
|
||||
"IsHide": "False",
|
||||
"IsTestingExclusive": "false",
|
||||
"DownloadCount": 1,
|
||||
"LastUpdate": 1618608322,
|
||||
"DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/raw/main/Glamourer.zip",
|
||||
"DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/raw/main/Glamourer.zip",
|
||||
"Author": "Ottermandias",
|
||||
"Name": "Glamourer",
|
||||
"Punchline": "Change and save appearance of players.",
|
||||
"Description": "Adds functionality to change and store appearance of players, customization and equip. Requires Penumbra to be installed and activated to work. Can also add preview options to the Changed Items tab for Penumbra.",
|
||||
"Tags": [ "Appearance", "Glamour", "Race", "Outfit", "Armor", "Clothes", "Skins", "Customization", "Design", "Character" ],
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "0.0.5.4",
|
||||
"TestingAssemblyVersion": "0.0.5.4",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 4,
|
||||
"IsHide": "False",
|
||||
"IsTestingExclusive": "false",
|
||||
"DownloadCount": 1,
|
||||
"LastUpdate": 1618608322,
|
||||
"DownloadLinkInstall": "https://github.com/Ottermandias/Glamourer/raw/api4/Glamourer.zip",
|
||||
"DownloadLinkUpdate": "https://github.com/Ottermandias/Glamourer/raw/api4/Glamourer.zip"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue